@rawdash/connector-meta-ads 0.27.0 → 0.28.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/README.md +8 -5
- package/dist/index.d.ts +14 -31
- package/dist/index.js +46 -25
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -40,19 +40,22 @@ A long-lived System User access token from Meta Business Manager, scoped with `a
|
|
|
40
40
|
- Endpoint: `GET /{ad_account_id}/insights?level=campaign&time_increment=1`
|
|
41
41
|
- Unit: USD
|
|
42
42
|
- Granularity: day
|
|
43
|
-
- Dimensions: `date`, `campaignId`, `campaignName
|
|
43
|
+
- Dimensions: `date`, `campaignId`, `campaignName`
|
|
44
|
+
- Measures: `impressions`, `clicks`, `reach`, `conversions`, `conversion_value`
|
|
44
45
|
- Primary value is `spend`. `conversions` is the sum of every entry in the upstream `actions` array; `conversion_value` is the sum of every entry in `action_values`.
|
|
45
46
|
- **`meta_adset_insights`** _(metric)_ - Daily adset-level Meta Ads insights - same fields as the campaign roll-up, bucketed by adset.
|
|
46
47
|
- Endpoint: `GET /{ad_account_id}/insights?level=adset&time_increment=1`
|
|
47
48
|
- Unit: USD
|
|
48
49
|
- Granularity: day
|
|
49
|
-
- Dimensions: `date`, `campaignId`, `campaignName`, `adsetId`, `adsetName
|
|
50
|
+
- Dimensions: `date`, `campaignId`, `campaignName`, `adsetId`, `adsetName`
|
|
51
|
+
- Measures: `impressions`, `clicks`, `reach`, `conversions`, `conversion_value`
|
|
50
52
|
- Primary value is `spend`. Includes campaign_id/campaign_name so adset rows are easy to roll up to their parent campaign.
|
|
51
53
|
- **`meta_ad_insights`** _(metric)_ - Daily ad-level Meta Ads insights - same fields as the adset roll-up, bucketed by ad.
|
|
52
54
|
- Endpoint: `GET /{ad_account_id}/insights?level=ad&time_increment=1`
|
|
53
55
|
- Unit: USD
|
|
54
56
|
- Granularity: day
|
|
55
|
-
- Dimensions: `date`, `campaignId`, `campaignName`, `adsetId`, `adsetName`, `adId`, `adName
|
|
57
|
+
- Dimensions: `date`, `campaignId`, `campaignName`, `adsetId`, `adsetName`, `adId`, `adName`
|
|
58
|
+
- Measures: `impressions`, `clicks`, `reach`, `conversions`, `conversion_value`
|
|
56
59
|
- Primary value is `spend`. Cardinality is the highest of the three insights resources - opt in via `resources: [..., "ad_insights"]` only when you need per-ad breakdowns.
|
|
57
60
|
|
|
58
61
|
## Example
|
|
@@ -88,7 +91,7 @@ export default defineConfig({
|
|
|
88
91
|
connector: metaAds,
|
|
89
92
|
shape: 'metric',
|
|
90
93
|
name: 'meta_campaign_insights',
|
|
91
|
-
field: '
|
|
94
|
+
field: 'value',
|
|
92
95
|
fn: 'sum',
|
|
93
96
|
}),
|
|
94
97
|
},
|
|
@@ -100,7 +103,7 @@ export default defineConfig({
|
|
|
100
103
|
connector: metaAds,
|
|
101
104
|
shape: 'metric',
|
|
102
105
|
name: 'meta_campaign_insights',
|
|
103
|
-
field: '
|
|
106
|
+
field: 'value',
|
|
104
107
|
fn: 'sum',
|
|
105
108
|
}),
|
|
106
109
|
},
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BaseConnector, ConnectorContext, SyncOptions, StorageHandle, SyncResult, ConnectorDoc } from '@rawdash/core';
|
|
1
|
+
import { BaseConnector, ConnectorContext, SyncOptions, StorageHandle, SyncResult, ConnectorDoc, MetricSample } from '@rawdash/core';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
|
|
4
4
|
declare const configFields: z.ZodObject<{
|
|
@@ -72,15 +72,13 @@ declare const metaAdsResources: {
|
|
|
72
72
|
}, {
|
|
73
73
|
readonly name: "campaignName";
|
|
74
74
|
readonly description: "Meta campaign name.";
|
|
75
|
-
}
|
|
75
|
+
}];
|
|
76
|
+
readonly measures: [{
|
|
76
77
|
readonly name: "impressions";
|
|
77
78
|
readonly description: "Total impressions on the day.";
|
|
78
79
|
}, {
|
|
79
80
|
readonly name: "clicks";
|
|
80
81
|
readonly description: "Total clicks on the day.";
|
|
81
|
-
}, {
|
|
82
|
-
readonly name: "spend";
|
|
83
|
-
readonly description: "Total spend (account currency).";
|
|
84
82
|
}, {
|
|
85
83
|
readonly name: "reach";
|
|
86
84
|
readonly description: "Unique reach on the day.";
|
|
@@ -134,15 +132,13 @@ declare const metaAdsResources: {
|
|
|
134
132
|
}, {
|
|
135
133
|
readonly name: "adsetName";
|
|
136
134
|
readonly description: "Meta adset name.";
|
|
137
|
-
}
|
|
135
|
+
}];
|
|
136
|
+
readonly measures: [{
|
|
138
137
|
readonly name: "impressions";
|
|
139
138
|
readonly description: "Total impressions on the day.";
|
|
140
139
|
}, {
|
|
141
140
|
readonly name: "clicks";
|
|
142
141
|
readonly description: "Total clicks on the day.";
|
|
143
|
-
}, {
|
|
144
|
-
readonly name: "spend";
|
|
145
|
-
readonly description: "Total spend (account currency).";
|
|
146
142
|
}, {
|
|
147
143
|
readonly name: "reach";
|
|
148
144
|
readonly description: "Unique reach on the day.";
|
|
@@ -204,15 +200,13 @@ declare const metaAdsResources: {
|
|
|
204
200
|
}, {
|
|
205
201
|
readonly name: "adName";
|
|
206
202
|
readonly description: "Meta ad name.";
|
|
207
|
-
}
|
|
203
|
+
}];
|
|
204
|
+
readonly measures: [{
|
|
208
205
|
readonly name: "impressions";
|
|
209
206
|
readonly description: "Total impressions on the day.";
|
|
210
207
|
}, {
|
|
211
208
|
readonly name: "clicks";
|
|
212
209
|
readonly description: "Total clicks on the day.";
|
|
213
|
-
}, {
|
|
214
|
-
readonly name: "spend";
|
|
215
|
-
readonly description: "Total spend (account currency).";
|
|
216
210
|
}, {
|
|
217
211
|
readonly name: "reach";
|
|
218
212
|
readonly description: "Unique reach on the day.";
|
|
@@ -292,12 +286,7 @@ declare function campaignToEntity(row: MetaCampaign): {
|
|
|
292
286
|
attributes: Record<string, string | number | null>;
|
|
293
287
|
updated_at: number;
|
|
294
288
|
};
|
|
295
|
-
declare function insightRowToMetricSample(row: MetaCampaignInsight | MetaAdsetInsight | MetaAdInsight, phase: 'campaign_insights' | 'adset_insights' | 'ad_insights'):
|
|
296
|
-
name: string;
|
|
297
|
-
ts: number;
|
|
298
|
-
value: number;
|
|
299
|
-
attributes: Record<string, string | number | null>;
|
|
300
|
-
};
|
|
289
|
+
declare function insightRowToMetricSample(row: MetaCampaignInsight | MetaAdsetInsight | MetaAdInsight, phase: 'campaign_insights' | 'adset_insights' | 'ad_insights'): MetricSample;
|
|
301
290
|
declare const id = "meta-ads";
|
|
302
291
|
declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCredentials> {
|
|
303
292
|
static readonly id = "meta-ads";
|
|
@@ -341,15 +330,13 @@ declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCre
|
|
|
341
330
|
}, {
|
|
342
331
|
readonly name: "campaignName";
|
|
343
332
|
readonly description: "Meta campaign name.";
|
|
344
|
-
}
|
|
333
|
+
}];
|
|
334
|
+
readonly measures: [{
|
|
345
335
|
readonly name: "impressions";
|
|
346
336
|
readonly description: "Total impressions on the day.";
|
|
347
337
|
}, {
|
|
348
338
|
readonly name: "clicks";
|
|
349
339
|
readonly description: "Total clicks on the day.";
|
|
350
|
-
}, {
|
|
351
|
-
readonly name: "spend";
|
|
352
|
-
readonly description: "Total spend (account currency).";
|
|
353
340
|
}, {
|
|
354
341
|
readonly name: "reach";
|
|
355
342
|
readonly description: "Unique reach on the day.";
|
|
@@ -403,15 +390,13 @@ declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCre
|
|
|
403
390
|
}, {
|
|
404
391
|
readonly name: "adsetName";
|
|
405
392
|
readonly description: "Meta adset name.";
|
|
406
|
-
}
|
|
393
|
+
}];
|
|
394
|
+
readonly measures: [{
|
|
407
395
|
readonly name: "impressions";
|
|
408
396
|
readonly description: "Total impressions on the day.";
|
|
409
397
|
}, {
|
|
410
398
|
readonly name: "clicks";
|
|
411
399
|
readonly description: "Total clicks on the day.";
|
|
412
|
-
}, {
|
|
413
|
-
readonly name: "spend";
|
|
414
|
-
readonly description: "Total spend (account currency).";
|
|
415
400
|
}, {
|
|
416
401
|
readonly name: "reach";
|
|
417
402
|
readonly description: "Unique reach on the day.";
|
|
@@ -473,15 +458,13 @@ declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCre
|
|
|
473
458
|
}, {
|
|
474
459
|
readonly name: "adName";
|
|
475
460
|
readonly description: "Meta ad name.";
|
|
476
|
-
}
|
|
461
|
+
}];
|
|
462
|
+
readonly measures: [{
|
|
477
463
|
readonly name: "impressions";
|
|
478
464
|
readonly description: "Total impressions on the day.";
|
|
479
465
|
}, {
|
|
480
466
|
readonly name: "clicks";
|
|
481
467
|
readonly description: "Total clicks on the day.";
|
|
482
|
-
}, {
|
|
483
|
-
readonly name: "spend";
|
|
484
|
-
readonly description: "Total spend (account currency).";
|
|
485
468
|
}, {
|
|
486
469
|
readonly name: "reach";
|
|
487
470
|
readonly description: "Unique reach on the day.";
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
defineConnectorDoc,
|
|
13
13
|
defineResources,
|
|
14
14
|
makeChunkedCursorGuard,
|
|
15
|
+
metricSample,
|
|
15
16
|
paginateChunked,
|
|
16
17
|
schemasFromResources,
|
|
17
18
|
selectActivePhases
|
|
@@ -265,10 +266,11 @@ var metaAdsResources = defineResources({
|
|
|
265
266
|
dimensions: [
|
|
266
267
|
{ name: "date", description: "Calendar day of the metric sample (UTC)." },
|
|
267
268
|
{ name: "campaignId", description: "Meta campaign id." },
|
|
268
|
-
{ name: "campaignName", description: "Meta campaign name." }
|
|
269
|
+
{ name: "campaignName", description: "Meta campaign name." }
|
|
270
|
+
],
|
|
271
|
+
measures: [
|
|
269
272
|
{ name: "impressions", description: "Total impressions on the day." },
|
|
270
273
|
{ name: "clicks", description: "Total clicks on the day." },
|
|
271
|
-
{ name: "spend", description: "Total spend (account currency)." },
|
|
272
274
|
{ name: "reach", description: "Unique reach on the day." },
|
|
273
275
|
{ name: "conversions", description: "Total attributed actions." },
|
|
274
276
|
{
|
|
@@ -290,10 +292,11 @@ var metaAdsResources = defineResources({
|
|
|
290
292
|
{ name: "campaignId", description: "Parent campaign id." },
|
|
291
293
|
{ name: "campaignName", description: "Parent campaign name." },
|
|
292
294
|
{ name: "adsetId", description: "Meta adset id." },
|
|
293
|
-
{ name: "adsetName", description: "Meta adset name." }
|
|
295
|
+
{ name: "adsetName", description: "Meta adset name." }
|
|
296
|
+
],
|
|
297
|
+
measures: [
|
|
294
298
|
{ name: "impressions", description: "Total impressions on the day." },
|
|
295
299
|
{ name: "clicks", description: "Total clicks on the day." },
|
|
296
|
-
{ name: "spend", description: "Total spend (account currency)." },
|
|
297
300
|
{ name: "reach", description: "Unique reach on the day." },
|
|
298
301
|
{ name: "conversions", description: "Total attributed actions." },
|
|
299
302
|
{
|
|
@@ -317,10 +320,11 @@ var metaAdsResources = defineResources({
|
|
|
317
320
|
{ name: "adsetId", description: "Parent adset id." },
|
|
318
321
|
{ name: "adsetName", description: "Parent adset name." },
|
|
319
322
|
{ name: "adId", description: "Meta ad id." },
|
|
320
|
-
{ name: "adName", description: "Meta ad name." }
|
|
323
|
+
{ name: "adName", description: "Meta ad name." }
|
|
324
|
+
],
|
|
325
|
+
measures: [
|
|
321
326
|
{ name: "impressions", description: "Total impressions on the day." },
|
|
322
327
|
{ name: "clicks", description: "Total clicks on the day." },
|
|
323
|
-
{ name: "spend", description: "Total spend (account currency)." },
|
|
324
328
|
{ name: "reach", description: "Unique reach on the day." },
|
|
325
329
|
{ name: "conversions", description: "Total attributed actions." },
|
|
326
330
|
{
|
|
@@ -351,7 +355,6 @@ function campaignToEntity(row) {
|
|
|
351
355
|
function buildInsightAttributes(row) {
|
|
352
356
|
const impressions = parseNumber(row.impressions);
|
|
353
357
|
const clicks = parseNumber(row.clicks);
|
|
354
|
-
const spend = parseNumber(row.spend);
|
|
355
358
|
const reach = parseNumber(row.reach);
|
|
356
359
|
const conversions = (row.actions ?? []).length ? sumActionValues(row.actions) : 0;
|
|
357
360
|
const conversionValue = sumActionValues(row.action_values);
|
|
@@ -359,32 +362,50 @@ function buildInsightAttributes(row) {
|
|
|
359
362
|
date: row.date_start,
|
|
360
363
|
impressions,
|
|
361
364
|
clicks,
|
|
362
|
-
spend,
|
|
363
365
|
reach,
|
|
364
366
|
conversions,
|
|
365
367
|
conversion_value: conversionValue
|
|
366
368
|
};
|
|
367
369
|
}
|
|
368
370
|
function insightRowToMetricSample(row, phase) {
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
}
|
|
377
|
-
if (phase === "
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
371
|
+
const base = buildInsightAttributes(row);
|
|
372
|
+
const ts = metaDateToMs(row.date_start);
|
|
373
|
+
const value = parseNumber(row.spend);
|
|
374
|
+
const campaignAttributes = {
|
|
375
|
+
...base,
|
|
376
|
+
campaignId: row.campaign_id,
|
|
377
|
+
campaignName: row.campaign_name ?? null
|
|
378
|
+
};
|
|
379
|
+
if (phase === "campaign_insights") {
|
|
380
|
+
return metricSample(metaAdsResources, "meta_campaign_insights", {
|
|
381
|
+
ts,
|
|
382
|
+
value,
|
|
383
|
+
attributes: campaignAttributes
|
|
384
|
+
});
|
|
381
385
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
386
|
+
const adsetRow = row;
|
|
387
|
+
const adsetAttributes = {
|
|
388
|
+
...campaignAttributes,
|
|
389
|
+
adsetId: adsetRow.adset_id,
|
|
390
|
+
adsetName: adsetRow.adset_name ?? null
|
|
387
391
|
};
|
|
392
|
+
if (phase === "adset_insights") {
|
|
393
|
+
return metricSample(metaAdsResources, "meta_adset_insights", {
|
|
394
|
+
ts,
|
|
395
|
+
value,
|
|
396
|
+
attributes: adsetAttributes
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
const adRow = row;
|
|
400
|
+
return metricSample(metaAdsResources, "meta_ad_insights", {
|
|
401
|
+
ts,
|
|
402
|
+
value,
|
|
403
|
+
attributes: {
|
|
404
|
+
...adsetAttributes,
|
|
405
|
+
adId: adRow.ad_id,
|
|
406
|
+
adName: adRow.ad_name ?? null
|
|
407
|
+
}
|
|
408
|
+
});
|
|
388
409
|
}
|
|
389
410
|
var CAMPAIGN_FIELDS = [
|
|
390
411
|
"id",
|
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/meta-ads.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 { connectorUserAgent } 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 adAccountId: z\n .string()\n .trim()\n .regex(\n /^act_\\d+$/,\n 'Ad account ID must look like `act_<digits>` (e.g. `act_1234567890`)',\n )\n .meta({\n label: 'Ad account ID',\n description:\n 'Meta Marketing API ad account ID. Find it in Ads Manager → Settings → Account info; it always starts with `act_`.',\n placeholder: 'act_1234567890',\n }),\n accessToken: z.object({ $secret: z.string() }).meta({\n label: 'System user access token',\n description:\n 'Long-lived System User access token from Meta Business Manager with `ads_read` (and, for newer accounts, `read_insights`) scopes on the chosen ad account.',\n placeholder: 'EAAB...',\n secret: true,\n }),\n apiVersion: z\n .string()\n .regex(/^v\\d+\\.\\d+$/, 'API version must look like `v21.0`')\n .optional()\n .meta({\n label: 'Graph API version',\n description:\n 'Pin a specific Meta Graph API version (e.g. `v21.0`). Defaults to `v21.0`.',\n placeholder: 'v21.0',\n }),\n lookbackDays: z.number().int().positive().optional().meta({\n label: 'Lookback days (full sync)',\n description:\n 'How many calendar days of insights to fetch on a full sync. Defaults to 90.',\n placeholder: '90',\n }),\n resources: z\n .array(\n z.enum([\n 'campaigns',\n 'campaign_insights',\n 'adset_insights',\n 'ad_insights',\n ]),\n )\n .nonempty()\n .optional()\n .meta({\n label: 'Resources',\n description:\n 'Which Meta resources to sync. Omit to sync all. Ad-level insights are the most expensive - leave them out if you only need campaign or adset rollups.',\n }),\n }),\n);\n\nexport const doc: ConnectorDoc = defineConnectorDoc({\n displayName: 'Meta Ads',\n category: 'marketing',\n brandColor: '#0866FF',\n tagline:\n 'Sync Meta (Facebook + Instagram) ad campaigns plus daily campaign, adset, and ad-level insights - spend, impressions, clicks, reach, conversions, and conversion value.',\n vendor: {\n name: 'Meta',\n domain: 'meta.com',\n apiDocs: 'https://developers.facebook.com/docs/marketing-api/insights',\n website: 'https://business.facebook.com',\n },\n auth: {\n summary:\n 'A long-lived System User access token from Meta Business Manager, scoped with `ads_read` (and `read_insights` on newer accounts) for the target ad account.',\n setup: [\n 'In Meta Business Manager → Business Settings → Users → System Users, create a System User (or reuse an existing one) and assign it to the ad account with at least the Advertiser role.',\n 'Generate a System User access token for the System User; pick `ads_read` and (where available) `read_insights` as the scopes. Choose the longest available expiry - System User tokens can be made effectively non-expiring.',\n 'Find the ad account ID in Ads Manager → Settings → Account info; it always starts with `act_`.',\n 'Store the token as a secret and reference it from the connector config as `accessToken: secret(\"META_ACCESS_TOKEN\")` alongside `adAccountId: \"act_<id>\"`.',\n ],\n },\n rateLimit:\n 'Meta enforces per-app and per-ad-account budgets surfaced through the `X-Business-Use-Case-Usage` header. Sync at most every few hours per ad account; very large accounts may need a daily cadence.',\n limitations: [\n 'Insights are always fetched at daily granularity. Sub-daily breakdowns are not supported.',\n 'Insights for the most recent 30 days are re-fetched on every incremental sync because Meta keeps attributing conversions after the event date.',\n 'Creative-level breakdowns (publisher_platform, placement, demographics) are intentionally out of scope to keep the metric cardinality bounded.',\n ],\n});\n\nexport interface MetaAdsSettings {\n adAccountId: string;\n apiVersion?: string;\n lookbackDays?: number;\n resources?: readonly MetaAdsResource[];\n}\n\nconst metaAdsCredentials = {\n accessToken: {\n description: 'Meta System User access token',\n auth: 'required' as const,\n },\n} satisfies CredentialsSchema;\n\ntype MetaAdsCredentials = typeof metaAdsCredentials;\n\nconst PHASE_ORDER = [\n 'campaigns',\n 'campaign_insights',\n 'adset_insights',\n 'ad_insights',\n] as const;\n\ntype MetaAdsPhase = (typeof PHASE_ORDER)[number];\n\nexport type MetaAdsResource = MetaAdsPhase;\n\nconst isMetaAdsSyncCursor = makeChunkedCursorGuard(PHASE_ORDER);\n\nconst DEFAULT_API_VERSION = 'v21.0';\nconst BASE_URL = 'https://graph.facebook.com';\nconst PAGE_LIMIT = 100;\n\nconst MS_PER_DAY = 24 * 60 * 60 * 1000;\nconst DEFAULT_LOOKBACK_DAYS = 90;\nconst INCREMENTAL_LOOKBACK_DAYS = 30;\n\nconst INSIGHTS_PHASES: ReadonlySet<MetaAdsPhase> = new Set([\n 'campaign_insights',\n 'adset_insights',\n 'ad_insights',\n]);\n\nconst PHASE_TO_LEVEL: Record<\n 'campaign_insights' | 'adset_insights' | 'ad_insights',\n 'campaign' | 'adset' | 'ad'\n> = {\n campaign_insights: 'campaign',\n adset_insights: 'adset',\n ad_insights: 'ad',\n};\n\nconst METRIC_NAME: Record<MetaAdsPhase, string> = {\n campaigns: 'meta_campaign',\n campaign_insights: 'meta_campaign_insights',\n adset_insights: 'meta_adset_insights',\n ad_insights: 'meta_ad_insights',\n};\n\nconst CAMPAIGN_ENTITY_TYPE = 'meta_campaign';\n\nfunction toMetaDate(date: Date): string {\n const y = date.getUTCFullYear();\n const m = String(date.getUTCMonth() + 1).padStart(2, '0');\n const d = String(date.getUTCDate()).padStart(2, '0');\n return `${y}-${m}-${d}`;\n}\n\nfunction metaDateToMs(metaDate: string): number {\n const [y, m, d] = metaDate.split('-').map((part) => Number(part));\n if (\n !Number.isFinite(y) ||\n !Number.isFinite(m) ||\n !Number.isFinite(d) ||\n y === undefined ||\n m === undefined ||\n d === undefined\n ) {\n return 0;\n }\n return Date.UTC(y, m - 1, d);\n}\n\ninterface MetaDateRange {\n since: string;\n until: string;\n}\n\nfunction getDateRange(\n options: SyncOptions,\n lookbackDays: number,\n): MetaDateRange {\n const now = Date.now();\n const until = toMetaDate(new Date(now));\n if (options.mode === 'latest' && options.since) {\n const startMs = now - (INCREMENTAL_LOOKBACK_DAYS - 1) * MS_PER_DAY;\n return { since: toMetaDate(new Date(startMs)), until };\n }\n if (options.since) {\n const sinceMs = new Date(options.since).getTime();\n if (Number.isFinite(sinceMs)) {\n const days = Math.max(1, Math.ceil((now - sinceMs) / MS_PER_DAY));\n const cappedDays = Math.min(days, lookbackDays);\n const startMs = now - (cappedDays - 1) * MS_PER_DAY;\n return { since: toMetaDate(new Date(startMs)), until };\n }\n }\n const startMs = now - (lookbackDays - 1) * MS_PER_DAY;\n return { since: toMetaDate(new Date(startMs)), until };\n}\n\nfunction parseNumber(value: unknown): number {\n if (typeof value === 'number' && Number.isFinite(value)) {\n return value;\n }\n if (typeof value === 'string' && value.trim() !== '') {\n const n = Number(value);\n return Number.isFinite(n) ? n : 0;\n }\n return 0;\n}\n\nfunction parseOptionalNumber(value: unknown): number | null {\n if (value === null || value === undefined) {\n return null;\n }\n if (typeof value === 'number' && Number.isFinite(value)) {\n return value;\n }\n if (typeof value === 'string' && value.trim() !== '') {\n const n = Number(value);\n return Number.isFinite(n) ? n : null;\n }\n return null;\n}\n\nfunction sumActionValues(\n entries: ReadonlyArray<{ value?: unknown }> | undefined,\n): number {\n if (!entries) {\n return 0;\n }\n let total = 0;\n for (const entry of entries) {\n total += parseNumber(entry.value);\n }\n return total;\n}\n\nconst actionEntrySchema = z.object({\n action_type: z.string().optional(),\n value: z.union([z.string(), z.number()]).optional(),\n});\n\nconst campaignSchema = z.object({\n id: z.string().min(1),\n name: z.string().nullish(),\n objective: z.string().nullish(),\n status: z.string().nullish(),\n effective_status: z.string().nullish(),\n daily_budget: z.union([z.string(), z.number()]).nullish(),\n lifetime_budget: z.union([z.string(), z.number()]).nullish(),\n created_time: z.string().nullish(),\n updated_time: z.string().nullish(),\n});\n\nconst insightsBaseShape = {\n date_start: z.string(),\n date_stop: z.string().optional(),\n impressions: z.union([z.string(), z.number()]).optional(),\n clicks: z.union([z.string(), z.number()]).optional(),\n spend: z.union([z.string(), z.number()]).optional(),\n reach: z.union([z.string(), z.number()]).optional(),\n actions: z.array(actionEntrySchema).optional(),\n action_values: z.array(actionEntrySchema).optional(),\n};\n\nconst campaignInsightSchema = z.object({\n ...insightsBaseShape,\n campaign_id: z.string().min(1),\n campaign_name: z.string().nullish(),\n});\n\nconst adsetInsightSchema = z.object({\n ...insightsBaseShape,\n campaign_id: z.string().min(1),\n campaign_name: z.string().nullish(),\n adset_id: z.string().min(1),\n adset_name: z.string().nullish(),\n});\n\nconst adInsightSchema = z.object({\n ...insightsBaseShape,\n campaign_id: z.string().min(1),\n campaign_name: z.string().nullish(),\n adset_id: z.string().min(1),\n adset_name: z.string().nullish(),\n ad_id: z.string().min(1),\n ad_name: z.string().nullish(),\n});\n\nconst campaignsSchema = z.array(campaignSchema);\nconst campaignInsightsSchema = z.array(campaignInsightSchema);\nconst adsetInsightsSchema = z.array(adsetInsightSchema);\nconst adInsightsSchema = z.array(adInsightSchema);\n\nexport const metaAdsResources = defineResources({\n meta_campaign: {\n shape: 'entity',\n filterable: [\n {\n field: 'effectiveStatus',\n ops: ['eq'],\n values: [\n 'ACTIVE',\n 'PAUSED',\n 'DELETED',\n 'ARCHIVED',\n 'IN_PROCESS',\n 'WITH_ISSUES',\n ],\n },\n ],\n description:\n 'Meta ad campaigns with name, objective, status, and budget. Upserted by id; one row per campaign in the ad account.',\n endpoint: 'GET /{ad_account_id}/campaigns',\n responses: { campaigns: campaignsSchema },\n },\n meta_campaign_insights: {\n shape: 'metric',\n description:\n 'Daily campaign-level Meta Ads insights - spend (primary value), impressions, clicks, reach, conversions, and conversion value bucketed by campaign.',\n endpoint: 'GET /{ad_account_id}/insights?level=campaign&time_increment=1',\n unit: 'USD',\n granularity: 'day',\n notes:\n 'Primary value is `spend`. `conversions` is the sum of every entry in the upstream `actions` array; `conversion_value` is the sum of every entry in `action_values`.',\n dimensions: [\n { name: 'date', description: 'Calendar day of the metric sample (UTC).' },\n { name: 'campaignId', description: 'Meta campaign id.' },\n { name: 'campaignName', description: 'Meta campaign name.' },\n { name: 'impressions', description: 'Total impressions on the day.' },\n { name: 'clicks', description: 'Total clicks on the day.' },\n { name: 'spend', description: 'Total spend (account currency).' },\n { name: 'reach', description: 'Unique reach on the day.' },\n { name: 'conversions', description: 'Total attributed actions.' },\n {\n name: 'conversion_value',\n description: 'Total attributed action value (account currency).',\n },\n ],\n responses: { campaign_insights: campaignInsightsSchema },\n },\n meta_adset_insights: {\n shape: 'metric',\n description:\n 'Daily adset-level Meta Ads insights - same fields as the campaign roll-up, bucketed by adset.',\n endpoint: 'GET /{ad_account_id}/insights?level=adset&time_increment=1',\n unit: 'USD',\n granularity: 'day',\n notes:\n 'Primary value is `spend`. Includes campaign_id/campaign_name so adset rows are easy to roll up to their parent campaign.',\n dimensions: [\n { name: 'date', description: 'Calendar day of the metric sample (UTC).' },\n { name: 'campaignId', description: 'Parent campaign id.' },\n { name: 'campaignName', description: 'Parent campaign name.' },\n { name: 'adsetId', description: 'Meta adset id.' },\n { name: 'adsetName', description: 'Meta adset name.' },\n { name: 'impressions', description: 'Total impressions on the day.' },\n { name: 'clicks', description: 'Total clicks on the day.' },\n { name: 'spend', description: 'Total spend (account currency).' },\n { name: 'reach', description: 'Unique reach on the day.' },\n { name: 'conversions', description: 'Total attributed actions.' },\n {\n name: 'conversion_value',\n description: 'Total attributed action value (account currency).',\n },\n ],\n responses: { adset_insights: adsetInsightsSchema },\n },\n meta_ad_insights: {\n shape: 'metric',\n description:\n 'Daily ad-level Meta Ads insights - same fields as the adset roll-up, bucketed by ad.',\n endpoint: 'GET /{ad_account_id}/insights?level=ad&time_increment=1',\n unit: 'USD',\n granularity: 'day',\n notes:\n 'Primary value is `spend`. Cardinality is the highest of the three insights resources - opt in via `resources: [..., \"ad_insights\"]` only when you need per-ad breakdowns.',\n dimensions: [\n { name: 'date', description: 'Calendar day of the metric sample (UTC).' },\n { name: 'campaignId', description: 'Parent campaign id.' },\n { name: 'campaignName', description: 'Parent campaign name.' },\n { name: 'adsetId', description: 'Parent adset id.' },\n { name: 'adsetName', description: 'Parent adset name.' },\n { name: 'adId', description: 'Meta ad id.' },\n { name: 'adName', description: 'Meta ad name.' },\n { name: 'impressions', description: 'Total impressions on the day.' },\n { name: 'clicks', description: 'Total clicks on the day.' },\n { name: 'spend', description: 'Total spend (account currency).' },\n { name: 'reach', description: 'Unique reach on the day.' },\n { name: 'conversions', description: 'Total attributed actions.' },\n {\n name: 'conversion_value',\n description: 'Total attributed action value (account currency).',\n },\n ],\n responses: { ad_insights: adInsightsSchema },\n },\n});\n\nexport interface MetaActionEntry {\n action_type?: string;\n value?: string | number;\n}\n\nexport interface MetaCampaign {\n id: string;\n name?: string | null;\n objective?: string | null;\n status?: string | null;\n effective_status?: string | null;\n daily_budget?: string | number | null;\n lifetime_budget?: string | number | null;\n created_time?: string | null;\n updated_time?: string | null;\n}\n\ninterface MetaInsightsBase {\n date_start: string;\n date_stop?: string;\n impressions?: string | number;\n clicks?: string | number;\n spend?: string | number;\n reach?: string | number;\n actions?: MetaActionEntry[];\n action_values?: MetaActionEntry[];\n}\n\nexport interface MetaCampaignInsight extends MetaInsightsBase {\n campaign_id: string;\n campaign_name?: string | null;\n}\n\nexport interface MetaAdsetInsight extends MetaCampaignInsight {\n adset_id: string;\n adset_name?: string | null;\n}\n\nexport interface MetaAdInsight extends MetaAdsetInsight {\n ad_id: string;\n ad_name?: string | null;\n}\n\ninterface MetaPagedResponse<T> {\n data?: T[];\n paging?: {\n cursors?: { after?: string; before?: string };\n next?: string;\n };\n}\n\nexport function campaignToEntity(row: MetaCampaign): {\n type: string;\n id: string;\n attributes: Record<string, string | number | null>;\n updated_at: number;\n} {\n const updatedAt = row.updated_time\n ? new Date(row.updated_time).getTime()\n : row.created_time\n ? new Date(row.created_time).getTime()\n : 0;\n return {\n type: CAMPAIGN_ENTITY_TYPE,\n id: row.id,\n attributes: {\n name: row.name ?? null,\n objective: row.objective ?? null,\n status: row.status ?? null,\n effectiveStatus: row.effective_status ?? null,\n dailyBudget: parseOptionalNumber(row.daily_budget),\n lifetimeBudget: parseOptionalNumber(row.lifetime_budget),\n createdAt: row.created_time ? new Date(row.created_time).getTime() : null,\n },\n updated_at: Number.isFinite(updatedAt) ? updatedAt : 0,\n };\n}\n\nfunction buildInsightAttributes(\n row: MetaInsightsBase,\n): Record<string, string | number | null> {\n const impressions = parseNumber(row.impressions);\n const clicks = parseNumber(row.clicks);\n const spend = parseNumber(row.spend);\n const reach = parseNumber(row.reach);\n const conversions = (row.actions ?? []).length\n ? sumActionValues(row.actions)\n : 0;\n const conversionValue = sumActionValues(row.action_values);\n return {\n date: row.date_start,\n impressions,\n clicks,\n spend,\n reach,\n conversions,\n conversion_value: conversionValue,\n };\n}\n\nexport function insightRowToMetricSample(\n row: MetaCampaignInsight | MetaAdsetInsight | MetaAdInsight,\n phase: 'campaign_insights' | 'adset_insights' | 'ad_insights',\n): {\n name: string;\n ts: number;\n value: number;\n attributes: Record<string, string | number | null>;\n} {\n const attributes = buildInsightAttributes(row);\n attributes['campaignId'] = row.campaign_id;\n attributes['campaignName'] = row.campaign_name ?? null;\n if (phase !== 'campaign_insights') {\n const ar = row as MetaAdsetInsight;\n attributes['adsetId'] = ar.adset_id;\n attributes['adsetName'] = ar.adset_name ?? null;\n }\n if (phase === 'ad_insights') {\n const ar = row as MetaAdInsight;\n attributes['adId'] = ar.ad_id;\n attributes['adName'] = ar.ad_name ?? null;\n }\n return {\n name: METRIC_NAME[phase],\n ts: metaDateToMs(row.date_start),\n value: parseNumber(row.spend),\n attributes,\n };\n}\n\nconst CAMPAIGN_FIELDS = [\n 'id',\n 'name',\n 'objective',\n 'status',\n 'effective_status',\n 'daily_budget',\n 'lifetime_budget',\n 'created_time',\n 'updated_time',\n].join(',');\n\nconst COMMON_INSIGHT_FIELDS = [\n 'date_start',\n 'date_stop',\n 'campaign_id',\n 'campaign_name',\n 'impressions',\n 'clicks',\n 'spend',\n 'reach',\n 'actions',\n 'action_values',\n];\n\nconst INSIGHT_FIELDS: Record<\n 'campaign_insights' | 'adset_insights' | 'ad_insights',\n string\n> = {\n campaign_insights: COMMON_INSIGHT_FIELDS.join(','),\n adset_insights: [...COMMON_INSIGHT_FIELDS, 'adset_id', 'adset_name'].join(\n ',',\n ),\n ad_insights: [\n ...COMMON_INSIGHT_FIELDS,\n 'adset_id',\n 'adset_name',\n 'ad_id',\n 'ad_name',\n ].join(','),\n};\n\nexport const id = 'meta-ads';\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 MetaAdsConnector extends BaseConnector<\n MetaAdsSettings,\n MetaAdsCredentials\n> {\n static readonly id = id;\n\n static readonly resources = metaAdsResources;\n\n static readonly schemas = schemasFromResources(metaAdsResources);\n\n static create(input: unknown, ctx?: ConnectorContext): MetaAdsConnector {\n const parsed = configFields.parse(input);\n return new MetaAdsConnector(\n {\n adAccountId: parsed.adAccountId,\n apiVersion: parsed.apiVersion,\n lookbackDays: parsed.lookbackDays,\n resources: parsed.resources,\n },\n { accessToken: parsed.accessToken },\n ctx,\n );\n }\n\n readonly id = id;\n override readonly credentials = metaAdsCredentials;\n\n private buildHeaders(): Record<string, string> {\n return {\n Authorization: `Bearer ${this.creds.accessToken}`,\n 'User-Agent': connectorUserAgent('meta-ads'),\n };\n }\n\n private apiVersion(): string {\n return this.settings.apiVersion ?? DEFAULT_API_VERSION;\n }\n\n private accountBase(): string {\n return `${BASE_URL}/${this.apiVersion()}/${this.settings.adAccountId}`;\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 async fetchCampaignsPage(\n after: string | null,\n options: SyncOptions,\n signal: AbortSignal | undefined,\n ): Promise<{ items: unknown[]; next: string | null }> {\n const url = new URL(`${this.accountBase()}/campaigns`);\n url.searchParams.set('fields', CAMPAIGN_FIELDS);\n url.searchParams.set('limit', String(PAGE_LIMIT));\n const filter = this.singleSpec(options, CAMPAIGN_ENTITY_TYPE)?.filter;\n const effectiveStatus = pushableEq(filter, 'effectiveStatus');\n if (effectiveStatus !== null) {\n url.searchParams.set(\n 'effective_status',\n JSON.stringify([effectiveStatus]),\n );\n }\n if (after) {\n url.searchParams.set('after', after);\n }\n const res = await this.get<MetaPagedResponse<MetaCampaign>>(\n url.toString(),\n {\n resource: 'campaigns',\n headers: this.buildHeaders(),\n signal,\n },\n );\n return {\n items: res.body.data ?? [],\n next: res.body.paging?.cursors?.after ?? null,\n };\n }\n\n private async fetchInsightsPage(\n phase: 'campaign_insights' | 'adset_insights' | 'ad_insights',\n dateRange: MetaDateRange,\n after: string | null,\n signal: AbortSignal | undefined,\n ): Promise<{ items: unknown[]; next: string | null }> {\n const url = new URL(`${this.accountBase()}/insights`);\n url.searchParams.set('level', PHASE_TO_LEVEL[phase]);\n url.searchParams.set('time_increment', '1');\n url.searchParams.set('fields', INSIGHT_FIELDS[phase]);\n url.searchParams.set(\n 'time_range',\n JSON.stringify({ since: dateRange.since, until: dateRange.until }),\n );\n url.searchParams.set('limit', String(PAGE_LIMIT));\n if (after) {\n url.searchParams.set('after', after);\n }\n const res = await this.get<MetaPagedResponse<unknown>>(url.toString(), {\n resource: phase,\n headers: this.buildHeaders(),\n signal,\n });\n return {\n items: res.body.data ?? [],\n next: res.body.paging?.cursors?.after ?? null,\n };\n }\n\n private async writeCampaigns(\n storage: StorageHandle,\n items: MetaCampaign[],\n ): Promise<void> {\n for (const row of items) {\n await storage.entity(campaignToEntity(row));\n }\n }\n\n private async writeInsights(\n storage: StorageHandle,\n phase: 'campaign_insights' | 'adset_insights' | 'ad_insights',\n items: Array<MetaCampaignInsight | MetaAdsetInsight | MetaAdInsight>,\n ): Promise<void> {\n for (const row of items) {\n await storage.metric(insightRowToMetricSample(row, phase));\n }\n }\n\n private async clearScopeOnFirstPage(\n storage: StorageHandle,\n phase: MetaAdsPhase,\n isFull: boolean,\n ): Promise<void> {\n if (INSIGHTS_PHASES.has(phase)) {\n await storage.metrics([], { names: [METRIC_NAME[phase]] });\n return;\n }\n if (!isFull) {\n return;\n }\n await storage.entities([], { types: [CAMPAIGN_ENTITY_TYPE] });\n }\n\n async sync(\n options: SyncOptions,\n storage: StorageHandle,\n signal?: AbortSignal,\n ): Promise<SyncResult> {\n const cursor = isMetaAdsSyncCursor(options.cursor)\n ? options.cursor\n : undefined;\n const isFull = options.mode === 'full';\n const lookbackDays = this.settings.lookbackDays ?? DEFAULT_LOOKBACK_DAYS;\n const dateRange = getDateRange(options, lookbackDays);\n\n const phases = selectActivePhases<MetaAdsResource, MetaAdsPhase>(\n (r) => r,\n PHASE_ORDER,\n this.settings.resources,\n );\n\n return paginateChunked<MetaAdsPhase, string>({\n phases,\n cursor,\n signal,\n logger: this.logger,\n fetchPage: async (phase, page, sig) => {\n if (phase === 'campaigns') {\n return this.fetchCampaignsPage(page, options, sig);\n }\n return this.fetchInsightsPage(phase, dateRange, page, sig);\n },\n writeBatch: async (phase, items, page) => {\n if (page === null) {\n await this.clearScopeOnFirstPage(storage, phase, isFull);\n }\n if (phase === 'campaigns') {\n await this.writeCampaigns(storage, items as MetaCampaign[]);\n return;\n }\n await this.writeInsights(\n storage,\n phase,\n items as Array<\n MetaCampaignInsight | MetaAdsetInsight | MetaAdInsight\n >,\n );\n },\n });\n }\n}\n","import { MetaAdsConnector } from './meta-ads';\n\nexport {\n campaignToEntity,\n configFields,\n doc,\n id,\n insightRowToMetricSample,\n MetaAdsConnector,\n metaAdsResources as resources,\n} from './meta-ads';\nexport type {\n MetaAdsResource,\n MetaAdsSettings,\n MetaActionEntry,\n MetaAdInsight,\n MetaAdsetInsight,\n MetaCampaign,\n MetaCampaignInsight,\n} from './meta-ads';\nexport default MetaAdsConnector;\n"],"mappings":"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","names":["startMs"]}
|
|
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/meta-ads.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 { connectorUserAgent } from '@rawdash/connector-shared';\nimport {\n BaseConnector,\n type ConnectorContext,\n type ConnectorDoc,\n type CredentialsSchema,\n type FetchSpec,\n type FilterClause,\n type MetricSample,\n type StorageHandle,\n type SyncOptions,\n type SyncResult,\n defineConfigFields,\n defineConnectorDoc,\n defineResources,\n makeChunkedCursorGuard,\n metricSample,\n paginateChunked,\n schemasFromResources,\n selectActivePhases,\n} from '@rawdash/core';\nimport { z } from 'zod';\n\nexport const configFields = defineConfigFields(\n z.object({\n adAccountId: z\n .string()\n .trim()\n .regex(\n /^act_\\d+$/,\n 'Ad account ID must look like `act_<digits>` (e.g. `act_1234567890`)',\n )\n .meta({\n label: 'Ad account ID',\n description:\n 'Meta Marketing API ad account ID. Find it in Ads Manager → Settings → Account info; it always starts with `act_`.',\n placeholder: 'act_1234567890',\n }),\n accessToken: z.object({ $secret: z.string() }).meta({\n label: 'System user access token',\n description:\n 'Long-lived System User access token from Meta Business Manager with `ads_read` (and, for newer accounts, `read_insights`) scopes on the chosen ad account.',\n placeholder: 'EAAB...',\n secret: true,\n }),\n apiVersion: z\n .string()\n .regex(/^v\\d+\\.\\d+$/, 'API version must look like `v21.0`')\n .optional()\n .meta({\n label: 'Graph API version',\n description:\n 'Pin a specific Meta Graph API version (e.g. `v21.0`). Defaults to `v21.0`.',\n placeholder: 'v21.0',\n }),\n lookbackDays: z.number().int().positive().optional().meta({\n label: 'Lookback days (full sync)',\n description:\n 'How many calendar days of insights to fetch on a full sync. Defaults to 90.',\n placeholder: '90',\n }),\n resources: z\n .array(\n z.enum([\n 'campaigns',\n 'campaign_insights',\n 'adset_insights',\n 'ad_insights',\n ]),\n )\n .nonempty()\n .optional()\n .meta({\n label: 'Resources',\n description:\n 'Which Meta resources to sync. Omit to sync all. Ad-level insights are the most expensive - leave them out if you only need campaign or adset rollups.',\n }),\n }),\n);\n\nexport const doc: ConnectorDoc = defineConnectorDoc({\n displayName: 'Meta Ads',\n category: 'marketing',\n brandColor: '#0866FF',\n tagline:\n 'Sync Meta (Facebook + Instagram) ad campaigns plus daily campaign, adset, and ad-level insights - spend, impressions, clicks, reach, conversions, and conversion value.',\n vendor: {\n name: 'Meta',\n domain: 'meta.com',\n apiDocs: 'https://developers.facebook.com/docs/marketing-api/insights',\n website: 'https://business.facebook.com',\n },\n auth: {\n summary:\n 'A long-lived System User access token from Meta Business Manager, scoped with `ads_read` (and `read_insights` on newer accounts) for the target ad account.',\n setup: [\n 'In Meta Business Manager → Business Settings → Users → System Users, create a System User (or reuse an existing one) and assign it to the ad account with at least the Advertiser role.',\n 'Generate a System User access token for the System User; pick `ads_read` and (where available) `read_insights` as the scopes. Choose the longest available expiry - System User tokens can be made effectively non-expiring.',\n 'Find the ad account ID in Ads Manager → Settings → Account info; it always starts with `act_`.',\n 'Store the token as a secret and reference it from the connector config as `accessToken: secret(\"META_ACCESS_TOKEN\")` alongside `adAccountId: \"act_<id>\"`.',\n ],\n },\n rateLimit:\n 'Meta enforces per-app and per-ad-account budgets surfaced through the `X-Business-Use-Case-Usage` header. Sync at most every few hours per ad account; very large accounts may need a daily cadence.',\n limitations: [\n 'Insights are always fetched at daily granularity. Sub-daily breakdowns are not supported.',\n 'Insights for the most recent 30 days are re-fetched on every incremental sync because Meta keeps attributing conversions after the event date.',\n 'Creative-level breakdowns (publisher_platform, placement, demographics) are intentionally out of scope to keep the metric cardinality bounded.',\n ],\n});\n\nexport interface MetaAdsSettings {\n adAccountId: string;\n apiVersion?: string;\n lookbackDays?: number;\n resources?: readonly MetaAdsResource[];\n}\n\nconst metaAdsCredentials = {\n accessToken: {\n description: 'Meta System User access token',\n auth: 'required' as const,\n },\n} satisfies CredentialsSchema;\n\ntype MetaAdsCredentials = typeof metaAdsCredentials;\n\nconst PHASE_ORDER = [\n 'campaigns',\n 'campaign_insights',\n 'adset_insights',\n 'ad_insights',\n] as const;\n\ntype MetaAdsPhase = (typeof PHASE_ORDER)[number];\n\nexport type MetaAdsResource = MetaAdsPhase;\n\nconst isMetaAdsSyncCursor = makeChunkedCursorGuard(PHASE_ORDER);\n\nconst DEFAULT_API_VERSION = 'v21.0';\nconst BASE_URL = 'https://graph.facebook.com';\nconst PAGE_LIMIT = 100;\n\nconst MS_PER_DAY = 24 * 60 * 60 * 1000;\nconst DEFAULT_LOOKBACK_DAYS = 90;\nconst INCREMENTAL_LOOKBACK_DAYS = 30;\n\nconst INSIGHTS_PHASES: ReadonlySet<MetaAdsPhase> = new Set([\n 'campaign_insights',\n 'adset_insights',\n 'ad_insights',\n]);\n\nconst PHASE_TO_LEVEL: Record<\n 'campaign_insights' | 'adset_insights' | 'ad_insights',\n 'campaign' | 'adset' | 'ad'\n> = {\n campaign_insights: 'campaign',\n adset_insights: 'adset',\n ad_insights: 'ad',\n};\n\nconst METRIC_NAME: Record<MetaAdsPhase, string> = {\n campaigns: 'meta_campaign',\n campaign_insights: 'meta_campaign_insights',\n adset_insights: 'meta_adset_insights',\n ad_insights: 'meta_ad_insights',\n};\n\nconst CAMPAIGN_ENTITY_TYPE = 'meta_campaign';\n\nfunction toMetaDate(date: Date): string {\n const y = date.getUTCFullYear();\n const m = String(date.getUTCMonth() + 1).padStart(2, '0');\n const d = String(date.getUTCDate()).padStart(2, '0');\n return `${y}-${m}-${d}`;\n}\n\nfunction metaDateToMs(metaDate: string): number {\n const [y, m, d] = metaDate.split('-').map((part) => Number(part));\n if (\n !Number.isFinite(y) ||\n !Number.isFinite(m) ||\n !Number.isFinite(d) ||\n y === undefined ||\n m === undefined ||\n d === undefined\n ) {\n return 0;\n }\n return Date.UTC(y, m - 1, d);\n}\n\ninterface MetaDateRange {\n since: string;\n until: string;\n}\n\nfunction getDateRange(\n options: SyncOptions,\n lookbackDays: number,\n): MetaDateRange {\n const now = Date.now();\n const until = toMetaDate(new Date(now));\n if (options.mode === 'latest' && options.since) {\n const startMs = now - (INCREMENTAL_LOOKBACK_DAYS - 1) * MS_PER_DAY;\n return { since: toMetaDate(new Date(startMs)), until };\n }\n if (options.since) {\n const sinceMs = new Date(options.since).getTime();\n if (Number.isFinite(sinceMs)) {\n const days = Math.max(1, Math.ceil((now - sinceMs) / MS_PER_DAY));\n const cappedDays = Math.min(days, lookbackDays);\n const startMs = now - (cappedDays - 1) * MS_PER_DAY;\n return { since: toMetaDate(new Date(startMs)), until };\n }\n }\n const startMs = now - (lookbackDays - 1) * MS_PER_DAY;\n return { since: toMetaDate(new Date(startMs)), until };\n}\n\nfunction parseNumber(value: unknown): number {\n if (typeof value === 'number' && Number.isFinite(value)) {\n return value;\n }\n if (typeof value === 'string' && value.trim() !== '') {\n const n = Number(value);\n return Number.isFinite(n) ? n : 0;\n }\n return 0;\n}\n\nfunction parseOptionalNumber(value: unknown): number | null {\n if (value === null || value === undefined) {\n return null;\n }\n if (typeof value === 'number' && Number.isFinite(value)) {\n return value;\n }\n if (typeof value === 'string' && value.trim() !== '') {\n const n = Number(value);\n return Number.isFinite(n) ? n : null;\n }\n return null;\n}\n\nfunction sumActionValues(\n entries: ReadonlyArray<{ value?: unknown }> | undefined,\n): number {\n if (!entries) {\n return 0;\n }\n let total = 0;\n for (const entry of entries) {\n total += parseNumber(entry.value);\n }\n return total;\n}\n\nconst actionEntrySchema = z.object({\n action_type: z.string().optional(),\n value: z.union([z.string(), z.number()]).optional(),\n});\n\nconst campaignSchema = z.object({\n id: z.string().min(1),\n name: z.string().nullish(),\n objective: z.string().nullish(),\n status: z.string().nullish(),\n effective_status: z.string().nullish(),\n daily_budget: z.union([z.string(), z.number()]).nullish(),\n lifetime_budget: z.union([z.string(), z.number()]).nullish(),\n created_time: z.string().nullish(),\n updated_time: z.string().nullish(),\n});\n\nconst insightsBaseShape = {\n date_start: z.string(),\n date_stop: z.string().optional(),\n impressions: z.union([z.string(), z.number()]).optional(),\n clicks: z.union([z.string(), z.number()]).optional(),\n spend: z.union([z.string(), z.number()]).optional(),\n reach: z.union([z.string(), z.number()]).optional(),\n actions: z.array(actionEntrySchema).optional(),\n action_values: z.array(actionEntrySchema).optional(),\n};\n\nconst campaignInsightSchema = z.object({\n ...insightsBaseShape,\n campaign_id: z.string().min(1),\n campaign_name: z.string().nullish(),\n});\n\nconst adsetInsightSchema = z.object({\n ...insightsBaseShape,\n campaign_id: z.string().min(1),\n campaign_name: z.string().nullish(),\n adset_id: z.string().min(1),\n adset_name: z.string().nullish(),\n});\n\nconst adInsightSchema = z.object({\n ...insightsBaseShape,\n campaign_id: z.string().min(1),\n campaign_name: z.string().nullish(),\n adset_id: z.string().min(1),\n adset_name: z.string().nullish(),\n ad_id: z.string().min(1),\n ad_name: z.string().nullish(),\n});\n\nconst campaignsSchema = z.array(campaignSchema);\nconst campaignInsightsSchema = z.array(campaignInsightSchema);\nconst adsetInsightsSchema = z.array(adsetInsightSchema);\nconst adInsightsSchema = z.array(adInsightSchema);\n\nexport const metaAdsResources = defineResources({\n meta_campaign: {\n shape: 'entity',\n filterable: [\n {\n field: 'effectiveStatus',\n ops: ['eq'],\n values: [\n 'ACTIVE',\n 'PAUSED',\n 'DELETED',\n 'ARCHIVED',\n 'IN_PROCESS',\n 'WITH_ISSUES',\n ],\n },\n ],\n description:\n 'Meta ad campaigns with name, objective, status, and budget. Upserted by id; one row per campaign in the ad account.',\n endpoint: 'GET /{ad_account_id}/campaigns',\n responses: { campaigns: campaignsSchema },\n },\n meta_campaign_insights: {\n shape: 'metric',\n description:\n 'Daily campaign-level Meta Ads insights - spend (primary value), impressions, clicks, reach, conversions, and conversion value bucketed by campaign.',\n endpoint: 'GET /{ad_account_id}/insights?level=campaign&time_increment=1',\n unit: 'USD',\n granularity: 'day',\n notes:\n 'Primary value is `spend`. `conversions` is the sum of every entry in the upstream `actions` array; `conversion_value` is the sum of every entry in `action_values`.',\n dimensions: [\n { name: 'date', description: 'Calendar day of the metric sample (UTC).' },\n { name: 'campaignId', description: 'Meta campaign id.' },\n { name: 'campaignName', description: 'Meta campaign name.' },\n ],\n measures: [\n { name: 'impressions', description: 'Total impressions on the day.' },\n { name: 'clicks', description: 'Total clicks on the day.' },\n { name: 'reach', description: 'Unique reach on the day.' },\n { name: 'conversions', description: 'Total attributed actions.' },\n {\n name: 'conversion_value',\n description: 'Total attributed action value (account currency).',\n },\n ],\n responses: { campaign_insights: campaignInsightsSchema },\n },\n meta_adset_insights: {\n shape: 'metric',\n description:\n 'Daily adset-level Meta Ads insights - same fields as the campaign roll-up, bucketed by adset.',\n endpoint: 'GET /{ad_account_id}/insights?level=adset&time_increment=1',\n unit: 'USD',\n granularity: 'day',\n notes:\n 'Primary value is `spend`. Includes campaign_id/campaign_name so adset rows are easy to roll up to their parent campaign.',\n dimensions: [\n { name: 'date', description: 'Calendar day of the metric sample (UTC).' },\n { name: 'campaignId', description: 'Parent campaign id.' },\n { name: 'campaignName', description: 'Parent campaign name.' },\n { name: 'adsetId', description: 'Meta adset id.' },\n { name: 'adsetName', description: 'Meta adset name.' },\n ],\n measures: [\n { name: 'impressions', description: 'Total impressions on the day.' },\n { name: 'clicks', description: 'Total clicks on the day.' },\n { name: 'reach', description: 'Unique reach on the day.' },\n { name: 'conversions', description: 'Total attributed actions.' },\n {\n name: 'conversion_value',\n description: 'Total attributed action value (account currency).',\n },\n ],\n responses: { adset_insights: adsetInsightsSchema },\n },\n meta_ad_insights: {\n shape: 'metric',\n description:\n 'Daily ad-level Meta Ads insights - same fields as the adset roll-up, bucketed by ad.',\n endpoint: 'GET /{ad_account_id}/insights?level=ad&time_increment=1',\n unit: 'USD',\n granularity: 'day',\n notes:\n 'Primary value is `spend`. Cardinality is the highest of the three insights resources - opt in via `resources: [..., \"ad_insights\"]` only when you need per-ad breakdowns.',\n dimensions: [\n { name: 'date', description: 'Calendar day of the metric sample (UTC).' },\n { name: 'campaignId', description: 'Parent campaign id.' },\n { name: 'campaignName', description: 'Parent campaign name.' },\n { name: 'adsetId', description: 'Parent adset id.' },\n { name: 'adsetName', description: 'Parent adset name.' },\n { name: 'adId', description: 'Meta ad id.' },\n { name: 'adName', description: 'Meta ad name.' },\n ],\n measures: [\n { name: 'impressions', description: 'Total impressions on the day.' },\n { name: 'clicks', description: 'Total clicks on the day.' },\n { name: 'reach', description: 'Unique reach on the day.' },\n { name: 'conversions', description: 'Total attributed actions.' },\n {\n name: 'conversion_value',\n description: 'Total attributed action value (account currency).',\n },\n ],\n responses: { ad_insights: adInsightsSchema },\n },\n});\n\nexport interface MetaActionEntry {\n action_type?: string;\n value?: string | number;\n}\n\nexport interface MetaCampaign {\n id: string;\n name?: string | null;\n objective?: string | null;\n status?: string | null;\n effective_status?: string | null;\n daily_budget?: string | number | null;\n lifetime_budget?: string | number | null;\n created_time?: string | null;\n updated_time?: string | null;\n}\n\ninterface MetaInsightsBase {\n date_start: string;\n date_stop?: string;\n impressions?: string | number;\n clicks?: string | number;\n spend?: string | number;\n reach?: string | number;\n actions?: MetaActionEntry[];\n action_values?: MetaActionEntry[];\n}\n\nexport interface MetaCampaignInsight extends MetaInsightsBase {\n campaign_id: string;\n campaign_name?: string | null;\n}\n\nexport interface MetaAdsetInsight extends MetaCampaignInsight {\n adset_id: string;\n adset_name?: string | null;\n}\n\nexport interface MetaAdInsight extends MetaAdsetInsight {\n ad_id: string;\n ad_name?: string | null;\n}\n\ninterface MetaPagedResponse<T> {\n data?: T[];\n paging?: {\n cursors?: { after?: string; before?: string };\n next?: string;\n };\n}\n\nexport function campaignToEntity(row: MetaCampaign): {\n type: string;\n id: string;\n attributes: Record<string, string | number | null>;\n updated_at: number;\n} {\n const updatedAt = row.updated_time\n ? new Date(row.updated_time).getTime()\n : row.created_time\n ? new Date(row.created_time).getTime()\n : 0;\n return {\n type: CAMPAIGN_ENTITY_TYPE,\n id: row.id,\n attributes: {\n name: row.name ?? null,\n objective: row.objective ?? null,\n status: row.status ?? null,\n effectiveStatus: row.effective_status ?? null,\n dailyBudget: parseOptionalNumber(row.daily_budget),\n lifetimeBudget: parseOptionalNumber(row.lifetime_budget),\n createdAt: row.created_time ? new Date(row.created_time).getTime() : null,\n },\n updated_at: Number.isFinite(updatedAt) ? updatedAt : 0,\n };\n}\n\nfunction buildInsightAttributes(\n row: MetaInsightsBase,\n): Record<string, string | number | null> {\n const impressions = parseNumber(row.impressions);\n const clicks = parseNumber(row.clicks);\n const reach = parseNumber(row.reach);\n const conversions = (row.actions ?? []).length\n ? sumActionValues(row.actions)\n : 0;\n const conversionValue = sumActionValues(row.action_values);\n return {\n date: row.date_start,\n impressions,\n clicks,\n reach,\n conversions,\n conversion_value: conversionValue,\n };\n}\n\nexport function insightRowToMetricSample(\n row: MetaCampaignInsight | MetaAdsetInsight | MetaAdInsight,\n phase: 'campaign_insights' | 'adset_insights' | 'ad_insights',\n): MetricSample {\n const base = buildInsightAttributes(row);\n const ts = metaDateToMs(row.date_start);\n const value = parseNumber(row.spend);\n const campaignAttributes = {\n ...base,\n campaignId: row.campaign_id,\n campaignName: row.campaign_name ?? null,\n };\n if (phase === 'campaign_insights') {\n return metricSample(metaAdsResources, 'meta_campaign_insights', {\n ts,\n value,\n attributes: campaignAttributes,\n });\n }\n const adsetRow = row as MetaAdsetInsight;\n const adsetAttributes = {\n ...campaignAttributes,\n adsetId: adsetRow.adset_id,\n adsetName: adsetRow.adset_name ?? null,\n };\n if (phase === 'adset_insights') {\n return metricSample(metaAdsResources, 'meta_adset_insights', {\n ts,\n value,\n attributes: adsetAttributes,\n });\n }\n const adRow = row as MetaAdInsight;\n return metricSample(metaAdsResources, 'meta_ad_insights', {\n ts,\n value,\n attributes: {\n ...adsetAttributes,\n adId: adRow.ad_id,\n adName: adRow.ad_name ?? null,\n },\n });\n}\n\nconst CAMPAIGN_FIELDS = [\n 'id',\n 'name',\n 'objective',\n 'status',\n 'effective_status',\n 'daily_budget',\n 'lifetime_budget',\n 'created_time',\n 'updated_time',\n].join(',');\n\nconst COMMON_INSIGHT_FIELDS = [\n 'date_start',\n 'date_stop',\n 'campaign_id',\n 'campaign_name',\n 'impressions',\n 'clicks',\n 'spend',\n 'reach',\n 'actions',\n 'action_values',\n];\n\nconst INSIGHT_FIELDS: Record<\n 'campaign_insights' | 'adset_insights' | 'ad_insights',\n string\n> = {\n campaign_insights: COMMON_INSIGHT_FIELDS.join(','),\n adset_insights: [...COMMON_INSIGHT_FIELDS, 'adset_id', 'adset_name'].join(\n ',',\n ),\n ad_insights: [\n ...COMMON_INSIGHT_FIELDS,\n 'adset_id',\n 'adset_name',\n 'ad_id',\n 'ad_name',\n ].join(','),\n};\n\nexport const id = 'meta-ads';\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 MetaAdsConnector extends BaseConnector<\n MetaAdsSettings,\n MetaAdsCredentials\n> {\n static readonly id = id;\n\n static readonly resources = metaAdsResources;\n\n static readonly schemas = schemasFromResources(metaAdsResources);\n\n static create(input: unknown, ctx?: ConnectorContext): MetaAdsConnector {\n const parsed = configFields.parse(input);\n return new MetaAdsConnector(\n {\n adAccountId: parsed.adAccountId,\n apiVersion: parsed.apiVersion,\n lookbackDays: parsed.lookbackDays,\n resources: parsed.resources,\n },\n { accessToken: parsed.accessToken },\n ctx,\n );\n }\n\n readonly id = id;\n override readonly credentials = metaAdsCredentials;\n\n private buildHeaders(): Record<string, string> {\n return {\n Authorization: `Bearer ${this.creds.accessToken}`,\n 'User-Agent': connectorUserAgent('meta-ads'),\n };\n }\n\n private apiVersion(): string {\n return this.settings.apiVersion ?? DEFAULT_API_VERSION;\n }\n\n private accountBase(): string {\n return `${BASE_URL}/${this.apiVersion()}/${this.settings.adAccountId}`;\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 async fetchCampaignsPage(\n after: string | null,\n options: SyncOptions,\n signal: AbortSignal | undefined,\n ): Promise<{ items: unknown[]; next: string | null }> {\n const url = new URL(`${this.accountBase()}/campaigns`);\n url.searchParams.set('fields', CAMPAIGN_FIELDS);\n url.searchParams.set('limit', String(PAGE_LIMIT));\n const filter = this.singleSpec(options, CAMPAIGN_ENTITY_TYPE)?.filter;\n const effectiveStatus = pushableEq(filter, 'effectiveStatus');\n if (effectiveStatus !== null) {\n url.searchParams.set(\n 'effective_status',\n JSON.stringify([effectiveStatus]),\n );\n }\n if (after) {\n url.searchParams.set('after', after);\n }\n const res = await this.get<MetaPagedResponse<MetaCampaign>>(\n url.toString(),\n {\n resource: 'campaigns',\n headers: this.buildHeaders(),\n signal,\n },\n );\n return {\n items: res.body.data ?? [],\n next: res.body.paging?.cursors?.after ?? null,\n };\n }\n\n private async fetchInsightsPage(\n phase: 'campaign_insights' | 'adset_insights' | 'ad_insights',\n dateRange: MetaDateRange,\n after: string | null,\n signal: AbortSignal | undefined,\n ): Promise<{ items: unknown[]; next: string | null }> {\n const url = new URL(`${this.accountBase()}/insights`);\n url.searchParams.set('level', PHASE_TO_LEVEL[phase]);\n url.searchParams.set('time_increment', '1');\n url.searchParams.set('fields', INSIGHT_FIELDS[phase]);\n url.searchParams.set(\n 'time_range',\n JSON.stringify({ since: dateRange.since, until: dateRange.until }),\n );\n url.searchParams.set('limit', String(PAGE_LIMIT));\n if (after) {\n url.searchParams.set('after', after);\n }\n const res = await this.get<MetaPagedResponse<unknown>>(url.toString(), {\n resource: phase,\n headers: this.buildHeaders(),\n signal,\n });\n return {\n items: res.body.data ?? [],\n next: res.body.paging?.cursors?.after ?? null,\n };\n }\n\n private async writeCampaigns(\n storage: StorageHandle,\n items: MetaCampaign[],\n ): Promise<void> {\n for (const row of items) {\n await storage.entity(campaignToEntity(row));\n }\n }\n\n private async writeInsights(\n storage: StorageHandle,\n phase: 'campaign_insights' | 'adset_insights' | 'ad_insights',\n items: Array<MetaCampaignInsight | MetaAdsetInsight | MetaAdInsight>,\n ): Promise<void> {\n for (const row of items) {\n await storage.metric(insightRowToMetricSample(row, phase));\n }\n }\n\n private async clearScopeOnFirstPage(\n storage: StorageHandle,\n phase: MetaAdsPhase,\n isFull: boolean,\n ): Promise<void> {\n if (INSIGHTS_PHASES.has(phase)) {\n await storage.metrics([], { names: [METRIC_NAME[phase]] });\n return;\n }\n if (!isFull) {\n return;\n }\n await storage.entities([], { types: [CAMPAIGN_ENTITY_TYPE] });\n }\n\n async sync(\n options: SyncOptions,\n storage: StorageHandle,\n signal?: AbortSignal,\n ): Promise<SyncResult> {\n const cursor = isMetaAdsSyncCursor(options.cursor)\n ? options.cursor\n : undefined;\n const isFull = options.mode === 'full';\n const lookbackDays = this.settings.lookbackDays ?? DEFAULT_LOOKBACK_DAYS;\n const dateRange = getDateRange(options, lookbackDays);\n\n const phases = selectActivePhases<MetaAdsResource, MetaAdsPhase>(\n (r) => r,\n PHASE_ORDER,\n this.settings.resources,\n );\n\n return paginateChunked<MetaAdsPhase, string>({\n phases,\n cursor,\n signal,\n logger: this.logger,\n fetchPage: async (phase, page, sig) => {\n if (phase === 'campaigns') {\n return this.fetchCampaignsPage(page, options, sig);\n }\n return this.fetchInsightsPage(phase, dateRange, page, sig);\n },\n writeBatch: async (phase, items, page) => {\n if (page === null) {\n await this.clearScopeOnFirstPage(storage, phase, isFull);\n }\n if (phase === 'campaigns') {\n await this.writeCampaigns(storage, items as MetaCampaign[]);\n return;\n }\n await this.writeInsights(\n storage,\n phase,\n items as Array<\n MetaCampaignInsight | MetaAdsetInsight | MetaAdInsight\n >,\n );\n },\n });\n }\n}\n","import { MetaAdsConnector } from './meta-ads';\n\nexport {\n campaignToEntity,\n configFields,\n doc,\n id,\n insightRowToMetricSample,\n MetaAdsConnector,\n metaAdsResources as resources,\n} from './meta-ads';\nexport type {\n MetaAdsResource,\n MetaAdsSettings,\n MetaActionEntry,\n MetaAdInsight,\n MetaAdsetInsight,\n MetaCampaign,\n MetaCampaignInsight,\n} from './meta-ads';\nexport default MetaAdsConnector;\n"],"mappings":"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","names":["startMs"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rawdash/connector-meta-ads",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"description": "Rawdash connector for Meta (Facebook + Instagram) Ads — campaigns and daily campaign/adset/ad insights",
|
|
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.28.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",
|