@rawdash/connector-hubspot 0.21.1 → 0.23.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.
- package/dist/index.d.ts +47 -0
- package/dist/index.js +54 -9
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -31,6 +31,13 @@ type HubSpotResource = HubSpotPhase;
|
|
|
31
31
|
declare const hubspotResources: {
|
|
32
32
|
readonly hubspot_contact: {
|
|
33
33
|
readonly shape: "entity";
|
|
34
|
+
readonly filterable: [{
|
|
35
|
+
readonly field: "lifecycleStage";
|
|
36
|
+
readonly ops: ["eq"];
|
|
37
|
+
}, {
|
|
38
|
+
readonly field: "leadStatus";
|
|
39
|
+
readonly ops: ["eq"];
|
|
40
|
+
}];
|
|
34
41
|
readonly description: "CRM contacts with email, lifecycle stage, lead status, owner, and creation time.";
|
|
35
42
|
readonly endpoint: "POST /crm/v3/objects/contacts/search";
|
|
36
43
|
readonly responses: {
|
|
@@ -39,6 +46,13 @@ declare const hubspotResources: {
|
|
|
39
46
|
};
|
|
40
47
|
readonly hubspot_company: {
|
|
41
48
|
readonly shape: "entity";
|
|
49
|
+
readonly filterable: [{
|
|
50
|
+
readonly field: "lifecycleStage";
|
|
51
|
+
readonly ops: ["eq"];
|
|
52
|
+
}, {
|
|
53
|
+
readonly field: "industry";
|
|
54
|
+
readonly ops: ["eq"];
|
|
55
|
+
}];
|
|
42
56
|
readonly description: "CRM companies with name, domain, industry, lifecycle stage, and creation time.";
|
|
43
57
|
readonly endpoint: "POST /crm/v3/objects/companies/search";
|
|
44
58
|
readonly responses: {
|
|
@@ -47,6 +61,13 @@ declare const hubspotResources: {
|
|
|
47
61
|
};
|
|
48
62
|
readonly hubspot_deal: {
|
|
49
63
|
readonly shape: "entity";
|
|
64
|
+
readonly filterable: [{
|
|
65
|
+
readonly field: "dealStage";
|
|
66
|
+
readonly ops: ["eq"];
|
|
67
|
+
}, {
|
|
68
|
+
readonly field: "pipeline";
|
|
69
|
+
readonly ops: ["eq"];
|
|
70
|
+
}];
|
|
50
71
|
readonly description: "CRM deals with name, stage, pipeline, amount, close date, owner, and creation time.";
|
|
51
72
|
readonly endpoint: "POST /crm/v3/objects/deals/search";
|
|
52
73
|
readonly responses: {
|
|
@@ -55,6 +76,7 @@ declare const hubspotResources: {
|
|
|
55
76
|
};
|
|
56
77
|
readonly hubspot_deal_stage_change: {
|
|
57
78
|
readonly shape: "event";
|
|
79
|
+
readonly filterable: [];
|
|
58
80
|
readonly description: "Deal stage-change events derived from deal property history, one event per stage transition.";
|
|
59
81
|
readonly endpoint: "GET /crm/v3/objects/deals?propertiesWithHistory=dealstage";
|
|
60
82
|
readonly responses: {
|
|
@@ -72,6 +94,7 @@ declare const hubspotResources: {
|
|
|
72
94
|
};
|
|
73
95
|
readonly hubspot_email_campaign: {
|
|
74
96
|
readonly shape: "entity";
|
|
97
|
+
readonly filterable: [];
|
|
75
98
|
readonly description: "Marketing email campaigns with name, subject, sender, type, send date, and recipient count.";
|
|
76
99
|
readonly endpoint: "GET /email/public/v1/campaigns";
|
|
77
100
|
readonly responses: {
|
|
@@ -149,6 +172,13 @@ declare class HubSpotConnector extends BaseConnector<HubSpotSettings, HubSpotCre
|
|
|
149
172
|
static readonly resources: {
|
|
150
173
|
readonly hubspot_contact: {
|
|
151
174
|
readonly shape: "entity";
|
|
175
|
+
readonly filterable: [{
|
|
176
|
+
readonly field: "lifecycleStage";
|
|
177
|
+
readonly ops: ["eq"];
|
|
178
|
+
}, {
|
|
179
|
+
readonly field: "leadStatus";
|
|
180
|
+
readonly ops: ["eq"];
|
|
181
|
+
}];
|
|
152
182
|
readonly description: "CRM contacts with email, lifecycle stage, lead status, owner, and creation time.";
|
|
153
183
|
readonly endpoint: "POST /crm/v3/objects/contacts/search";
|
|
154
184
|
readonly responses: {
|
|
@@ -157,6 +187,13 @@ declare class HubSpotConnector extends BaseConnector<HubSpotSettings, HubSpotCre
|
|
|
157
187
|
};
|
|
158
188
|
readonly hubspot_company: {
|
|
159
189
|
readonly shape: "entity";
|
|
190
|
+
readonly filterable: [{
|
|
191
|
+
readonly field: "lifecycleStage";
|
|
192
|
+
readonly ops: ["eq"];
|
|
193
|
+
}, {
|
|
194
|
+
readonly field: "industry";
|
|
195
|
+
readonly ops: ["eq"];
|
|
196
|
+
}];
|
|
160
197
|
readonly description: "CRM companies with name, domain, industry, lifecycle stage, and creation time.";
|
|
161
198
|
readonly endpoint: "POST /crm/v3/objects/companies/search";
|
|
162
199
|
readonly responses: {
|
|
@@ -165,6 +202,13 @@ declare class HubSpotConnector extends BaseConnector<HubSpotSettings, HubSpotCre
|
|
|
165
202
|
};
|
|
166
203
|
readonly hubspot_deal: {
|
|
167
204
|
readonly shape: "entity";
|
|
205
|
+
readonly filterable: [{
|
|
206
|
+
readonly field: "dealStage";
|
|
207
|
+
readonly ops: ["eq"];
|
|
208
|
+
}, {
|
|
209
|
+
readonly field: "pipeline";
|
|
210
|
+
readonly ops: ["eq"];
|
|
211
|
+
}];
|
|
168
212
|
readonly description: "CRM deals with name, stage, pipeline, amount, close date, owner, and creation time.";
|
|
169
213
|
readonly endpoint: "POST /crm/v3/objects/deals/search";
|
|
170
214
|
readonly responses: {
|
|
@@ -173,6 +217,7 @@ declare class HubSpotConnector extends BaseConnector<HubSpotSettings, HubSpotCre
|
|
|
173
217
|
};
|
|
174
218
|
readonly hubspot_deal_stage_change: {
|
|
175
219
|
readonly shape: "event";
|
|
220
|
+
readonly filterable: [];
|
|
176
221
|
readonly description: "Deal stage-change events derived from deal property history, one event per stage transition.";
|
|
177
222
|
readonly endpoint: "GET /crm/v3/objects/deals?propertiesWithHistory=dealstage";
|
|
178
223
|
readonly responses: {
|
|
@@ -190,6 +235,7 @@ declare class HubSpotConnector extends BaseConnector<HubSpotSettings, HubSpotCre
|
|
|
190
235
|
};
|
|
191
236
|
readonly hubspot_email_campaign: {
|
|
192
237
|
readonly shape: "entity";
|
|
238
|
+
readonly filterable: [];
|
|
193
239
|
readonly description: "Marketing email campaigns with name, subject, sender, type, send date, and recipient count.";
|
|
194
240
|
readonly endpoint: "GET /email/public/v1/campaigns";
|
|
195
241
|
readonly responses: {
|
|
@@ -326,6 +372,7 @@ declare class HubSpotConnector extends BaseConnector<HubSpotSettings, HubSpotCre
|
|
|
326
372
|
private buildHeaders;
|
|
327
373
|
private apiGet;
|
|
328
374
|
private apiPost;
|
|
375
|
+
private singleSpec;
|
|
329
376
|
private buildSearchBody;
|
|
330
377
|
private fetchSearchPage;
|
|
331
378
|
private writeSearchPhase;
|
package/dist/index.js
CHANGED
|
@@ -68,6 +68,7 @@ var doc = defineConnectorDoc({
|
|
|
68
68
|
tagline: "Sync CRM contacts, companies, and deals plus deal stage-change events and marketing email campaign stats from HubSpot.",
|
|
69
69
|
vendor: {
|
|
70
70
|
name: "HubSpot",
|
|
71
|
+
domain: "hubspot.com",
|
|
71
72
|
apiDocs: "https://developers.hubspot.com/docs/api/overview",
|
|
72
73
|
website: "https://www.hubspot.com"
|
|
73
74
|
},
|
|
@@ -144,6 +145,11 @@ var ENTITY_TYPE_BY_PHASE = {
|
|
|
144
145
|
deals: "hubspot_deal",
|
|
145
146
|
email_campaigns: "hubspot_email_campaign"
|
|
146
147
|
};
|
|
148
|
+
var SEARCH_PUSH_FIELDS = {
|
|
149
|
+
contacts: { lifecycleStage: "lifecyclestage", leadStatus: "hs_lead_status" },
|
|
150
|
+
companies: { lifecycleStage: "lifecyclestage", industry: "industry" },
|
|
151
|
+
deals: { dealStage: "dealstage", pipeline: "pipeline" }
|
|
152
|
+
};
|
|
147
153
|
var DEAL_STAGE_EVENT = "hubspot_deal_stage_change";
|
|
148
154
|
var EMAIL_STATS_METRIC = "hubspot_email_stats";
|
|
149
155
|
function finiteNumberOrNull(value) {
|
|
@@ -229,30 +235,44 @@ var campaignsSchema = z.array(campaignDetailSchema);
|
|
|
229
235
|
var hubspotResources = defineResources({
|
|
230
236
|
hubspot_contact: {
|
|
231
237
|
shape: "entity",
|
|
238
|
+
filterable: [
|
|
239
|
+
{ field: "lifecycleStage", ops: ["eq"] },
|
|
240
|
+
{ field: "leadStatus", ops: ["eq"] }
|
|
241
|
+
],
|
|
232
242
|
description: "CRM contacts with email, lifecycle stage, lead status, owner, and creation time.",
|
|
233
243
|
endpoint: "POST /crm/v3/objects/contacts/search",
|
|
234
244
|
responses: { contacts: contactsSchema }
|
|
235
245
|
},
|
|
236
246
|
hubspot_company: {
|
|
237
247
|
shape: "entity",
|
|
248
|
+
filterable: [
|
|
249
|
+
{ field: "lifecycleStage", ops: ["eq"] },
|
|
250
|
+
{ field: "industry", ops: ["eq"] }
|
|
251
|
+
],
|
|
238
252
|
description: "CRM companies with name, domain, industry, lifecycle stage, and creation time.",
|
|
239
253
|
endpoint: "POST /crm/v3/objects/companies/search",
|
|
240
254
|
responses: { companies: companiesSchema }
|
|
241
255
|
},
|
|
242
256
|
hubspot_deal: {
|
|
243
257
|
shape: "entity",
|
|
258
|
+
filterable: [
|
|
259
|
+
{ field: "dealStage", ops: ["eq"] },
|
|
260
|
+
{ field: "pipeline", ops: ["eq"] }
|
|
261
|
+
],
|
|
244
262
|
description: "CRM deals with name, stage, pipeline, amount, close date, owner, and creation time.",
|
|
245
263
|
endpoint: "POST /crm/v3/objects/deals/search",
|
|
246
264
|
responses: { deals: dealsSchema }
|
|
247
265
|
},
|
|
248
266
|
hubspot_deal_stage_change: {
|
|
249
267
|
shape: "event",
|
|
268
|
+
filterable: [],
|
|
250
269
|
description: "Deal stage-change events derived from deal property history, one event per stage transition.",
|
|
251
270
|
endpoint: "GET /crm/v3/objects/deals?propertiesWithHistory=dealstage",
|
|
252
271
|
responses: { deal_events: dealEventsSchema }
|
|
253
272
|
},
|
|
254
273
|
hubspot_email_campaign: {
|
|
255
274
|
shape: "entity",
|
|
275
|
+
filterable: [],
|
|
256
276
|
description: "Marketing email campaigns with name, subject, sender, type, send date, and recipient count.",
|
|
257
277
|
endpoint: "GET /email/public/v1/campaigns",
|
|
258
278
|
responses: { email_campaigns: campaignsSchema }
|
|
@@ -279,6 +299,17 @@ var hubspotResources = defineResources({
|
|
|
279
299
|
}
|
|
280
300
|
});
|
|
281
301
|
var id = "hubspot";
|
|
302
|
+
function pushableEq(filter, field) {
|
|
303
|
+
if (!filter) {
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
for (const clause of filter) {
|
|
307
|
+
if ("field" in clause && clause.field === field && clause.op === "eq" && typeof clause.value === "string") {
|
|
308
|
+
return clause.value;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
282
313
|
var HubSpotConnector = class _HubSpotConnector extends BaseConnector {
|
|
283
314
|
static id = id;
|
|
284
315
|
static resources = hubspotResources;
|
|
@@ -315,23 +346,37 @@ var HubSpotConnector = class _HubSpotConnector extends BaseConnector {
|
|
|
315
346
|
signal
|
|
316
347
|
});
|
|
317
348
|
}
|
|
349
|
+
singleSpec(options, resource) {
|
|
350
|
+
const specs = options.fetchSpecs?.[resource];
|
|
351
|
+
return specs && specs.length === 1 ? specs[0] : void 0;
|
|
352
|
+
}
|
|
318
353
|
buildSearchBody(phase, after, options) {
|
|
319
354
|
const modifiedProperty = MODIFIED_PROPERTY[phase];
|
|
320
|
-
const
|
|
355
|
+
const filters = [];
|
|
321
356
|
if (options.since) {
|
|
322
357
|
const sinceMs = new Date(options.since).getTime();
|
|
323
358
|
if (Number.isFinite(sinceMs)) {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
operator: "GTE",
|
|
329
|
-
value: String(sinceMs)
|
|
330
|
-
}
|
|
331
|
-
]
|
|
359
|
+
filters.push({
|
|
360
|
+
propertyName: modifiedProperty,
|
|
361
|
+
operator: "GTE",
|
|
362
|
+
value: String(sinceMs)
|
|
332
363
|
});
|
|
333
364
|
}
|
|
334
365
|
}
|
|
366
|
+
const pushMap = SEARCH_PUSH_FIELDS[phase];
|
|
367
|
+
if (pushMap) {
|
|
368
|
+
const filter = this.singleSpec(
|
|
369
|
+
options,
|
|
370
|
+
ENTITY_TYPE_BY_PHASE[phase]
|
|
371
|
+
)?.filter;
|
|
372
|
+
for (const [ourField, propertyName] of Object.entries(pushMap)) {
|
|
373
|
+
const value = pushableEq(filter, ourField);
|
|
374
|
+
if (value !== null) {
|
|
375
|
+
filters.push({ propertyName, operator: "EQ", value });
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
const filterGroups = filters.length > 0 ? [{ filters }] : [];
|
|
335
380
|
return {
|
|
336
381
|
filterGroups,
|
|
337
382
|
sorts: [{ propertyName: modifiedProperty, direction: "ASCENDING" }],
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../connector-shared/src/errors.ts","../../../connector-shared/src/retry.ts","../../../connector-shared/src/version.ts","../../../connector-shared/src/request.ts","../../../connector-shared/src/rate-limit.ts","../../../connector-shared/src/map-concurrent.ts","../../../connector-shared/src/sanitize.ts","../../../connector-shared/src/epoch.ts","../../../connector-shared/src/pagination.ts","../../../connector-shared/src/logger.ts","../src/hubspot.ts","../src/index.ts"],"sourcesContent":["import type { HttpResponse } from './types';\n\nexport type HttpErrorKind =\n | 'transient'\n | 'rate_limit'\n | 'auth'\n | 'upstream_bug'\n | 'client_bug';\n\nexport abstract class HttpClientError extends Error {\n abstract readonly kind: HttpErrorKind;\n readonly response?: HttpResponse;\n\n constructor(message: string, response?: HttpResponse) {\n super(message);\n this.name = new.target.name;\n this.response = response;\n }\n}\n\nexport class TransientError extends HttpClientError {\n readonly kind = 'transient' as const;\n}\n\nexport class RateLimitError extends HttpClientError {\n readonly kind = 'rate_limit' as const;\n readonly retryAfter?: Date;\n\n constructor(message: string, response?: HttpResponse, retryAfter?: Date) {\n super(message, response);\n this.retryAfter = retryAfter;\n }\n}\n\nexport class AuthError extends HttpClientError {\n readonly kind = 'auth' as const;\n}\n\nexport class UpstreamBugError extends HttpClientError {\n readonly kind = 'upstream_bug' as const;\n}\n\nexport class ClientBugError extends HttpClientError {\n readonly kind = 'client_bug' as const;\n}\n\nexport function classifyStatus(status: number): HttpErrorKind {\n if (status === 429) {\n return 'rate_limit';\n }\n if (status === 401 || status === 403) {\n return 'auth';\n }\n if (status === 408) {\n return 'transient';\n }\n if (status >= 500) {\n return 'upstream_bug';\n }\n if (status >= 400) {\n return 'client_bug';\n }\n return 'client_bug';\n}\n\nexport function errorForStatus(\n message: string,\n response: HttpResponse,\n retryAfter?: Date,\n): HttpClientError {\n const kind = classifyStatus(response.status);\n switch (kind) {\n case 'rate_limit':\n return new RateLimitError(message, response, retryAfter);\n case 'auth':\n return new AuthError(message, response);\n case 'transient':\n return new TransientError(message, response);\n case 'upstream_bug':\n return new UpstreamBugError(message, response);\n case 'client_bug':\n return new ClientBugError(message, response);\n }\n}\n","import { HttpClientError, RateLimitError, TransientError } from './errors';\n\nexport interface RetryPolicy {\n maxAttempts?: number;\n initialDelayMs?: number;\n maxDelayMs?: number;\n retryOn?: (status: number | null, err?: Error) => boolean;\n}\n\nexport const defaultRetryOn = (status: number | null, err?: Error): boolean => {\n if (err instanceof RateLimitError) {\n return true;\n }\n if (err instanceof TransientError) {\n return true;\n }\n if (status === null) {\n return err instanceof Error && !(err instanceof HttpClientError);\n }\n if (status === 408 || status === 429) {\n return true;\n }\n if (status >= 500) {\n return true;\n }\n return false;\n};\n\nexport function backoffDelayMs(\n attempt: number,\n policy: Required<Pick<RetryPolicy, 'initialDelayMs' | 'maxDelayMs'>>,\n): number {\n const base = policy.initialDelayMs * 2 ** attempt;\n const jitter = base * 0.25 * Math.random();\n return Math.min(base + jitter, policy.maxDelayMs);\n}\n\nexport function parseRetryAfter(\n headerValue: string | null,\n now: Date = new Date(),\n): Date | undefined {\n if (!headerValue) {\n return undefined;\n }\n const trimmed = headerValue.trim();\n if (/^\\d+$/.test(trimmed)) {\n return new Date(now.getTime() + Number(trimmed) * 1000);\n }\n const parsed = Date.parse(trimmed);\n if (Number.isNaN(parsed)) {\n return undefined;\n }\n return new Date(parsed);\n}\n\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n if (signal?.aborted) {\n return Promise.reject(signal.reason ?? new Error('Aborted'));\n }\n return new Promise<void>((resolve, reject) => {\n const onAbort = () => {\n clearTimeout(timer);\n reject(signal!.reason ?? new Error('Aborted'));\n };\n const timer = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n}\n","export const HTTP_CLIENT_VERSION = '0.0.0';\n\nexport const DEFAULT_USER_AGENT = `rawdash-connector/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;\n\nexport function connectorUserAgent(connectorId: string): string {\n return `rawdash-connector-${connectorId}/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;\n}\n","import {\n AuthError,\n ClientBugError,\n HttpClientError,\n RateLimitError,\n TransientError,\n UpstreamBugError,\n errorForStatus,\n} from './errors';\nimport { defaultRetryOn, parseRetryAfter, sleep } from './retry';\nimport type { FetchLike, HttpMethod, HttpRequest, HttpResponse } from './types';\nimport { DEFAULT_USER_AGENT } from './version';\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\nconst DEFAULT_MAX_ATTEMPTS = 3;\nconst DEFAULT_INITIAL_DELAY_MS = 1000;\nconst DEFAULT_MAX_DELAY_MS = 60_000;\nconst OBSERVER_TIMEOUT_MS = 250;\n\nexport interface RequestObservation {\n url: string;\n method: HttpMethod;\n status: number;\n resource: string;\n requestId: string;\n body: unknown;\n}\n\nexport type RequestObserver = (\n event: RequestObservation,\n) => void | Promise<void>;\n\nexport interface RequestOptions {\n fetch?: FetchLike;\n observer?: RequestObserver;\n resource: string;\n requestId?: string;\n}\n\nasync function notifyObserver(\n observer: RequestObserver,\n event: RequestObservation,\n): Promise<void> {\n let result: void | Promise<void>;\n try {\n result = observer(event);\n } catch (err) {\n console.warn('[connector-shared] request observer threw:', err);\n return;\n }\n if (!(result instanceof Promise)) {\n return;\n }\n const guarded = result.catch((err) => {\n console.warn('[connector-shared] request observer rejected:', err);\n });\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<void>((resolve) => {\n timer = setTimeout(resolve, OBSERVER_TIMEOUT_MS);\n });\n try {\n await Promise.race([guarded, timeout]);\n } finally {\n if (timer) {\n clearTimeout(timer);\n }\n }\n}\n\nfunction newRequestId(): string {\n const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;\n if (c?.randomUUID) {\n return c.randomUUID();\n }\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\nfunction mergeHeaders(\n defaults: Record<string, string>,\n overrides: Record<string, string> | undefined,\n): Record<string, string> {\n const merged: Record<string, string> = {};\n for (const [k, v] of Object.entries(defaults)) {\n merged[k.toLowerCase()] = v;\n }\n if (overrides) {\n for (const [k, v] of Object.entries(overrides)) {\n merged[k.toLowerCase()] = v;\n }\n }\n return merged;\n}\n\nfunction linkTimeoutSignal(\n parent: AbortSignal | undefined,\n timeoutMs: number,\n): { signal: AbortSignal; cancel: () => void } {\n const controller = new AbortController();\n const onParentAbort = () => {\n controller.abort(parent?.reason);\n };\n if (parent) {\n if (parent.aborted) {\n controller.abort(parent.reason);\n } else {\n parent.addEventListener('abort', onParentAbort, { once: true });\n }\n }\n const timer = setTimeout(() => {\n controller.abort(new Error(`Request timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n return {\n signal: controller.signal,\n cancel: () => {\n clearTimeout(timer);\n if (parent) {\n parent.removeEventListener('abort', onParentAbort);\n }\n },\n };\n}\n\nasync function readBody(res: Response, parseJson: boolean): Promise<unknown> {\n if (res.status === 204 || res.status === 205) {\n return null;\n }\n const contentType = res.headers.get('content-type') ?? '';\n if (parseJson && contentType.includes('application/json')) {\n const text = await res.text();\n if (text.length === 0) {\n return null;\n }\n return JSON.parse(text);\n }\n return res.text();\n}\n\nexport async function request<T = unknown>(\n req: HttpRequest,\n options: RequestOptions,\n): Promise<HttpResponse<T>> {\n const fetchImpl: FetchLike = options.fetch ?? (globalThis.fetch as FetchLike);\n const retry = req.retry ?? {};\n const maxAttempts = retry.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;\n const initialDelayMs = retry.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS;\n const maxDelayMs = retry.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;\n const retryOn = retry.retryOn ?? defaultRetryOn;\n const timeoutMs = req.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const parseJson = req.parseJson ?? true;\n\n const headers = mergeHeaders(\n {\n 'User-Agent': DEFAULT_USER_AGENT,\n Accept: 'application/json',\n },\n req.headers,\n );\n\n let lastErr: Error | undefined;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n req.signal?.throwIfAborted();\n\n const { signal, cancel } = linkTimeoutSignal(req.signal, timeoutMs);\n let res: Response;\n try {\n res = await fetchImpl(req.url, {\n method: req.method ?? 'GET',\n headers,\n body: req.body as RequestInit['body'],\n signal,\n });\n } catch (err) {\n cancel();\n if (req.signal?.aborted) {\n throw req.signal.reason ?? err;\n }\n const error = err instanceof Error ? err : new Error(String(err));\n lastErr = error;\n if (attempt < maxAttempts - 1 && retryOn(null, error)) {\n const delay = computeDelay(attempt, initialDelayMs, maxDelayMs);\n await sleep(delay, req.signal);\n continue;\n }\n throw new TransientError(error.message);\n }\n cancel();\n\n const body = await readBody(res, parseJson);\n const httpResponse: HttpResponse<T> = {\n status: res.status,\n headers: res.headers,\n body: body as T,\n };\n if (req.rateLimit) {\n const state = req.rateLimit.parse(res.headers);\n if (state) {\n httpResponse.rateLimitState = state;\n }\n }\n\n if (options.observer) {\n await notifyObserver(options.observer, {\n url: req.url,\n method: req.method ?? 'GET',\n status: res.status,\n resource: options.resource,\n requestId: options.requestId ?? newRequestId(),\n body,\n });\n }\n\n if (res.ok) {\n return httpResponse;\n }\n\n const retryAfter = parseRetryAfter(res.headers.get('retry-after'));\n const message = `HTTP ${res.status} ${res.statusText} for ${req.method ?? 'GET'} ${req.url}`;\n const err = errorForStatus(message, httpResponse, retryAfter);\n\n if (\n attempt < maxAttempts - 1 &&\n retryOn(res.status, err) &&\n !(err instanceof AuthError) &&\n !(err instanceof ClientBugError)\n ) {\n lastErr = err;\n let delay = computeDelay(attempt, initialDelayMs, maxDelayMs);\n if (err instanceof RateLimitError && retryAfter) {\n const wait = retryAfter.getTime() - Date.now();\n if (wait > 0) {\n delay = Math.min(wait, maxDelayMs);\n }\n }\n await sleep(delay, req.signal);\n continue;\n }\n\n throw err;\n }\n\n throw lastErr ?? new UpstreamBugError('Exhausted retry attempts');\n}\n\nfunction computeDelay(\n attempt: number,\n initialDelayMs: number,\n maxDelayMs: number,\n): number {\n const base = initialDelayMs * 2 ** attempt;\n const jitter = base * 0.25 * Math.random();\n return Math.min(base + jitter, maxDelayMs);\n}\n\nexport { HttpClientError };\n","export interface RateLimitState {\n remaining: number;\n resetAt: Date;\n}\n\nexport interface RateLimitPolicy {\n parse(headers: Headers): RateLimitState | null;\n}\n\nexport interface StandardRateLimitPolicyConfig {\n remainingHeader: string;\n resetHeader: string;\n resetUnit: 's' | 'ms';\n resetFallbackMs?: number;\n}\n\nexport function standardRateLimitPolicy(\n config: StandardRateLimitPolicyConfig,\n): RateLimitPolicy {\n const { remainingHeader, resetHeader, resetUnit, resetFallbackMs } = config;\n const multiplier = resetUnit === 's' ? 1000 : 1;\n return {\n parse(h) {\n const remainingRaw = h.get(remainingHeader);\n if (remainingRaw === null || remainingRaw.trim() === '') {\n return null;\n }\n const remaining = Number(remainingRaw);\n if (!Number.isFinite(remaining)) {\n return null;\n }\n const resetRaw = h.get(resetHeader);\n if (resetRaw === null) {\n if (resetFallbackMs === undefined) {\n return null;\n }\n return {\n remaining,\n resetAt: new Date(Date.now() + resetFallbackMs),\n };\n }\n if (resetRaw.trim() === '') {\n return null;\n }\n const reset = Number(resetRaw);\n if (!Number.isFinite(reset) || reset < 0) {\n return null;\n }\n const resetMs = reset * multiplier;\n if (!Number.isFinite(resetMs)) {\n return null;\n }\n return { remaining, resetAt: new Date(resetMs) };\n },\n };\n}\n","export async function mapWithConcurrency<T, R>(\n items: readonly T[],\n concurrency: number,\n fn: (item: T, index: number) => Promise<R>,\n): Promise<R[]> {\n const results = new Array<R>(items.length);\n if (items.length === 0) {\n return results;\n }\n const normalized = Number.isFinite(concurrency) ? Math.floor(concurrency) : 1;\n const limit = Math.max(1, Math.min(normalized, items.length));\n let next = 0;\n let failed = false;\n\n async function worker(): Promise<void> {\n while (!failed) {\n const i = next++;\n if (i >= items.length) {\n return;\n }\n try {\n results[i] = await fn(items[i]!, i);\n } catch (err) {\n failed = true;\n throw err;\n }\n }\n }\n\n const workers: Promise<void>[] = [];\n for (let w = 0; w < limit; w++) {\n workers.push(worker());\n }\n await Promise.all(workers);\n return results;\n}\n","export interface SanitizeAllowedUrlOptions {\n url: string | null;\n host: string;\n pathname: string;\n protocol?: 'https:' | 'http:';\n}\n\nexport function sanitizeAllowedUrl(\n options: SanitizeAllowedUrlOptions,\n): string | null {\n const { url, host, pathname, protocol = 'https:' } = options;\n if (url === null) {\n return null;\n }\n try {\n const u = new URL(url);\n if (u.protocol !== protocol || u.host !== host || u.pathname !== pathname) {\n return null;\n }\n return u.toString();\n } catch {\n return null;\n }\n}\n","export type EpochUnit = 'ms' | 's' | 'iso';\n\nexport function parseEpoch(\n value: number | string | null | undefined,\n unit: EpochUnit,\n): number | null {\n if (value === null || value === undefined) {\n return null;\n }\n if (unit === 'iso') {\n if (typeof value !== 'string') {\n return null;\n }\n const ms = new Date(value).getTime();\n return Number.isFinite(ms) ? ms : null;\n }\n if (typeof value === 'string' && value.trim() === '') {\n return null;\n }\n const n = typeof value === 'number' ? value : Number(value);\n if (!Number.isFinite(n)) {\n return null;\n }\n const result = unit === 's' ? n * 1000 : n;\n return Number.isFinite(result) ? result : null;\n}\n","import { request } from './request';\nimport type { HttpRequest } from './types';\n\nexport function parseLinkHeader(header: string | null): Record<string, string> {\n if (!header) {\n return {};\n }\n const result: Record<string, string> = {};\n for (const part of header.split(',')) {\n const match = part.match(/<([^>]+)>\\s*;\\s*rel=\"([^\"]+)\"/);\n if (match) {\n result[match[2]!] = match[1]!;\n }\n }\n return result;\n}\n\nexport async function* paginateLink<T>(\n initial: HttpRequest,\n parse: (body: unknown) => T[],\n options: { resource: string },\n): AsyncIterable<T> {\n let next: string | null = initial.url;\n while (next) {\n const res: Awaited<ReturnType<typeof request>> = await request(\n {\n ...initial,\n url: next,\n },\n { resource: options.resource },\n );\n for (const item of parse(res.body)) {\n yield item;\n }\n const links = parseLinkHeader(res.headers.get('link'));\n next = links['next'] ?? null;\n }\n}\n\nexport async function* paginateCursor<T>(\n initial: HttpRequest,\n parse: (body: unknown) => { items: T[]; nextCursor: string | null },\n buildNext: (req: HttpRequest, cursor: string) => HttpRequest,\n options: { resource: string },\n): AsyncIterable<T> {\n let req: HttpRequest = initial;\n while (true) {\n const res = await request(req, { resource: options.resource });\n const { items, nextCursor } = parse(res.body);\n for (const item of items) {\n yield item;\n }\n if (!nextCursor) {\n return;\n }\n req = buildNext(req, nextCursor);\n }\n}\n\nexport async function* paginatePage<T>(\n initial: HttpRequest,\n parse: (body: unknown) => { items: T[]; hasMore: boolean },\n buildPage: (req: HttpRequest, page: number) => HttpRequest,\n options: { resource: string },\n): AsyncIterable<T> {\n let page = 1;\n while (true) {\n const req = page === 1 ? initial : buildPage(initial, page);\n const res = await request(req, { resource: options.resource });\n const { items, hasMore } = parse(res.body);\n for (const item of items) {\n yield item;\n }\n if (!hasMore || items.length === 0) {\n return;\n }\n page++;\n }\n}\n","export type LogFields = Record<string, unknown>;\n\nexport interface ConnectorLogger {\n info(event: string, fields?: LogFields): void;\n warn(event: string, fields?: LogFields): void;\n}\n\nexport interface ConnectorLoggerOptions {\n scope: string;\n}\n\nconst MAX_VALUE_LEN = 120;\n\nfunction truncate(s: string, max = MAX_VALUE_LEN): string {\n if (s.length <= max) {\n return s;\n }\n return `${s.slice(0, max - 1)}…`;\n}\n\nfunction formatValue(value: unknown): string {\n if (value === null) {\n return 'null';\n }\n if (value === undefined) {\n return '';\n }\n if (typeof value === 'number' || typeof value === 'boolean') {\n return String(value);\n }\n if (typeof value === 'string') {\n const t = truncate(value);\n if (/[\\s\"=]/.test(t)) {\n return JSON.stringify(t);\n }\n return t;\n }\n if (typeof value === 'bigint') {\n return value.toString();\n }\n let json: string | undefined;\n try {\n json = JSON.stringify(value);\n } catch {\n json = undefined;\n }\n return truncate(json ?? String(value));\n}\n\nexport function formatLogFields(fields?: LogFields): string {\n if (!fields) {\n return '';\n }\n const parts: string[] = [];\n for (const [k, v] of Object.entries(fields)) {\n if (v === undefined) {\n continue;\n }\n parts.push(`${k}=${formatValue(v)}`);\n }\n return parts.length > 0 ? ` ${parts.join(' ')}` : '';\n}\n\nexport function formatLogLine(\n scope: string,\n event: string,\n fields?: LogFields,\n): string {\n return `[${scope}] ${event}${formatLogFields(fields)}`;\n}\n\nexport function createDefaultConnectorLogger(\n opts: ConnectorLoggerOptions,\n): ConnectorLogger {\n return {\n info(event, fields) {\n console.info(formatLogLine(opts.scope, event, fields));\n },\n warn(event, fields) {\n console.warn(formatLogLine(opts.scope, event, fields));\n },\n };\n}\n\nconst NOOP_LOGGER: ConnectorLogger = {\n info() {},\n warn() {},\n};\n\nexport function noopConnectorLogger(): ConnectorLogger {\n return NOOP_LOGGER;\n}\n","import {\n type HttpResponse,\n connectorUserAgent,\n parseEpoch,\n} from '@rawdash/connector-shared';\nimport {\n BaseConnector,\n type ConnectorContext,\n type ConnectorDoc,\n type CredentialsSchema,\n type StorageHandle,\n type SyncOptions,\n type SyncResult,\n defineConfigFields,\n defineConnectorDoc,\n defineResources,\n makeChunkedCursorGuard,\n paginateChunked,\n schemasFromResources,\n selectActivePhases,\n} from '@rawdash/core';\nimport { z } from 'zod';\n\nexport const configFields = defineConfigFields(\n z.object({\n accessToken: z.object({ $secret: z.string() }).meta({\n label: 'Private App access token',\n description:\n 'HubSpot private app access token with read scopes for contacts, companies, deals, and marketing email. Create one at Settings → Integrations → Private Apps.',\n placeholder: 'pat-na1-...',\n secret: true,\n }),\n resources: z\n .array(\n z.enum([\n 'contacts',\n 'companies',\n 'deals',\n 'deal_events',\n 'email_campaigns',\n 'email_stats',\n ]),\n )\n .nonempty()\n .optional()\n .meta({\n label: 'Resources',\n description:\n 'Which HubSpot resources to sync. Omit to sync all resources. The access token only needs read scopes for the resources listed here.',\n }),\n }),\n);\n\nexport const doc: ConnectorDoc = defineConnectorDoc({\n displayName: 'HubSpot',\n category: 'sales',\n brandColor: '#FF7A59',\n tagline:\n 'Sync CRM contacts, companies, and deals plus deal stage-change events and marketing email campaign stats from HubSpot.',\n vendor: {\n name: 'HubSpot',\n apiDocs: 'https://developers.hubspot.com/docs/api/overview',\n website: 'https://www.hubspot.com',\n },\n auth: {\n summary:\n 'A HubSpot private app access token with read scopes for the resources you sync (contacts, companies, deals, and marketing email).',\n setup: [\n 'In HubSpot, go to Settings → Integrations → Private Apps and create a private app.',\n 'Grant read scopes for the resources you intend to sync (CRM contacts, companies, deals, and marketing email).',\n 'Copy the generated access token (starts with `pat-`).',\n 'Store it as a secret and reference it from the connector config as `accessToken: secret(\"HUBSPOT_ACCESS_TOKEN\")`.',\n ],\n },\n rateLimit:\n 'HubSpot allows 100 requests / 10s; the Search API caps results at 10,000 per query.',\n limitations: [\n 'Deal stage-change events are rewritten on every sync because the deal list endpoint has no incremental `since` filter.',\n 'Marketing email campaign data comes from the legacy email campaigns API and is only available for marketing emails.',\n 'Very large CRM portfolios may not backfill in full because the Search API caps at 10,000 results per query.',\n ],\n});\n\nexport interface HubSpotSettings {\n resources?: readonly HubSpotResource[];\n}\n\ntype HubSpotProperties = Record<string, string | null | undefined>;\n\ninterface CrmRecord {\n id: string;\n properties: HubSpotProperties;\n createdAt: string;\n updatedAt: string;\n archived?: boolean;\n}\n\ninterface CrmSearchResponse {\n total?: number;\n results: CrmRecord[];\n paging?: { next?: { after?: string } };\n}\n\ninterface CrmListResponse {\n results: DealHistoryRecord[];\n paging?: { next?: { after?: string } };\n}\n\ninterface DealHistoryEntry {\n value?: string | null;\n timestamp?: string | null;\n sourceType?: string | null;\n}\n\ninterface DealHistoryRecord {\n id: string;\n propertiesWithHistory?: {\n dealstage?: DealHistoryEntry[];\n };\n}\n\ninterface CampaignListResponse {\n campaigns: Array<{ id: number | string }>;\n hasMore?: boolean;\n offset?: number | string;\n}\n\ninterface CampaignCounters {\n sent?: number;\n delivered?: number;\n open?: number;\n click?: number;\n bounce?: number;\n unsubscribed?: number;\n}\n\ninterface CampaignDetail {\n id: number | string;\n name?: string | null;\n subject?: string | null;\n fromName?: string | null;\n type?: string | null;\n lastProcessingFinishedAt?: number | string | null;\n numIncluded?: number | null;\n counters?: CampaignCounters;\n}\n\nconst hubspotCredentials = {\n accessToken: {\n description: 'HubSpot private app access token',\n auth: 'required' as const,\n },\n} satisfies CredentialsSchema;\n\ntype HubSpotCredentials = typeof hubspotCredentials;\n\nconst PHASE_ORDER = [\n 'contacts',\n 'companies',\n 'deals',\n 'deal_events',\n 'email_campaigns',\n 'email_stats',\n] as const;\n\ntype HubSpotPhase = (typeof PHASE_ORDER)[number];\n\nexport type HubSpotResource = HubSpotPhase;\n\nconst isHubSpotSyncCursor = makeChunkedCursorGuard(PHASE_ORDER);\n\nconst BASE_URL = 'https://api.hubapi.com';\nconst SEARCH_LIMIT = 100;\nconst LIST_LIMIT = 100;\n\ntype CrmObjectPhase = 'contacts' | 'companies' | 'deals';\n\nconst SEARCH_PROPERTIES: Record<CrmObjectPhase, readonly string[]> = {\n contacts: [\n 'email',\n 'lifecyclestage',\n 'hs_lead_status',\n 'createdate',\n 'lastmodifieddate',\n 'hubspot_owner_id',\n ],\n companies: [\n 'name',\n 'domain',\n 'industry',\n 'createdate',\n 'lifecyclestage',\n 'hs_lastmodifieddate',\n ],\n deals: [\n 'dealname',\n 'dealstage',\n 'pipeline',\n 'amount',\n 'closedate',\n 'hubspot_owner_id',\n 'createdate',\n 'hs_lastmodifieddate',\n ],\n};\n\nconst MODIFIED_PROPERTY: Record<CrmObjectPhase, string> = {\n contacts: 'lastmodifieddate',\n companies: 'hs_lastmodifieddate',\n deals: 'hs_lastmodifieddate',\n};\n\nconst ENTITY_TYPE_BY_PHASE: Partial<Record<HubSpotPhase, string>> = {\n contacts: 'hubspot_contact',\n companies: 'hubspot_company',\n deals: 'hubspot_deal',\n email_campaigns: 'hubspot_email_campaign',\n};\n\nconst DEAL_STAGE_EVENT = 'hubspot_deal_stage_change';\nconst EMAIL_STATS_METRIC = 'hubspot_email_stats';\n\nfunction finiteNumberOrNull(value: string | null | undefined): number | null {\n if (value === null || value === undefined || value.trim() === '') {\n return null;\n }\n const n = Number(value);\n return Number.isFinite(n) ? n : null;\n}\n\nfunction counterValue(value: number | undefined): number {\n return typeof value === 'number' && Number.isFinite(value) ? value : 0;\n}\n\nconst idString = z.string().min(1);\n\nconst contactProperties = z.object({\n email: z.string().nullish(),\n lifecyclestage: z.string().nullish(),\n hs_lead_status: z.string().nullish(),\n createdate: z.string().nullish(),\n lastmodifieddate: z.string().nullish(),\n hubspot_owner_id: z.string().nullish(),\n});\n\nconst companyProperties = z.object({\n name: z.string().nullish(),\n domain: z.string().nullish(),\n industry: z.string().nullish(),\n createdate: z.string().nullish(),\n lifecyclestage: z.string().nullish(),\n hs_lastmodifieddate: z.string().nullish(),\n});\n\nconst dealProperties = z.object({\n dealname: z.string().nullish(),\n dealstage: z.string().nullish(),\n pipeline: z.string().nullish(),\n amount: z.string().nullish(),\n closedate: z.string().nullish(),\n hubspot_owner_id: z.string().nullish(),\n createdate: z.string().nullish(),\n hs_lastmodifieddate: z.string().nullish(),\n});\n\nfunction crmRecordSchema(props: z.ZodType): z.ZodType {\n return z.object({\n id: idString,\n properties: props,\n createdAt: z.string(),\n updatedAt: z.string(),\n archived: z.boolean().optional(),\n });\n}\n\nconst dealHistoryRecordSchema = z.object({\n id: idString,\n propertiesWithHistory: z\n .object({\n dealstage: z\n .array(\n z.object({\n value: z.string().nullish(),\n timestamp: z.string().nullish(),\n sourceType: z.string().nullish(),\n }),\n )\n .optional(),\n })\n .optional(),\n});\n\nconst campaignDetailSchema = z.object({\n id: z.union([z.string(), z.number()]),\n name: z.string().nullish(),\n subject: z.string().nullish(),\n fromName: z.string().nullish(),\n type: z.string().nullish(),\n lastProcessingFinishedAt: z.union([z.string(), z.number()]).nullish(),\n numIncluded: z.number().nullish(),\n counters: z\n .object({\n sent: z.number().optional(),\n delivered: z.number().optional(),\n open: z.number().optional(),\n click: z.number().optional(),\n bounce: z.number().optional(),\n unsubscribed: z.number().optional(),\n })\n .optional(),\n});\n\nconst contactsSchema = z.array(crmRecordSchema(contactProperties));\nconst companiesSchema = z.array(crmRecordSchema(companyProperties));\nconst dealsSchema = z.array(crmRecordSchema(dealProperties));\nconst dealEventsSchema = z.array(dealHistoryRecordSchema);\nconst campaignsSchema = z.array(campaignDetailSchema);\n\nexport const hubspotResources = defineResources({\n hubspot_contact: {\n shape: 'entity',\n description:\n 'CRM contacts with email, lifecycle stage, lead status, owner, and creation time.',\n endpoint: 'POST /crm/v3/objects/contacts/search',\n responses: { contacts: contactsSchema },\n },\n hubspot_company: {\n shape: 'entity',\n description:\n 'CRM companies with name, domain, industry, lifecycle stage, and creation time.',\n endpoint: 'POST /crm/v3/objects/companies/search',\n responses: { companies: companiesSchema },\n },\n hubspot_deal: {\n shape: 'entity',\n description:\n 'CRM deals with name, stage, pipeline, amount, close date, owner, and creation time.',\n endpoint: 'POST /crm/v3/objects/deals/search',\n responses: { deals: dealsSchema },\n },\n hubspot_deal_stage_change: {\n shape: 'event',\n description:\n 'Deal stage-change events derived from deal property history, one event per stage transition.',\n endpoint: 'GET /crm/v3/objects/deals?propertiesWithHistory=dealstage',\n responses: { deal_events: dealEventsSchema },\n },\n hubspot_email_campaign: {\n shape: 'entity',\n description:\n 'Marketing email campaigns with name, subject, sender, type, send date, and recipient count.',\n endpoint: 'GET /email/public/v1/campaigns',\n responses: { email_campaigns: campaignsSchema },\n },\n hubspot_email_stats: {\n shape: 'metric',\n description:\n 'Per-campaign marketing email engagement stats (sent, delivered, opened, clicked, bounced, unsubscribed) timestamped at the campaign send time.',\n endpoint: 'GET /email/public/v1/campaigns/{id}',\n unit: 'emails',\n notes:\n 'One sample per campaign; value is the sent count, and every counter (delivered, opened, clicked, bounced, unsubscribed) is also exposed in attributes.',\n dimensions: [\n { name: 'campaignId', description: 'HubSpot campaign id.' },\n { name: 'campaignName', description: 'Campaign name.' },\n { name: 'delivered', description: 'Number of emails delivered.' },\n { name: 'opened', description: 'Number of emails opened.' },\n { name: 'clicked', description: 'Number of emails clicked.' },\n { name: 'bounced', description: 'Number of emails bounced.' },\n {\n name: 'unsubscribed',\n description: 'Number of recipients who unsubscribed.',\n },\n ],\n responses: { email_stats: campaignsSchema },\n },\n});\n\nexport const id = 'hubspot';\n\nexport class HubSpotConnector extends BaseConnector<\n HubSpotSettings,\n HubSpotCredentials\n> {\n static readonly id = id;\n\n static readonly resources = hubspotResources;\n\n static readonly schemas = schemasFromResources(hubspotResources);\n\n static create(input: unknown, ctx?: ConnectorContext): HubSpotConnector {\n const parsed = configFields.parse(input);\n return new HubSpotConnector(\n { resources: parsed.resources },\n { accessToken: parsed.accessToken },\n ctx,\n );\n }\n\n readonly id = id;\n override readonly credentials = hubspotCredentials;\n\n private buildHeaders(): Record<string, string> {\n return {\n Authorization: `Bearer ${this.creds.accessToken}`,\n 'Content-Type': 'application/json',\n 'User-Agent': connectorUserAgent('hubspot'),\n };\n }\n\n private apiGet<T>(\n url: string,\n resource: string,\n signal?: AbortSignal,\n ): Promise<HttpResponse<T>> {\n return this.get<T>(url, {\n resource,\n headers: this.buildHeaders(),\n signal,\n });\n }\n\n private apiPost<T>(\n url: string,\n resource: string,\n body: unknown,\n signal?: AbortSignal,\n ): Promise<HttpResponse<T>> {\n return this.post<T>(url, {\n resource,\n headers: this.buildHeaders(),\n body: JSON.stringify(body),\n signal,\n });\n }\n\n private buildSearchBody(\n phase: CrmObjectPhase,\n after: string | null,\n options: SyncOptions,\n ): Record<string, unknown> {\n const modifiedProperty = MODIFIED_PROPERTY[phase];\n const filterGroups: unknown[] = [];\n if (options.since) {\n const sinceMs = new Date(options.since).getTime();\n if (Number.isFinite(sinceMs)) {\n filterGroups.push({\n filters: [\n {\n propertyName: modifiedProperty,\n operator: 'GTE',\n value: String(sinceMs),\n },\n ],\n });\n }\n }\n return {\n filterGroups,\n sorts: [{ propertyName: modifiedProperty, direction: 'ASCENDING' }],\n properties: SEARCH_PROPERTIES[phase],\n limit: SEARCH_LIMIT,\n ...(after ? { after } : {}),\n };\n }\n\n private async fetchSearchPage(\n phase: CrmObjectPhase,\n after: string | null,\n options: SyncOptions,\n signal?: AbortSignal,\n ): Promise<{ items: unknown[]; next: string | null }> {\n const body = this.buildSearchBody(phase, after, options);\n const res = await this.apiPost<CrmSearchResponse>(\n `${BASE_URL}/crm/v3/objects/${phase}/search`,\n phase,\n body,\n signal,\n );\n return {\n items: res.body.results,\n next: res.body.paging?.next?.after ?? null,\n };\n }\n\n private async writeSearchPhase(\n storage: StorageHandle,\n phase: CrmObjectPhase,\n items: CrmRecord[],\n ): Promise<void> {\n for (const record of items) {\n const props = record.properties;\n const modifiedProperty = MODIFIED_PROPERTY[phase];\n const updatedAt =\n parseEpoch(record.updatedAt, 'iso') ??\n parseEpoch(props[modifiedProperty], 'ms') ??\n 0;\n\n let attributes: Record<string, string | number | null>;\n if (phase === 'contacts') {\n attributes = {\n email: props.email ?? null,\n lifecycleStage: props.lifecyclestage ?? null,\n leadStatus: props.hs_lead_status ?? null,\n ownerId: props.hubspot_owner_id ?? null,\n createdAt: parseEpoch(props.createdate, 'ms'),\n };\n } else if (phase === 'companies') {\n attributes = {\n name: props.name ?? null,\n domain: props.domain ?? null,\n industry: props.industry ?? null,\n lifecycleStage: props.lifecyclestage ?? null,\n createdAt: parseEpoch(props.createdate, 'ms'),\n };\n } else {\n attributes = {\n dealName: props.dealname ?? null,\n dealStage: props.dealstage ?? null,\n pipeline: props.pipeline ?? null,\n amount: finiteNumberOrNull(props.amount),\n closeDate: parseEpoch(props.closedate, 'ms'),\n ownerId: props.hubspot_owner_id ?? null,\n createdAt: parseEpoch(props.createdate, 'ms'),\n };\n }\n\n await storage.entity({\n type: ENTITY_TYPE_BY_PHASE[phase]!,\n id: record.id,\n attributes,\n updated_at: updatedAt,\n });\n }\n }\n\n private async fetchDealHistoryPage(\n after: string | null,\n signal?: AbortSignal,\n ): Promise<{ items: unknown[]; next: string | null }> {\n const url = new URL(`${BASE_URL}/crm/v3/objects/deals`);\n url.searchParams.set('limit', String(LIST_LIMIT));\n url.searchParams.set('properties', 'dealstage');\n url.searchParams.set('propertiesWithHistory', 'dealstage');\n if (after) {\n url.searchParams.set('after', after);\n }\n const res = await this.apiGet<CrmListResponse>(\n url.toString(),\n 'deal_events',\n signal,\n );\n return {\n items: res.body.results,\n next: res.body.paging?.next?.after ?? null,\n };\n }\n\n private async writeDealEvents(\n storage: StorageHandle,\n items: DealHistoryRecord[],\n _options: SyncOptions,\n ): Promise<void> {\n for (const record of items) {\n const history = record.propertiesWithHistory?.dealstage ?? [];\n for (const entry of history) {\n const ts = parseEpoch(entry.timestamp, 'iso');\n if (ts === null) {\n continue;\n }\n await storage.event({\n name: DEAL_STAGE_EVENT,\n start_ts: ts,\n end_ts: null,\n attributes: {\n dealId: record.id,\n stage: entry.value ?? null,\n sourceType: entry.sourceType ?? null,\n },\n });\n }\n }\n }\n\n private async fetchCampaignDetail(\n id: number | string,\n resource: string,\n signal?: AbortSignal,\n ): Promise<CampaignDetail> {\n const res = await this.apiGet<CampaignDetail>(\n `${BASE_URL}/email/public/v1/campaigns/${id}`,\n resource,\n signal,\n );\n return res.body;\n }\n\n private async fetchCampaignsPage(\n phase: 'email_campaigns' | 'email_stats',\n after: string | null,\n signal?: AbortSignal,\n ): Promise<{ items: unknown[]; next: string | null }> {\n const url = new URL(`${BASE_URL}/email/public/v1/campaigns`);\n url.searchParams.set('limit', String(LIST_LIMIT));\n if (after) {\n url.searchParams.set('offset', after);\n }\n const listRes = await this.apiGet<CampaignListResponse>(\n url.toString(),\n `${phase}_list`,\n signal,\n );\n const { campaigns, hasMore, offset } = listRes.body;\n\n const details: CampaignDetail[] = [];\n for (const campaign of campaigns) {\n details.push(await this.fetchCampaignDetail(campaign.id, phase, signal));\n }\n\n const next =\n hasMore && offset !== undefined && offset !== null\n ? String(offset)\n : null;\n return { items: details, next };\n }\n\n private async writeEmailCampaigns(\n storage: StorageHandle,\n items: CampaignDetail[],\n ): Promise<void> {\n for (const detail of items) {\n const sentDate = parseEpoch(detail.lastProcessingFinishedAt, 'ms');\n await storage.entity({\n type: ENTITY_TYPE_BY_PHASE.email_campaigns!,\n id: String(detail.id),\n attributes: {\n name: detail.name ?? null,\n subject: detail.subject ?? null,\n fromName: detail.fromName ?? null,\n type: detail.type ?? null,\n sentDate,\n numIncluded: detail.numIncluded ?? null,\n },\n updated_at: sentDate ?? 0,\n });\n }\n }\n\n private async writeEmailStats(\n storage: StorageHandle,\n items: CampaignDetail[],\n ): Promise<void> {\n for (const detail of items) {\n const ts = parseEpoch(detail.lastProcessingFinishedAt, 'ms');\n if (ts === null) {\n continue;\n }\n const counters = detail.counters ?? {};\n const sent = counterValue(counters.sent);\n await storage.metric({\n name: EMAIL_STATS_METRIC,\n ts,\n value: sent,\n attributes: {\n campaignId: String(detail.id),\n campaignName: detail.name ?? null,\n sent,\n delivered: counterValue(counters.delivered),\n opened: counterValue(counters.open),\n clicked: counterValue(counters.click),\n bounced: counterValue(counters.bounce),\n unsubscribed: counterValue(counters.unsubscribed),\n },\n });\n }\n }\n\n private async clearScopeOnFirstPage(\n storage: StorageHandle,\n phase: HubSpotPhase,\n isFull: boolean,\n ): Promise<void> {\n if (phase === 'deal_events') {\n await storage.events([], { names: [DEAL_STAGE_EVENT] });\n return;\n }\n if (phase === 'email_stats') {\n await storage.metrics([], { names: [EMAIL_STATS_METRIC] });\n return;\n }\n if (!isFull) {\n return;\n }\n const entityType = ENTITY_TYPE_BY_PHASE[phase];\n if (entityType) {\n await storage.entities([], { types: [entityType] });\n }\n }\n\n private async writePhase(\n storage: StorageHandle,\n phase: HubSpotPhase,\n items: unknown[],\n options: SyncOptions,\n ): Promise<void> {\n switch (phase) {\n case 'contacts':\n case 'companies':\n case 'deals':\n await this.writeSearchPhase(storage, phase, items as CrmRecord[]);\n return;\n case 'deal_events':\n await this.writeDealEvents(\n storage,\n items as DealHistoryRecord[],\n options,\n );\n return;\n case 'email_campaigns':\n await this.writeEmailCampaigns(storage, items as CampaignDetail[]);\n return;\n case 'email_stats':\n await this.writeEmailStats(storage, items as CampaignDetail[]);\n return;\n }\n }\n\n async sync(\n options: SyncOptions,\n storage: StorageHandle,\n signal?: AbortSignal,\n ): Promise<SyncResult> {\n const cursor = isHubSpotSyncCursor(options.cursor)\n ? options.cursor\n : undefined;\n const isFull = options.mode === 'full';\n\n const phases = selectActivePhases<HubSpotResource, HubSpotPhase>(\n (r) => r,\n PHASE_ORDER,\n this.settings.resources,\n );\n\n return paginateChunked<HubSpotPhase, string>({\n phases,\n cursor,\n signal,\n logger: this.logger,\n fetchPage: async (phase, page, sig) => {\n if (\n phase === 'contacts' ||\n phase === 'companies' ||\n phase === 'deals'\n ) {\n return this.fetchSearchPage(phase, page, options, sig);\n }\n if (phase === 'deal_events') {\n return this.fetchDealHistoryPage(page, sig);\n }\n return this.fetchCampaignsPage(phase, page, sig);\n },\n writeBatch: async (phase, items, page) => {\n if (page === null) {\n await this.clearScopeOnFirstPage(storage, phase, isFull);\n }\n await this.writePhase(storage, phase, items, options);\n },\n });\n }\n}\n","import { HubSpotConnector } from './hubspot';\n\nexport {\n configFields,\n doc,\n HubSpotConnector,\n hubspotResources as resources,\n id,\n} from './hubspot';\nexport type { HubSpotSettings, HubSpotResource } from './hubspot';\nexport default HubSpotConnector;\n"],"mappings":"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","names":["id"]}
|
|
1
|
+
{"version":3,"sources":["../../../connector-shared/src/errors.ts","../../../connector-shared/src/retry.ts","../../../connector-shared/src/version.ts","../../../connector-shared/src/request.ts","../../../connector-shared/src/rate-limit.ts","../../../connector-shared/src/map-concurrent.ts","../../../connector-shared/src/sanitize.ts","../../../connector-shared/src/epoch.ts","../../../connector-shared/src/pagination.ts","../../../connector-shared/src/logger.ts","../src/hubspot.ts","../src/index.ts"],"sourcesContent":["import type { HttpResponse } from './types';\n\nexport type HttpErrorKind =\n | 'transient'\n | 'rate_limit'\n | 'auth'\n | 'upstream_bug'\n | 'client_bug';\n\nexport abstract class HttpClientError extends Error {\n abstract readonly kind: HttpErrorKind;\n readonly response?: HttpResponse;\n\n constructor(message: string, response?: HttpResponse) {\n super(message);\n this.name = new.target.name;\n this.response = response;\n }\n}\n\nexport class TransientError extends HttpClientError {\n readonly kind = 'transient' as const;\n}\n\nexport class RateLimitError extends HttpClientError {\n readonly kind = 'rate_limit' as const;\n readonly retryAfter?: Date;\n\n constructor(message: string, response?: HttpResponse, retryAfter?: Date) {\n super(message, response);\n this.retryAfter = retryAfter;\n }\n}\n\nexport class AuthError extends HttpClientError {\n readonly kind = 'auth' as const;\n}\n\nexport class UpstreamBugError extends HttpClientError {\n readonly kind = 'upstream_bug' as const;\n}\n\nexport class ClientBugError extends HttpClientError {\n readonly kind = 'client_bug' as const;\n}\n\nexport function classifyStatus(status: number): HttpErrorKind {\n if (status === 429) {\n return 'rate_limit';\n }\n if (status === 401 || status === 403) {\n return 'auth';\n }\n if (status === 408) {\n return 'transient';\n }\n if (status >= 500) {\n return 'upstream_bug';\n }\n if (status >= 400) {\n return 'client_bug';\n }\n return 'client_bug';\n}\n\nexport function errorForStatus(\n message: string,\n response: HttpResponse,\n retryAfter?: Date,\n): HttpClientError {\n const kind = classifyStatus(response.status);\n switch (kind) {\n case 'rate_limit':\n return new RateLimitError(message, response, retryAfter);\n case 'auth':\n return new AuthError(message, response);\n case 'transient':\n return new TransientError(message, response);\n case 'upstream_bug':\n return new UpstreamBugError(message, response);\n case 'client_bug':\n return new ClientBugError(message, response);\n }\n}\n","import { HttpClientError, RateLimitError, TransientError } from './errors';\n\nexport interface RetryPolicy {\n maxAttempts?: number;\n initialDelayMs?: number;\n maxDelayMs?: number;\n retryOn?: (status: number | null, err?: Error) => boolean;\n}\n\nexport const defaultRetryOn = (status: number | null, err?: Error): boolean => {\n if (err instanceof RateLimitError) {\n return true;\n }\n if (err instanceof TransientError) {\n return true;\n }\n if (status === null) {\n return err instanceof Error && !(err instanceof HttpClientError);\n }\n if (status === 408 || status === 429) {\n return true;\n }\n if (status >= 500) {\n return true;\n }\n return false;\n};\n\nexport function backoffDelayMs(\n attempt: number,\n policy: Required<Pick<RetryPolicy, 'initialDelayMs' | 'maxDelayMs'>>,\n): number {\n const base = policy.initialDelayMs * 2 ** attempt;\n const jitter = base * 0.25 * Math.random();\n return Math.min(base + jitter, policy.maxDelayMs);\n}\n\nexport function parseRetryAfter(\n headerValue: string | null,\n now: Date = new Date(),\n): Date | undefined {\n if (!headerValue) {\n return undefined;\n }\n const trimmed = headerValue.trim();\n if (/^\\d+$/.test(trimmed)) {\n return new Date(now.getTime() + Number(trimmed) * 1000);\n }\n const parsed = Date.parse(trimmed);\n if (Number.isNaN(parsed)) {\n return undefined;\n }\n return new Date(parsed);\n}\n\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n if (signal?.aborted) {\n return Promise.reject(signal.reason ?? new Error('Aborted'));\n }\n return new Promise<void>((resolve, reject) => {\n const onAbort = () => {\n clearTimeout(timer);\n reject(signal!.reason ?? new Error('Aborted'));\n };\n const timer = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n}\n","export const HTTP_CLIENT_VERSION = '0.0.0';\n\nexport const DEFAULT_USER_AGENT = `rawdash-connector/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;\n\nexport function connectorUserAgent(connectorId: string): string {\n return `rawdash-connector-${connectorId}/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;\n}\n","import {\n AuthError,\n ClientBugError,\n HttpClientError,\n RateLimitError,\n TransientError,\n UpstreamBugError,\n errorForStatus,\n} from './errors';\nimport { defaultRetryOn, parseRetryAfter, sleep } from './retry';\nimport type { FetchLike, HttpMethod, HttpRequest, HttpResponse } from './types';\nimport { DEFAULT_USER_AGENT } from './version';\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\nconst DEFAULT_MAX_ATTEMPTS = 3;\nconst DEFAULT_INITIAL_DELAY_MS = 1000;\nconst DEFAULT_MAX_DELAY_MS = 60_000;\nconst OBSERVER_TIMEOUT_MS = 250;\n\nexport interface RequestObservation {\n url: string;\n method: HttpMethod;\n status: number;\n resource: string;\n requestId: string;\n body: unknown;\n}\n\nexport type RequestObserver = (\n event: RequestObservation,\n) => void | Promise<void>;\n\nexport interface RequestOptions {\n fetch?: FetchLike;\n observer?: RequestObserver;\n resource: string;\n requestId?: string;\n}\n\nasync function notifyObserver(\n observer: RequestObserver,\n event: RequestObservation,\n): Promise<void> {\n let result: void | Promise<void>;\n try {\n result = observer(event);\n } catch (err) {\n console.warn('[connector-shared] request observer threw:', err);\n return;\n }\n if (!(result instanceof Promise)) {\n return;\n }\n const guarded = result.catch((err) => {\n console.warn('[connector-shared] request observer rejected:', err);\n });\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<void>((resolve) => {\n timer = setTimeout(resolve, OBSERVER_TIMEOUT_MS);\n });\n try {\n await Promise.race([guarded, timeout]);\n } finally {\n if (timer) {\n clearTimeout(timer);\n }\n }\n}\n\nfunction newRequestId(): string {\n const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;\n if (c?.randomUUID) {\n return c.randomUUID();\n }\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\nfunction mergeHeaders(\n defaults: Record<string, string>,\n overrides: Record<string, string> | undefined,\n): Record<string, string> {\n const merged: Record<string, string> = {};\n for (const [k, v] of Object.entries(defaults)) {\n merged[k.toLowerCase()] = v;\n }\n if (overrides) {\n for (const [k, v] of Object.entries(overrides)) {\n merged[k.toLowerCase()] = v;\n }\n }\n return merged;\n}\n\nfunction linkTimeoutSignal(\n parent: AbortSignal | undefined,\n timeoutMs: number,\n): { signal: AbortSignal; cancel: () => void } {\n const controller = new AbortController();\n const onParentAbort = () => {\n controller.abort(parent?.reason);\n };\n if (parent) {\n if (parent.aborted) {\n controller.abort(parent.reason);\n } else {\n parent.addEventListener('abort', onParentAbort, { once: true });\n }\n }\n const timer = setTimeout(() => {\n controller.abort(new Error(`Request timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n return {\n signal: controller.signal,\n cancel: () => {\n clearTimeout(timer);\n if (parent) {\n parent.removeEventListener('abort', onParentAbort);\n }\n },\n };\n}\n\nasync function readBody(res: Response, parseJson: boolean): Promise<unknown> {\n if (res.status === 204 || res.status === 205) {\n return null;\n }\n const contentType = res.headers.get('content-type') ?? '';\n if (parseJson && contentType.includes('application/json')) {\n const text = await res.text();\n if (text.length === 0) {\n return null;\n }\n return JSON.parse(text);\n }\n return res.text();\n}\n\nexport async function request<T = unknown>(\n req: HttpRequest,\n options: RequestOptions,\n): Promise<HttpResponse<T>> {\n const fetchImpl: FetchLike = options.fetch ?? (globalThis.fetch as FetchLike);\n const retry = req.retry ?? {};\n const maxAttempts = retry.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;\n const initialDelayMs = retry.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS;\n const maxDelayMs = retry.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;\n const retryOn = retry.retryOn ?? defaultRetryOn;\n const timeoutMs = req.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const parseJson = req.parseJson ?? true;\n\n const headers = mergeHeaders(\n {\n 'User-Agent': DEFAULT_USER_AGENT,\n Accept: 'application/json',\n },\n req.headers,\n );\n\n let lastErr: Error | undefined;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n req.signal?.throwIfAborted();\n\n const { signal, cancel } = linkTimeoutSignal(req.signal, timeoutMs);\n let res: Response;\n try {\n res = await fetchImpl(req.url, {\n method: req.method ?? 'GET',\n headers,\n body: req.body as RequestInit['body'],\n signal,\n });\n } catch (err) {\n cancel();\n if (req.signal?.aborted) {\n throw req.signal.reason ?? err;\n }\n const error = err instanceof Error ? err : new Error(String(err));\n lastErr = error;\n if (attempt < maxAttempts - 1 && retryOn(null, error)) {\n const delay = computeDelay(attempt, initialDelayMs, maxDelayMs);\n await sleep(delay, req.signal);\n continue;\n }\n throw new TransientError(error.message);\n }\n cancel();\n\n const body = await readBody(res, parseJson);\n const httpResponse: HttpResponse<T> = {\n status: res.status,\n headers: res.headers,\n body: body as T,\n };\n if (req.rateLimit) {\n const state = req.rateLimit.parse(res.headers);\n if (state) {\n httpResponse.rateLimitState = state;\n }\n }\n\n if (options.observer) {\n await notifyObserver(options.observer, {\n url: req.url,\n method: req.method ?? 'GET',\n status: res.status,\n resource: options.resource,\n requestId: options.requestId ?? newRequestId(),\n body,\n });\n }\n\n if (res.ok) {\n return httpResponse;\n }\n\n const retryAfter = parseRetryAfter(res.headers.get('retry-after'));\n const message = `HTTP ${res.status} ${res.statusText} for ${req.method ?? 'GET'} ${req.url}`;\n const err = errorForStatus(message, httpResponse, retryAfter);\n\n if (\n attempt < maxAttempts - 1 &&\n retryOn(res.status, err) &&\n !(err instanceof AuthError) &&\n !(err instanceof ClientBugError)\n ) {\n lastErr = err;\n let delay = computeDelay(attempt, initialDelayMs, maxDelayMs);\n if (err instanceof RateLimitError && retryAfter) {\n const wait = retryAfter.getTime() - Date.now();\n if (wait > 0) {\n delay = Math.min(wait, maxDelayMs);\n }\n }\n await sleep(delay, req.signal);\n continue;\n }\n\n throw err;\n }\n\n throw lastErr ?? new UpstreamBugError('Exhausted retry attempts');\n}\n\nfunction computeDelay(\n attempt: number,\n initialDelayMs: number,\n maxDelayMs: number,\n): number {\n const base = initialDelayMs * 2 ** attempt;\n const jitter = base * 0.25 * Math.random();\n return Math.min(base + jitter, maxDelayMs);\n}\n\nexport { HttpClientError };\n","export interface RateLimitState {\n remaining: number;\n resetAt: Date;\n}\n\nexport interface RateLimitPolicy {\n parse(headers: Headers): RateLimitState | null;\n}\n\nexport interface StandardRateLimitPolicyConfig {\n remainingHeader: string;\n resetHeader: string;\n resetUnit: 's' | 'ms';\n resetFallbackMs?: number;\n}\n\nexport function standardRateLimitPolicy(\n config: StandardRateLimitPolicyConfig,\n): RateLimitPolicy {\n const { remainingHeader, resetHeader, resetUnit, resetFallbackMs } = config;\n const multiplier = resetUnit === 's' ? 1000 : 1;\n return {\n parse(h) {\n const remainingRaw = h.get(remainingHeader);\n if (remainingRaw === null || remainingRaw.trim() === '') {\n return null;\n }\n const remaining = Number(remainingRaw);\n if (!Number.isFinite(remaining)) {\n return null;\n }\n const resetRaw = h.get(resetHeader);\n if (resetRaw === null) {\n if (resetFallbackMs === undefined) {\n return null;\n }\n return {\n remaining,\n resetAt: new Date(Date.now() + resetFallbackMs),\n };\n }\n if (resetRaw.trim() === '') {\n return null;\n }\n const reset = Number(resetRaw);\n if (!Number.isFinite(reset) || reset < 0) {\n return null;\n }\n const resetMs = reset * multiplier;\n if (!Number.isFinite(resetMs)) {\n return null;\n }\n return { remaining, resetAt: new Date(resetMs) };\n },\n };\n}\n","export async function mapWithConcurrency<T, R>(\n items: readonly T[],\n concurrency: number,\n fn: (item: T, index: number) => Promise<R>,\n): Promise<R[]> {\n const results = new Array<R>(items.length);\n if (items.length === 0) {\n return results;\n }\n const normalized = Number.isFinite(concurrency) ? Math.floor(concurrency) : 1;\n const limit = Math.max(1, Math.min(normalized, items.length));\n let next = 0;\n let failed = false;\n\n async function worker(): Promise<void> {\n while (!failed) {\n const i = next++;\n if (i >= items.length) {\n return;\n }\n try {\n results[i] = await fn(items[i]!, i);\n } catch (err) {\n failed = true;\n throw err;\n }\n }\n }\n\n const workers: Promise<void>[] = [];\n for (let w = 0; w < limit; w++) {\n workers.push(worker());\n }\n await Promise.all(workers);\n return results;\n}\n","export interface SanitizeAllowedUrlOptions {\n url: string | null;\n host: string;\n pathname: string;\n protocol?: 'https:' | 'http:';\n}\n\nexport function sanitizeAllowedUrl(\n options: SanitizeAllowedUrlOptions,\n): string | null {\n const { url, host, pathname, protocol = 'https:' } = options;\n if (url === null) {\n return null;\n }\n try {\n const u = new URL(url);\n if (u.protocol !== protocol || u.host !== host || u.pathname !== pathname) {\n return null;\n }\n return u.toString();\n } catch {\n return null;\n }\n}\n","export type EpochUnit = 'ms' | 's' | 'iso';\n\nexport function parseEpoch(\n value: number | string | null | undefined,\n unit: EpochUnit,\n): number | null {\n if (value === null || value === undefined) {\n return null;\n }\n if (unit === 'iso') {\n if (typeof value !== 'string') {\n return null;\n }\n const ms = new Date(value).getTime();\n return Number.isFinite(ms) ? ms : null;\n }\n if (typeof value === 'string' && value.trim() === '') {\n return null;\n }\n const n = typeof value === 'number' ? value : Number(value);\n if (!Number.isFinite(n)) {\n return null;\n }\n const result = unit === 's' ? n * 1000 : n;\n return Number.isFinite(result) ? result : null;\n}\n","import { request } from './request';\nimport type { HttpRequest } from './types';\n\nexport function parseLinkHeader(header: string | null): Record<string, string> {\n if (!header) {\n return {};\n }\n const result: Record<string, string> = {};\n for (const part of header.split(',')) {\n const match = part.match(/<([^>]+)>\\s*;\\s*rel=\"([^\"]+)\"/);\n if (match) {\n result[match[2]!] = match[1]!;\n }\n }\n return result;\n}\n\nexport async function* paginateLink<T>(\n initial: HttpRequest,\n parse: (body: unknown) => T[],\n options: { resource: string },\n): AsyncIterable<T> {\n let next: string | null = initial.url;\n while (next) {\n const res: Awaited<ReturnType<typeof request>> = await request(\n {\n ...initial,\n url: next,\n },\n { resource: options.resource },\n );\n for (const item of parse(res.body)) {\n yield item;\n }\n const links = parseLinkHeader(res.headers.get('link'));\n next = links['next'] ?? null;\n }\n}\n\nexport async function* paginateCursor<T>(\n initial: HttpRequest,\n parse: (body: unknown) => { items: T[]; nextCursor: string | null },\n buildNext: (req: HttpRequest, cursor: string) => HttpRequest,\n options: { resource: string },\n): AsyncIterable<T> {\n let req: HttpRequest = initial;\n while (true) {\n const res = await request(req, { resource: options.resource });\n const { items, nextCursor } = parse(res.body);\n for (const item of items) {\n yield item;\n }\n if (!nextCursor) {\n return;\n }\n req = buildNext(req, nextCursor);\n }\n}\n\nexport async function* paginatePage<T>(\n initial: HttpRequest,\n parse: (body: unknown) => { items: T[]; hasMore: boolean },\n buildPage: (req: HttpRequest, page: number) => HttpRequest,\n options: { resource: string },\n): AsyncIterable<T> {\n let page = 1;\n while (true) {\n const req = page === 1 ? initial : buildPage(initial, page);\n const res = await request(req, { resource: options.resource });\n const { items, hasMore } = parse(res.body);\n for (const item of items) {\n yield item;\n }\n if (!hasMore || items.length === 0) {\n return;\n }\n page++;\n }\n}\n","export type LogFields = Record<string, unknown>;\n\nexport interface ConnectorLogger {\n info(event: string, fields?: LogFields): void;\n warn(event: string, fields?: LogFields): void;\n}\n\nexport interface ConnectorLoggerOptions {\n scope: string;\n}\n\nconst MAX_VALUE_LEN = 120;\n\nfunction truncate(s: string, max = MAX_VALUE_LEN): string {\n if (s.length <= max) {\n return s;\n }\n return `${s.slice(0, max - 1)}…`;\n}\n\nfunction formatValue(value: unknown): string {\n if (value === null) {\n return 'null';\n }\n if (value === undefined) {\n return '';\n }\n if (typeof value === 'number' || typeof value === 'boolean') {\n return String(value);\n }\n if (typeof value === 'string') {\n const t = truncate(value);\n if (/[\\s\"=]/.test(t)) {\n return JSON.stringify(t);\n }\n return t;\n }\n if (typeof value === 'bigint') {\n return value.toString();\n }\n let json: string | undefined;\n try {\n json = JSON.stringify(value);\n } catch {\n json = undefined;\n }\n return truncate(json ?? String(value));\n}\n\nexport function formatLogFields(fields?: LogFields): string {\n if (!fields) {\n return '';\n }\n const parts: string[] = [];\n for (const [k, v] of Object.entries(fields)) {\n if (v === undefined) {\n continue;\n }\n parts.push(`${k}=${formatValue(v)}`);\n }\n return parts.length > 0 ? ` ${parts.join(' ')}` : '';\n}\n\nexport function formatLogLine(\n scope: string,\n event: string,\n fields?: LogFields,\n): string {\n return `[${scope}] ${event}${formatLogFields(fields)}`;\n}\n\nexport function createDefaultConnectorLogger(\n opts: ConnectorLoggerOptions,\n): ConnectorLogger {\n return {\n info(event, fields) {\n console.info(formatLogLine(opts.scope, event, fields));\n },\n warn(event, fields) {\n console.warn(formatLogLine(opts.scope, event, fields));\n },\n };\n}\n\nconst NOOP_LOGGER: ConnectorLogger = {\n info() {},\n warn() {},\n};\n\nexport function noopConnectorLogger(): ConnectorLogger {\n return NOOP_LOGGER;\n}\n","import {\n type HttpResponse,\n connectorUserAgent,\n parseEpoch,\n} from '@rawdash/connector-shared';\nimport {\n BaseConnector,\n type ConnectorContext,\n type ConnectorDoc,\n type CredentialsSchema,\n type FetchSpec,\n type FilterClause,\n type StorageHandle,\n type SyncOptions,\n type SyncResult,\n defineConfigFields,\n defineConnectorDoc,\n defineResources,\n makeChunkedCursorGuard,\n paginateChunked,\n schemasFromResources,\n selectActivePhases,\n} from '@rawdash/core';\nimport { z } from 'zod';\n\nexport const configFields = defineConfigFields(\n z.object({\n accessToken: z.object({ $secret: z.string() }).meta({\n label: 'Private App access token',\n description:\n 'HubSpot private app access token with read scopes for contacts, companies, deals, and marketing email. Create one at Settings → Integrations → Private Apps.',\n placeholder: 'pat-na1-...',\n secret: true,\n }),\n resources: z\n .array(\n z.enum([\n 'contacts',\n 'companies',\n 'deals',\n 'deal_events',\n 'email_campaigns',\n 'email_stats',\n ]),\n )\n .nonempty()\n .optional()\n .meta({\n label: 'Resources',\n description:\n 'Which HubSpot resources to sync. Omit to sync all resources. The access token only needs read scopes for the resources listed here.',\n }),\n }),\n);\n\nexport const doc: ConnectorDoc = defineConnectorDoc({\n displayName: 'HubSpot',\n category: 'sales',\n brandColor: '#FF7A59',\n tagline:\n 'Sync CRM contacts, companies, and deals plus deal stage-change events and marketing email campaign stats from HubSpot.',\n vendor: {\n name: 'HubSpot',\n domain: 'hubspot.com',\n apiDocs: 'https://developers.hubspot.com/docs/api/overview',\n website: 'https://www.hubspot.com',\n },\n auth: {\n summary:\n 'A HubSpot private app access token with read scopes for the resources you sync (contacts, companies, deals, and marketing email).',\n setup: [\n 'In HubSpot, go to Settings → Integrations → Private Apps and create a private app.',\n 'Grant read scopes for the resources you intend to sync (CRM contacts, companies, deals, and marketing email).',\n 'Copy the generated access token (starts with `pat-`).',\n 'Store it as a secret and reference it from the connector config as `accessToken: secret(\"HUBSPOT_ACCESS_TOKEN\")`.',\n ],\n },\n rateLimit:\n 'HubSpot allows 100 requests / 10s; the Search API caps results at 10,000 per query.',\n limitations: [\n 'Deal stage-change events are rewritten on every sync because the deal list endpoint has no incremental `since` filter.',\n 'Marketing email campaign data comes from the legacy email campaigns API and is only available for marketing emails.',\n 'Very large CRM portfolios may not backfill in full because the Search API caps at 10,000 results per query.',\n ],\n});\n\nexport interface HubSpotSettings {\n resources?: readonly HubSpotResource[];\n}\n\ntype HubSpotProperties = Record<string, string | null | undefined>;\n\ninterface CrmRecord {\n id: string;\n properties: HubSpotProperties;\n createdAt: string;\n updatedAt: string;\n archived?: boolean;\n}\n\ninterface CrmSearchResponse {\n total?: number;\n results: CrmRecord[];\n paging?: { next?: { after?: string } };\n}\n\ninterface CrmListResponse {\n results: DealHistoryRecord[];\n paging?: { next?: { after?: string } };\n}\n\ninterface DealHistoryEntry {\n value?: string | null;\n timestamp?: string | null;\n sourceType?: string | null;\n}\n\ninterface DealHistoryRecord {\n id: string;\n propertiesWithHistory?: {\n dealstage?: DealHistoryEntry[];\n };\n}\n\ninterface CampaignListResponse {\n campaigns: Array<{ id: number | string }>;\n hasMore?: boolean;\n offset?: number | string;\n}\n\ninterface CampaignCounters {\n sent?: number;\n delivered?: number;\n open?: number;\n click?: number;\n bounce?: number;\n unsubscribed?: number;\n}\n\ninterface CampaignDetail {\n id: number | string;\n name?: string | null;\n subject?: string | null;\n fromName?: string | null;\n type?: string | null;\n lastProcessingFinishedAt?: number | string | null;\n numIncluded?: number | null;\n counters?: CampaignCounters;\n}\n\nconst hubspotCredentials = {\n accessToken: {\n description: 'HubSpot private app access token',\n auth: 'required' as const,\n },\n} satisfies CredentialsSchema;\n\ntype HubSpotCredentials = typeof hubspotCredentials;\n\nconst PHASE_ORDER = [\n 'contacts',\n 'companies',\n 'deals',\n 'deal_events',\n 'email_campaigns',\n 'email_stats',\n] as const;\n\ntype HubSpotPhase = (typeof PHASE_ORDER)[number];\n\nexport type HubSpotResource = HubSpotPhase;\n\nconst isHubSpotSyncCursor = makeChunkedCursorGuard(PHASE_ORDER);\n\nconst BASE_URL = 'https://api.hubapi.com';\nconst SEARCH_LIMIT = 100;\nconst LIST_LIMIT = 100;\n\ntype CrmObjectPhase = 'contacts' | 'companies' | 'deals';\n\nconst SEARCH_PROPERTIES: Record<CrmObjectPhase, readonly string[]> = {\n contacts: [\n 'email',\n 'lifecyclestage',\n 'hs_lead_status',\n 'createdate',\n 'lastmodifieddate',\n 'hubspot_owner_id',\n ],\n companies: [\n 'name',\n 'domain',\n 'industry',\n 'createdate',\n 'lifecyclestage',\n 'hs_lastmodifieddate',\n ],\n deals: [\n 'dealname',\n 'dealstage',\n 'pipeline',\n 'amount',\n 'closedate',\n 'hubspot_owner_id',\n 'createdate',\n 'hs_lastmodifieddate',\n ],\n};\n\nconst MODIFIED_PROPERTY: Record<CrmObjectPhase, string> = {\n contacts: 'lastmodifieddate',\n companies: 'hs_lastmodifieddate',\n deals: 'hs_lastmodifieddate',\n};\n\nconst ENTITY_TYPE_BY_PHASE: Partial<Record<HubSpotPhase, string>> = {\n contacts: 'hubspot_contact',\n companies: 'hubspot_company',\n deals: 'hubspot_deal',\n email_campaigns: 'hubspot_email_campaign',\n};\n\nconst SEARCH_PUSH_FIELDS: Partial<\n Record<CrmObjectPhase, Record<string, string>>\n> = {\n contacts: { lifecycleStage: 'lifecyclestage', leadStatus: 'hs_lead_status' },\n companies: { lifecycleStage: 'lifecyclestage', industry: 'industry' },\n deals: { dealStage: 'dealstage', pipeline: 'pipeline' },\n};\n\nconst DEAL_STAGE_EVENT = 'hubspot_deal_stage_change';\nconst EMAIL_STATS_METRIC = 'hubspot_email_stats';\n\nfunction finiteNumberOrNull(value: string | null | undefined): number | null {\n if (value === null || value === undefined || value.trim() === '') {\n return null;\n }\n const n = Number(value);\n return Number.isFinite(n) ? n : null;\n}\n\nfunction counterValue(value: number | undefined): number {\n return typeof value === 'number' && Number.isFinite(value) ? value : 0;\n}\n\nconst idString = z.string().min(1);\n\nconst contactProperties = z.object({\n email: z.string().nullish(),\n lifecyclestage: z.string().nullish(),\n hs_lead_status: z.string().nullish(),\n createdate: z.string().nullish(),\n lastmodifieddate: z.string().nullish(),\n hubspot_owner_id: z.string().nullish(),\n});\n\nconst companyProperties = z.object({\n name: z.string().nullish(),\n domain: z.string().nullish(),\n industry: z.string().nullish(),\n createdate: z.string().nullish(),\n lifecyclestage: z.string().nullish(),\n hs_lastmodifieddate: z.string().nullish(),\n});\n\nconst dealProperties = z.object({\n dealname: z.string().nullish(),\n dealstage: z.string().nullish(),\n pipeline: z.string().nullish(),\n amount: z.string().nullish(),\n closedate: z.string().nullish(),\n hubspot_owner_id: z.string().nullish(),\n createdate: z.string().nullish(),\n hs_lastmodifieddate: z.string().nullish(),\n});\n\nfunction crmRecordSchema(props: z.ZodType): z.ZodType {\n return z.object({\n id: idString,\n properties: props,\n createdAt: z.string(),\n updatedAt: z.string(),\n archived: z.boolean().optional(),\n });\n}\n\nconst dealHistoryRecordSchema = z.object({\n id: idString,\n propertiesWithHistory: z\n .object({\n dealstage: z\n .array(\n z.object({\n value: z.string().nullish(),\n timestamp: z.string().nullish(),\n sourceType: z.string().nullish(),\n }),\n )\n .optional(),\n })\n .optional(),\n});\n\nconst campaignDetailSchema = z.object({\n id: z.union([z.string(), z.number()]),\n name: z.string().nullish(),\n subject: z.string().nullish(),\n fromName: z.string().nullish(),\n type: z.string().nullish(),\n lastProcessingFinishedAt: z.union([z.string(), z.number()]).nullish(),\n numIncluded: z.number().nullish(),\n counters: z\n .object({\n sent: z.number().optional(),\n delivered: z.number().optional(),\n open: z.number().optional(),\n click: z.number().optional(),\n bounce: z.number().optional(),\n unsubscribed: z.number().optional(),\n })\n .optional(),\n});\n\nconst contactsSchema = z.array(crmRecordSchema(contactProperties));\nconst companiesSchema = z.array(crmRecordSchema(companyProperties));\nconst dealsSchema = z.array(crmRecordSchema(dealProperties));\nconst dealEventsSchema = z.array(dealHistoryRecordSchema);\nconst campaignsSchema = z.array(campaignDetailSchema);\n\nexport const hubspotResources = defineResources({\n hubspot_contact: {\n shape: 'entity',\n filterable: [\n { field: 'lifecycleStage', ops: ['eq'] },\n { field: 'leadStatus', ops: ['eq'] },\n ],\n description:\n 'CRM contacts with email, lifecycle stage, lead status, owner, and creation time.',\n endpoint: 'POST /crm/v3/objects/contacts/search',\n responses: { contacts: contactsSchema },\n },\n hubspot_company: {\n shape: 'entity',\n filterable: [\n { field: 'lifecycleStage', ops: ['eq'] },\n { field: 'industry', ops: ['eq'] },\n ],\n description:\n 'CRM companies with name, domain, industry, lifecycle stage, and creation time.',\n endpoint: 'POST /crm/v3/objects/companies/search',\n responses: { companies: companiesSchema },\n },\n hubspot_deal: {\n shape: 'entity',\n filterable: [\n { field: 'dealStage', ops: ['eq'] },\n { field: 'pipeline', ops: ['eq'] },\n ],\n description:\n 'CRM deals with name, stage, pipeline, amount, close date, owner, and creation time.',\n endpoint: 'POST /crm/v3/objects/deals/search',\n responses: { deals: dealsSchema },\n },\n hubspot_deal_stage_change: {\n shape: 'event',\n filterable: [],\n description:\n 'Deal stage-change events derived from deal property history, one event per stage transition.',\n endpoint: 'GET /crm/v3/objects/deals?propertiesWithHistory=dealstage',\n responses: { deal_events: dealEventsSchema },\n },\n hubspot_email_campaign: {\n shape: 'entity',\n filterable: [],\n description:\n 'Marketing email campaigns with name, subject, sender, type, send date, and recipient count.',\n endpoint: 'GET /email/public/v1/campaigns',\n responses: { email_campaigns: campaignsSchema },\n },\n hubspot_email_stats: {\n shape: 'metric',\n description:\n 'Per-campaign marketing email engagement stats (sent, delivered, opened, clicked, bounced, unsubscribed) timestamped at the campaign send time.',\n endpoint: 'GET /email/public/v1/campaigns/{id}',\n unit: 'emails',\n notes:\n 'One sample per campaign; value is the sent count, and every counter (delivered, opened, clicked, bounced, unsubscribed) is also exposed in attributes.',\n dimensions: [\n { name: 'campaignId', description: 'HubSpot campaign id.' },\n { name: 'campaignName', description: 'Campaign name.' },\n { name: 'delivered', description: 'Number of emails delivered.' },\n { name: 'opened', description: 'Number of emails opened.' },\n { name: 'clicked', description: 'Number of emails clicked.' },\n { name: 'bounced', description: 'Number of emails bounced.' },\n {\n name: 'unsubscribed',\n description: 'Number of recipients who unsubscribed.',\n },\n ],\n responses: { email_stats: campaignsSchema },\n },\n});\n\nexport const id = 'hubspot';\n\nfunction pushableEq(\n filter: FilterClause[] | undefined,\n field: string,\n): string | null {\n if (!filter) {\n return null;\n }\n for (const clause of filter) {\n if (\n 'field' in clause &&\n clause.field === field &&\n clause.op === 'eq' &&\n typeof clause.value === 'string'\n ) {\n return clause.value;\n }\n }\n return null;\n}\n\nexport class HubSpotConnector extends BaseConnector<\n HubSpotSettings,\n HubSpotCredentials\n> {\n static readonly id = id;\n\n static readonly resources = hubspotResources;\n\n static readonly schemas = schemasFromResources(hubspotResources);\n\n static create(input: unknown, ctx?: ConnectorContext): HubSpotConnector {\n const parsed = configFields.parse(input);\n return new HubSpotConnector(\n { resources: parsed.resources },\n { accessToken: parsed.accessToken },\n ctx,\n );\n }\n\n readonly id = id;\n override readonly credentials = hubspotCredentials;\n\n private buildHeaders(): Record<string, string> {\n return {\n Authorization: `Bearer ${this.creds.accessToken}`,\n 'Content-Type': 'application/json',\n 'User-Agent': connectorUserAgent('hubspot'),\n };\n }\n\n private apiGet<T>(\n url: string,\n resource: string,\n signal?: AbortSignal,\n ): Promise<HttpResponse<T>> {\n return this.get<T>(url, {\n resource,\n headers: this.buildHeaders(),\n signal,\n });\n }\n\n private apiPost<T>(\n url: string,\n resource: string,\n body: unknown,\n signal?: AbortSignal,\n ): Promise<HttpResponse<T>> {\n return this.post<T>(url, {\n resource,\n headers: this.buildHeaders(),\n body: JSON.stringify(body),\n signal,\n });\n }\n\n private singleSpec(\n options: SyncOptions,\n resource: string,\n ): FetchSpec | undefined {\n const specs = options.fetchSpecs?.[resource];\n return specs && specs.length === 1 ? specs[0] : undefined;\n }\n\n private buildSearchBody(\n phase: CrmObjectPhase,\n after: string | null,\n options: SyncOptions,\n ): Record<string, unknown> {\n const modifiedProperty = MODIFIED_PROPERTY[phase];\n const filters: unknown[] = [];\n if (options.since) {\n const sinceMs = new Date(options.since).getTime();\n if (Number.isFinite(sinceMs)) {\n filters.push({\n propertyName: modifiedProperty,\n operator: 'GTE',\n value: String(sinceMs),\n });\n }\n }\n const pushMap = SEARCH_PUSH_FIELDS[phase];\n if (pushMap) {\n const filter = this.singleSpec(\n options,\n ENTITY_TYPE_BY_PHASE[phase]!,\n )?.filter;\n for (const [ourField, propertyName] of Object.entries(pushMap)) {\n const value = pushableEq(filter, ourField);\n if (value !== null) {\n filters.push({ propertyName, operator: 'EQ', value });\n }\n }\n }\n const filterGroups = filters.length > 0 ? [{ filters }] : [];\n return {\n filterGroups,\n sorts: [{ propertyName: modifiedProperty, direction: 'ASCENDING' }],\n properties: SEARCH_PROPERTIES[phase],\n limit: SEARCH_LIMIT,\n ...(after ? { after } : {}),\n };\n }\n\n private async fetchSearchPage(\n phase: CrmObjectPhase,\n after: string | null,\n options: SyncOptions,\n signal?: AbortSignal,\n ): Promise<{ items: unknown[]; next: string | null }> {\n const body = this.buildSearchBody(phase, after, options);\n const res = await this.apiPost<CrmSearchResponse>(\n `${BASE_URL}/crm/v3/objects/${phase}/search`,\n phase,\n body,\n signal,\n );\n return {\n items: res.body.results,\n next: res.body.paging?.next?.after ?? null,\n };\n }\n\n private async writeSearchPhase(\n storage: StorageHandle,\n phase: CrmObjectPhase,\n items: CrmRecord[],\n ): Promise<void> {\n for (const record of items) {\n const props = record.properties;\n const modifiedProperty = MODIFIED_PROPERTY[phase];\n const updatedAt =\n parseEpoch(record.updatedAt, 'iso') ??\n parseEpoch(props[modifiedProperty], 'ms') ??\n 0;\n\n let attributes: Record<string, string | number | null>;\n if (phase === 'contacts') {\n attributes = {\n email: props.email ?? null,\n lifecycleStage: props.lifecyclestage ?? null,\n leadStatus: props.hs_lead_status ?? null,\n ownerId: props.hubspot_owner_id ?? null,\n createdAt: parseEpoch(props.createdate, 'ms'),\n };\n } else if (phase === 'companies') {\n attributes = {\n name: props.name ?? null,\n domain: props.domain ?? null,\n industry: props.industry ?? null,\n lifecycleStage: props.lifecyclestage ?? null,\n createdAt: parseEpoch(props.createdate, 'ms'),\n };\n } else {\n attributes = {\n dealName: props.dealname ?? null,\n dealStage: props.dealstage ?? null,\n pipeline: props.pipeline ?? null,\n amount: finiteNumberOrNull(props.amount),\n closeDate: parseEpoch(props.closedate, 'ms'),\n ownerId: props.hubspot_owner_id ?? null,\n createdAt: parseEpoch(props.createdate, 'ms'),\n };\n }\n\n await storage.entity({\n type: ENTITY_TYPE_BY_PHASE[phase]!,\n id: record.id,\n attributes,\n updated_at: updatedAt,\n });\n }\n }\n\n private async fetchDealHistoryPage(\n after: string | null,\n signal?: AbortSignal,\n ): Promise<{ items: unknown[]; next: string | null }> {\n const url = new URL(`${BASE_URL}/crm/v3/objects/deals`);\n url.searchParams.set('limit', String(LIST_LIMIT));\n url.searchParams.set('properties', 'dealstage');\n url.searchParams.set('propertiesWithHistory', 'dealstage');\n if (after) {\n url.searchParams.set('after', after);\n }\n const res = await this.apiGet<CrmListResponse>(\n url.toString(),\n 'deal_events',\n signal,\n );\n return {\n items: res.body.results,\n next: res.body.paging?.next?.after ?? null,\n };\n }\n\n private async writeDealEvents(\n storage: StorageHandle,\n items: DealHistoryRecord[],\n _options: SyncOptions,\n ): Promise<void> {\n for (const record of items) {\n const history = record.propertiesWithHistory?.dealstage ?? [];\n for (const entry of history) {\n const ts = parseEpoch(entry.timestamp, 'iso');\n if (ts === null) {\n continue;\n }\n await storage.event({\n name: DEAL_STAGE_EVENT,\n start_ts: ts,\n end_ts: null,\n attributes: {\n dealId: record.id,\n stage: entry.value ?? null,\n sourceType: entry.sourceType ?? null,\n },\n });\n }\n }\n }\n\n private async fetchCampaignDetail(\n id: number | string,\n resource: string,\n signal?: AbortSignal,\n ): Promise<CampaignDetail> {\n const res = await this.apiGet<CampaignDetail>(\n `${BASE_URL}/email/public/v1/campaigns/${id}`,\n resource,\n signal,\n );\n return res.body;\n }\n\n private async fetchCampaignsPage(\n phase: 'email_campaigns' | 'email_stats',\n after: string | null,\n signal?: AbortSignal,\n ): Promise<{ items: unknown[]; next: string | null }> {\n const url = new URL(`${BASE_URL}/email/public/v1/campaigns`);\n url.searchParams.set('limit', String(LIST_LIMIT));\n if (after) {\n url.searchParams.set('offset', after);\n }\n const listRes = await this.apiGet<CampaignListResponse>(\n url.toString(),\n `${phase}_list`,\n signal,\n );\n const { campaigns, hasMore, offset } = listRes.body;\n\n const details: CampaignDetail[] = [];\n for (const campaign of campaigns) {\n details.push(await this.fetchCampaignDetail(campaign.id, phase, signal));\n }\n\n const next =\n hasMore && offset !== undefined && offset !== null\n ? String(offset)\n : null;\n return { items: details, next };\n }\n\n private async writeEmailCampaigns(\n storage: StorageHandle,\n items: CampaignDetail[],\n ): Promise<void> {\n for (const detail of items) {\n const sentDate = parseEpoch(detail.lastProcessingFinishedAt, 'ms');\n await storage.entity({\n type: ENTITY_TYPE_BY_PHASE.email_campaigns!,\n id: String(detail.id),\n attributes: {\n name: detail.name ?? null,\n subject: detail.subject ?? null,\n fromName: detail.fromName ?? null,\n type: detail.type ?? null,\n sentDate,\n numIncluded: detail.numIncluded ?? null,\n },\n updated_at: sentDate ?? 0,\n });\n }\n }\n\n private async writeEmailStats(\n storage: StorageHandle,\n items: CampaignDetail[],\n ): Promise<void> {\n for (const detail of items) {\n const ts = parseEpoch(detail.lastProcessingFinishedAt, 'ms');\n if (ts === null) {\n continue;\n }\n const counters = detail.counters ?? {};\n const sent = counterValue(counters.sent);\n await storage.metric({\n name: EMAIL_STATS_METRIC,\n ts,\n value: sent,\n attributes: {\n campaignId: String(detail.id),\n campaignName: detail.name ?? null,\n sent,\n delivered: counterValue(counters.delivered),\n opened: counterValue(counters.open),\n clicked: counterValue(counters.click),\n bounced: counterValue(counters.bounce),\n unsubscribed: counterValue(counters.unsubscribed),\n },\n });\n }\n }\n\n private async clearScopeOnFirstPage(\n storage: StorageHandle,\n phase: HubSpotPhase,\n isFull: boolean,\n ): Promise<void> {\n if (phase === 'deal_events') {\n await storage.events([], { names: [DEAL_STAGE_EVENT] });\n return;\n }\n if (phase === 'email_stats') {\n await storage.metrics([], { names: [EMAIL_STATS_METRIC] });\n return;\n }\n if (!isFull) {\n return;\n }\n const entityType = ENTITY_TYPE_BY_PHASE[phase];\n if (entityType) {\n await storage.entities([], { types: [entityType] });\n }\n }\n\n private async writePhase(\n storage: StorageHandle,\n phase: HubSpotPhase,\n items: unknown[],\n options: SyncOptions,\n ): Promise<void> {\n switch (phase) {\n case 'contacts':\n case 'companies':\n case 'deals':\n await this.writeSearchPhase(storage, phase, items as CrmRecord[]);\n return;\n case 'deal_events':\n await this.writeDealEvents(\n storage,\n items as DealHistoryRecord[],\n options,\n );\n return;\n case 'email_campaigns':\n await this.writeEmailCampaigns(storage, items as CampaignDetail[]);\n return;\n case 'email_stats':\n await this.writeEmailStats(storage, items as CampaignDetail[]);\n return;\n }\n }\n\n async sync(\n options: SyncOptions,\n storage: StorageHandle,\n signal?: AbortSignal,\n ): Promise<SyncResult> {\n const cursor = isHubSpotSyncCursor(options.cursor)\n ? options.cursor\n : undefined;\n const isFull = options.mode === 'full';\n\n const phases = selectActivePhases<HubSpotResource, HubSpotPhase>(\n (r) => r,\n PHASE_ORDER,\n this.settings.resources,\n );\n\n return paginateChunked<HubSpotPhase, string>({\n phases,\n cursor,\n signal,\n logger: this.logger,\n fetchPage: async (phase, page, sig) => {\n if (\n phase === 'contacts' ||\n phase === 'companies' ||\n phase === 'deals'\n ) {\n return this.fetchSearchPage(phase, page, options, sig);\n }\n if (phase === 'deal_events') {\n return this.fetchDealHistoryPage(page, sig);\n }\n return this.fetchCampaignsPage(phase, page, sig);\n },\n writeBatch: async (phase, items, page) => {\n if (page === null) {\n await this.clearScopeOnFirstPage(storage, phase, isFull);\n }\n await this.writePhase(storage, phase, items, options);\n },\n });\n }\n}\n","import { HubSpotConnector } from './hubspot';\n\nexport {\n configFields,\n doc,\n HubSpotConnector,\n hubspotResources as resources,\n id,\n} from './hubspot';\nexport type { HubSpotSettings, HubSpotResource } from './hubspot';\nexport default HubSpotConnector;\n"],"mappings":"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","names":["id"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rawdash/connector-hubspot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "Rawdash connector for HubSpot — CRM contacts, companies, deals, and marketing email performance",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -24,15 +24,15 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"zod": "^4.4.3",
|
|
27
|
-
"@rawdash/core": "0.
|
|
27
|
+
"@rawdash/core": "0.23.0"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"fast-check": "^4.8.0",
|
|
31
31
|
"tsup": "^8.0.0",
|
|
32
32
|
"typescript": "^5.7.2",
|
|
33
33
|
"vitest": "^4.1.4",
|
|
34
|
-
"@rawdash/connector-
|
|
35
|
-
"@rawdash/connector-
|
|
34
|
+
"@rawdash/connector-shared": "0.3.1",
|
|
35
|
+
"@rawdash/connector-test-utils": "0.0.10"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"build": "tsup",
|