@rawdash/connector-meta-ads 0.28.0 → 0.28.2
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 -8
- package/dist/index.d.ts +34 -34
- package/dist/index.js +26 -17
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -24,13 +24,13 @@ A long-lived System User access token from Meta Business Manager, scoped with `a
|
|
|
24
24
|
|
|
25
25
|
## Configuration
|
|
26
26
|
|
|
27
|
-
| Field | Type | Required | Description
|
|
28
|
-
| -------------- | ------ | -------- |
|
|
29
|
-
| `adAccountId` | string | Yes | Meta Marketing API ad account ID. Find it in Ads Manager → Settings → Account info; it always starts with `act_`.
|
|
30
|
-
| `accessToken` | secret | Yes | 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.
|
|
31
|
-
| `apiVersion` | string | No | Pin a specific Meta Graph API version (e.g. `
|
|
32
|
-
| `lookbackDays` | number | No | How many calendar days of insights to fetch on a full sync. Defaults to 90.
|
|
33
|
-
| `resources` | array | No | 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.
|
|
27
|
+
| Field | Type | Required | Description |
|
|
28
|
+
| -------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
29
|
+
| `adAccountId` | string | Yes | Meta Marketing API ad account ID. Find it in Ads Manager → Settings → Account info; it always starts with `act_`. |
|
|
30
|
+
| `accessToken` | secret | Yes | 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. |
|
|
31
|
+
| `apiVersion` | string | No | Pin a specific Meta Graph API version (e.g. `v25.0`). Defaults to `v25.0`. Meta deprecates Marketing API versions on a rolling schedule, so older pins eventually stop working. |
|
|
32
|
+
| `lookbackDays` | number | No | How many calendar days of insights to fetch on a full sync. Defaults to 90. |
|
|
33
|
+
| `resources` | array | No | 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. |
|
|
34
34
|
|
|
35
35
|
## Resources
|
|
36
36
|
|
|
@@ -42,7 +42,7 @@ A long-lived System User access token from Meta Business Manager, scoped with `a
|
|
|
42
42
|
- Granularity: day
|
|
43
43
|
- Dimensions: `date`, `campaignId`, `campaignName`
|
|
44
44
|
- Measures: `impressions`, `clicks`, `reach`, `conversions`, `conversion_value`
|
|
45
|
-
- Primary value is `spend`. `conversions` is the sum of
|
|
45
|
+
- Primary value is `spend`. `conversions` is the sum of the upstream `conversions` field (conversion events only, default attribution window); `conversion_value` is the sum of the upstream `conversion_values` field.
|
|
46
46
|
- **`meta_adset_insights`** _(metric)_ - Daily adset-level Meta Ads insights - same fields as the campaign roll-up, bucketed by adset.
|
|
47
47
|
- Endpoint: `GET /{ad_account_id}/insights?level=adset&time_increment=1`
|
|
48
48
|
- Unit: USD
|
package/dist/index.d.ts
CHANGED
|
@@ -62,7 +62,7 @@ declare const metaAdsResources: {
|
|
|
62
62
|
readonly endpoint: "GET /{ad_account_id}/insights?level=campaign&time_increment=1";
|
|
63
63
|
readonly unit: "USD";
|
|
64
64
|
readonly granularity: "day";
|
|
65
|
-
readonly notes: "Primary value is `spend`. `conversions` is the sum of
|
|
65
|
+
readonly notes: "Primary value is `spend`. `conversions` is the sum of the upstream `conversions` field (conversion events only, default attribution window); `conversion_value` is the sum of the upstream `conversion_values` field.";
|
|
66
66
|
readonly dimensions: [{
|
|
67
67
|
readonly name: "date";
|
|
68
68
|
readonly description: "Calendar day of the metric sample (UTC).";
|
|
@@ -84,10 +84,10 @@ declare const metaAdsResources: {
|
|
|
84
84
|
readonly description: "Unique reach on the day.";
|
|
85
85
|
}, {
|
|
86
86
|
readonly name: "conversions";
|
|
87
|
-
readonly description: "Total attributed
|
|
87
|
+
readonly description: "Total attributed conversion events on the day.";
|
|
88
88
|
}, {
|
|
89
89
|
readonly name: "conversion_value";
|
|
90
|
-
readonly description: "Total attributed
|
|
90
|
+
readonly description: "Total attributed conversion value (account currency).";
|
|
91
91
|
}];
|
|
92
92
|
readonly responses: {
|
|
93
93
|
readonly campaign_insights: z.ZodArray<z.ZodObject<{
|
|
@@ -99,11 +99,11 @@ declare const metaAdsResources: {
|
|
|
99
99
|
clicks: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
100
100
|
spend: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
101
101
|
reach: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
102
|
-
|
|
102
|
+
conversions: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
103
103
|
action_type: z.ZodOptional<z.ZodString>;
|
|
104
104
|
value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
105
105
|
}, z.core.$strip>>>;
|
|
106
|
-
|
|
106
|
+
conversion_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
107
107
|
action_type: z.ZodOptional<z.ZodString>;
|
|
108
108
|
value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
109
109
|
}, z.core.$strip>>>;
|
|
@@ -144,10 +144,10 @@ declare const metaAdsResources: {
|
|
|
144
144
|
readonly description: "Unique reach on the day.";
|
|
145
145
|
}, {
|
|
146
146
|
readonly name: "conversions";
|
|
147
|
-
readonly description: "Total attributed
|
|
147
|
+
readonly description: "Total attributed conversion events on the day.";
|
|
148
148
|
}, {
|
|
149
149
|
readonly name: "conversion_value";
|
|
150
|
-
readonly description: "Total attributed
|
|
150
|
+
readonly description: "Total attributed conversion value (account currency).";
|
|
151
151
|
}];
|
|
152
152
|
readonly responses: {
|
|
153
153
|
readonly adset_insights: z.ZodArray<z.ZodObject<{
|
|
@@ -161,11 +161,11 @@ declare const metaAdsResources: {
|
|
|
161
161
|
clicks: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
162
162
|
spend: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
163
163
|
reach: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
164
|
-
|
|
164
|
+
conversions: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
165
165
|
action_type: z.ZodOptional<z.ZodString>;
|
|
166
166
|
value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
167
167
|
}, z.core.$strip>>>;
|
|
168
|
-
|
|
168
|
+
conversion_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
169
169
|
action_type: z.ZodOptional<z.ZodString>;
|
|
170
170
|
value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
171
171
|
}, z.core.$strip>>>;
|
|
@@ -212,10 +212,10 @@ declare const metaAdsResources: {
|
|
|
212
212
|
readonly description: "Unique reach on the day.";
|
|
213
213
|
}, {
|
|
214
214
|
readonly name: "conversions";
|
|
215
|
-
readonly description: "Total attributed
|
|
215
|
+
readonly description: "Total attributed conversion events on the day.";
|
|
216
216
|
}, {
|
|
217
217
|
readonly name: "conversion_value";
|
|
218
|
-
readonly description: "Total attributed
|
|
218
|
+
readonly description: "Total attributed conversion value (account currency).";
|
|
219
219
|
}];
|
|
220
220
|
readonly responses: {
|
|
221
221
|
readonly ad_insights: z.ZodArray<z.ZodObject<{
|
|
@@ -231,11 +231,11 @@ declare const metaAdsResources: {
|
|
|
231
231
|
clicks: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
232
232
|
spend: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
233
233
|
reach: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
234
|
-
|
|
234
|
+
conversions: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
235
235
|
action_type: z.ZodOptional<z.ZodString>;
|
|
236
236
|
value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
237
237
|
}, z.core.$strip>>>;
|
|
238
|
-
|
|
238
|
+
conversion_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
239
239
|
action_type: z.ZodOptional<z.ZodString>;
|
|
240
240
|
value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
241
241
|
}, z.core.$strip>>>;
|
|
@@ -265,8 +265,8 @@ interface MetaInsightsBase {
|
|
|
265
265
|
clicks?: string | number;
|
|
266
266
|
spend?: string | number;
|
|
267
267
|
reach?: string | number;
|
|
268
|
-
|
|
269
|
-
|
|
268
|
+
conversions?: MetaActionEntry[];
|
|
269
|
+
conversion_values?: MetaActionEntry[];
|
|
270
270
|
}
|
|
271
271
|
interface MetaCampaignInsight extends MetaInsightsBase {
|
|
272
272
|
campaign_id: string;
|
|
@@ -320,7 +320,7 @@ declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCre
|
|
|
320
320
|
readonly endpoint: "GET /{ad_account_id}/insights?level=campaign&time_increment=1";
|
|
321
321
|
readonly unit: "USD";
|
|
322
322
|
readonly granularity: "day";
|
|
323
|
-
readonly notes: "Primary value is `spend`. `conversions` is the sum of
|
|
323
|
+
readonly notes: "Primary value is `spend`. `conversions` is the sum of the upstream `conversions` field (conversion events only, default attribution window); `conversion_value` is the sum of the upstream `conversion_values` field.";
|
|
324
324
|
readonly dimensions: [{
|
|
325
325
|
readonly name: "date";
|
|
326
326
|
readonly description: "Calendar day of the metric sample (UTC).";
|
|
@@ -342,10 +342,10 @@ declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCre
|
|
|
342
342
|
readonly description: "Unique reach on the day.";
|
|
343
343
|
}, {
|
|
344
344
|
readonly name: "conversions";
|
|
345
|
-
readonly description: "Total attributed
|
|
345
|
+
readonly description: "Total attributed conversion events on the day.";
|
|
346
346
|
}, {
|
|
347
347
|
readonly name: "conversion_value";
|
|
348
|
-
readonly description: "Total attributed
|
|
348
|
+
readonly description: "Total attributed conversion value (account currency).";
|
|
349
349
|
}];
|
|
350
350
|
readonly responses: {
|
|
351
351
|
readonly campaign_insights: z.ZodArray<z.ZodObject<{
|
|
@@ -357,11 +357,11 @@ declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCre
|
|
|
357
357
|
clicks: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
358
358
|
spend: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
359
359
|
reach: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
360
|
-
|
|
360
|
+
conversions: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
361
361
|
action_type: z.ZodOptional<z.ZodString>;
|
|
362
362
|
value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
363
363
|
}, z.core.$strip>>>;
|
|
364
|
-
|
|
364
|
+
conversion_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
365
365
|
action_type: z.ZodOptional<z.ZodString>;
|
|
366
366
|
value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
367
367
|
}, z.core.$strip>>>;
|
|
@@ -402,10 +402,10 @@ declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCre
|
|
|
402
402
|
readonly description: "Unique reach on the day.";
|
|
403
403
|
}, {
|
|
404
404
|
readonly name: "conversions";
|
|
405
|
-
readonly description: "Total attributed
|
|
405
|
+
readonly description: "Total attributed conversion events on the day.";
|
|
406
406
|
}, {
|
|
407
407
|
readonly name: "conversion_value";
|
|
408
|
-
readonly description: "Total attributed
|
|
408
|
+
readonly description: "Total attributed conversion value (account currency).";
|
|
409
409
|
}];
|
|
410
410
|
readonly responses: {
|
|
411
411
|
readonly adset_insights: z.ZodArray<z.ZodObject<{
|
|
@@ -419,11 +419,11 @@ declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCre
|
|
|
419
419
|
clicks: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
420
420
|
spend: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
421
421
|
reach: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
422
|
-
|
|
422
|
+
conversions: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
423
423
|
action_type: z.ZodOptional<z.ZodString>;
|
|
424
424
|
value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
425
425
|
}, z.core.$strip>>>;
|
|
426
|
-
|
|
426
|
+
conversion_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
427
427
|
action_type: z.ZodOptional<z.ZodString>;
|
|
428
428
|
value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
429
429
|
}, z.core.$strip>>>;
|
|
@@ -470,10 +470,10 @@ declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCre
|
|
|
470
470
|
readonly description: "Unique reach on the day.";
|
|
471
471
|
}, {
|
|
472
472
|
readonly name: "conversions";
|
|
473
|
-
readonly description: "Total attributed
|
|
473
|
+
readonly description: "Total attributed conversion events on the day.";
|
|
474
474
|
}, {
|
|
475
475
|
readonly name: "conversion_value";
|
|
476
|
-
readonly description: "Total attributed
|
|
476
|
+
readonly description: "Total attributed conversion value (account currency).";
|
|
477
477
|
}];
|
|
478
478
|
readonly responses: {
|
|
479
479
|
readonly ad_insights: z.ZodArray<z.ZodObject<{
|
|
@@ -489,11 +489,11 @@ declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCre
|
|
|
489
489
|
clicks: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
490
490
|
spend: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
491
491
|
reach: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
492
|
-
|
|
492
|
+
conversions: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
493
493
|
action_type: z.ZodOptional<z.ZodString>;
|
|
494
494
|
value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
495
495
|
}, z.core.$strip>>>;
|
|
496
|
-
|
|
496
|
+
conversion_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
497
497
|
action_type: z.ZodOptional<z.ZodString>;
|
|
498
498
|
value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
499
499
|
}, z.core.$strip>>>;
|
|
@@ -523,11 +523,11 @@ declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCre
|
|
|
523
523
|
clicks: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
524
524
|
spend: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
525
525
|
reach: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
526
|
-
|
|
526
|
+
conversions: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
527
527
|
action_type: z.ZodOptional<z.ZodString>;
|
|
528
528
|
value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
529
529
|
}, z.core.$strip>>>;
|
|
530
|
-
|
|
530
|
+
conversion_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
531
531
|
action_type: z.ZodOptional<z.ZodString>;
|
|
532
532
|
value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
533
533
|
}, z.core.$strip>>>;
|
|
@@ -544,11 +544,11 @@ declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCre
|
|
|
544
544
|
clicks: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
545
545
|
spend: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
546
546
|
reach: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
547
|
-
|
|
547
|
+
conversions: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
548
548
|
action_type: z.ZodOptional<z.ZodString>;
|
|
549
549
|
value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
550
550
|
}, z.core.$strip>>>;
|
|
551
|
-
|
|
551
|
+
conversion_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
552
552
|
action_type: z.ZodOptional<z.ZodString>;
|
|
553
553
|
value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
554
554
|
}, z.core.$strip>>>;
|
|
@@ -567,11 +567,11 @@ declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCre
|
|
|
567
567
|
clicks: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
568
568
|
spend: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
569
569
|
reach: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
570
|
-
|
|
570
|
+
conversions: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
571
571
|
action_type: z.ZodOptional<z.ZodString>;
|
|
572
572
|
value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
573
573
|
}, z.core.$strip>>>;
|
|
574
|
-
|
|
574
|
+
conversion_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
575
575
|
action_type: z.ZodOptional<z.ZodString>;
|
|
576
576
|
value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
577
577
|
}, z.core.$strip>>>;
|
package/dist/index.js
CHANGED
|
@@ -34,10 +34,10 @@ var configFields = defineConfigFields(
|
|
|
34
34
|
placeholder: "EAAB...",
|
|
35
35
|
secret: true
|
|
36
36
|
}),
|
|
37
|
-
apiVersion: z.string().regex(/^v\d+\.\d+$/, "API version must look like `
|
|
37
|
+
apiVersion: z.string().regex(/^v\d+\.\d+$/, "API version must look like `v25.0`").optional().meta({
|
|
38
38
|
label: "Graph API version",
|
|
39
|
-
description: "Pin a specific Meta Graph API version (e.g. `
|
|
40
|
-
placeholder: "
|
|
39
|
+
description: "Pin a specific Meta Graph API version (e.g. `v25.0`). Defaults to `v25.0`. Meta deprecates Marketing API versions on a rolling schedule, so older pins eventually stop working.",
|
|
40
|
+
placeholder: "v25.0"
|
|
41
41
|
}),
|
|
42
42
|
lookbackDays: z.number().int().positive().optional().meta({
|
|
43
43
|
label: "Lookback days (full sync)",
|
|
@@ -97,7 +97,7 @@ var PHASE_ORDER = [
|
|
|
97
97
|
"ad_insights"
|
|
98
98
|
];
|
|
99
99
|
var isMetaAdsSyncCursor = makeChunkedCursorGuard(PHASE_ORDER);
|
|
100
|
-
var DEFAULT_API_VERSION = "
|
|
100
|
+
var DEFAULT_API_VERSION = "v25.0";
|
|
101
101
|
var BASE_URL = "https://graph.facebook.com";
|
|
102
102
|
var PAGE_LIMIT = 100;
|
|
103
103
|
var MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
@@ -207,8 +207,8 @@ var insightsBaseShape = {
|
|
|
207
207
|
clicks: z.union([z.string(), z.number()]).optional(),
|
|
208
208
|
spend: z.union([z.string(), z.number()]).optional(),
|
|
209
209
|
reach: z.union([z.string(), z.number()]).optional(),
|
|
210
|
-
|
|
211
|
-
|
|
210
|
+
conversions: z.array(actionEntrySchema).optional(),
|
|
211
|
+
conversion_values: z.array(actionEntrySchema).optional()
|
|
212
212
|
};
|
|
213
213
|
var campaignInsightSchema = z.object({
|
|
214
214
|
...insightsBaseShape,
|
|
@@ -262,7 +262,7 @@ var metaAdsResources = defineResources({
|
|
|
262
262
|
endpoint: "GET /{ad_account_id}/insights?level=campaign&time_increment=1",
|
|
263
263
|
unit: "USD",
|
|
264
264
|
granularity: "day",
|
|
265
|
-
notes: "Primary value is `spend`. `conversions` is the sum of
|
|
265
|
+
notes: "Primary value is `spend`. `conversions` is the sum of the upstream `conversions` field (conversion events only, default attribution window); `conversion_value` is the sum of the upstream `conversion_values` field.",
|
|
266
266
|
dimensions: [
|
|
267
267
|
{ name: "date", description: "Calendar day of the metric sample (UTC)." },
|
|
268
268
|
{ name: "campaignId", description: "Meta campaign id." },
|
|
@@ -272,10 +272,13 @@ var metaAdsResources = defineResources({
|
|
|
272
272
|
{ name: "impressions", description: "Total impressions on the day." },
|
|
273
273
|
{ name: "clicks", description: "Total clicks on the day." },
|
|
274
274
|
{ name: "reach", description: "Unique reach on the day." },
|
|
275
|
-
{
|
|
275
|
+
{
|
|
276
|
+
name: "conversions",
|
|
277
|
+
description: "Total attributed conversion events on the day."
|
|
278
|
+
},
|
|
276
279
|
{
|
|
277
280
|
name: "conversion_value",
|
|
278
|
-
description: "Total attributed
|
|
281
|
+
description: "Total attributed conversion value (account currency)."
|
|
279
282
|
}
|
|
280
283
|
],
|
|
281
284
|
responses: { campaign_insights: campaignInsightsSchema }
|
|
@@ -298,10 +301,13 @@ var metaAdsResources = defineResources({
|
|
|
298
301
|
{ name: "impressions", description: "Total impressions on the day." },
|
|
299
302
|
{ name: "clicks", description: "Total clicks on the day." },
|
|
300
303
|
{ name: "reach", description: "Unique reach on the day." },
|
|
301
|
-
{
|
|
304
|
+
{
|
|
305
|
+
name: "conversions",
|
|
306
|
+
description: "Total attributed conversion events on the day."
|
|
307
|
+
},
|
|
302
308
|
{
|
|
303
309
|
name: "conversion_value",
|
|
304
|
-
description: "Total attributed
|
|
310
|
+
description: "Total attributed conversion value (account currency)."
|
|
305
311
|
}
|
|
306
312
|
],
|
|
307
313
|
responses: { adset_insights: adsetInsightsSchema }
|
|
@@ -326,10 +332,13 @@ var metaAdsResources = defineResources({
|
|
|
326
332
|
{ name: "impressions", description: "Total impressions on the day." },
|
|
327
333
|
{ name: "clicks", description: "Total clicks on the day." },
|
|
328
334
|
{ name: "reach", description: "Unique reach on the day." },
|
|
329
|
-
{
|
|
335
|
+
{
|
|
336
|
+
name: "conversions",
|
|
337
|
+
description: "Total attributed conversion events on the day."
|
|
338
|
+
},
|
|
330
339
|
{
|
|
331
340
|
name: "conversion_value",
|
|
332
|
-
description: "Total attributed
|
|
341
|
+
description: "Total attributed conversion value (account currency)."
|
|
333
342
|
}
|
|
334
343
|
],
|
|
335
344
|
responses: { ad_insights: adInsightsSchema }
|
|
@@ -356,8 +365,8 @@ function buildInsightAttributes(row) {
|
|
|
356
365
|
const impressions = parseNumber(row.impressions);
|
|
357
366
|
const clicks = parseNumber(row.clicks);
|
|
358
367
|
const reach = parseNumber(row.reach);
|
|
359
|
-
const conversions =
|
|
360
|
-
const conversionValue = sumActionValues(row.
|
|
368
|
+
const conversions = sumActionValues(row.conversions);
|
|
369
|
+
const conversionValue = sumActionValues(row.conversion_values);
|
|
361
370
|
return {
|
|
362
371
|
date: row.date_start,
|
|
363
372
|
impressions,
|
|
@@ -427,8 +436,8 @@ var COMMON_INSIGHT_FIELDS = [
|
|
|
427
436
|
"clicks",
|
|
428
437
|
"spend",
|
|
429
438
|
"reach",
|
|
430
|
-
"
|
|
431
|
-
"
|
|
439
|
+
"conversions",
|
|
440
|
+
"conversion_values"
|
|
432
441
|
];
|
|
433
442
|
var INSIGHT_FIELDS = {
|
|
434
443
|
campaign_insights: COMMON_INSIGHT_FIELDS.join(","),
|
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 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"]}
|
|
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(\n res: Response,\n parseJson: boolean,\n binary: boolean,\n): Promise<unknown> {\n if (res.status === 204 || res.status === 205) {\n return null;\n }\n if (binary) {\n return new Uint8Array(await res.arrayBuffer());\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 const binary = req.binary ?? false;\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, binary);\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 `v25.0`')\n .optional()\n .meta({\n label: 'Graph API version',\n description:\n 'Pin a specific Meta Graph API version (e.g. `v25.0`). Defaults to `v25.0`. Meta deprecates Marketing API versions on a rolling schedule, so older pins eventually stop working.',\n placeholder: 'v25.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 = 'v25.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 conversions: z.array(actionEntrySchema).optional(),\n conversion_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 the upstream `conversions` field (conversion events only, default attribution window); `conversion_value` is the sum of the upstream `conversion_values` field.',\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 {\n name: 'conversions',\n description: 'Total attributed conversion events on the day.',\n },\n {\n name: 'conversion_value',\n description: 'Total attributed conversion 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 {\n name: 'conversions',\n description: 'Total attributed conversion events on the day.',\n },\n {\n name: 'conversion_value',\n description: 'Total attributed conversion 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 {\n name: 'conversions',\n description: 'Total attributed conversion events on the day.',\n },\n {\n name: 'conversion_value',\n description: 'Total attributed conversion 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 conversions?: MetaActionEntry[];\n conversion_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 = sumActionValues(row.conversions);\n const conversionValue = sumActionValues(row.conversion_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 'conversions',\n 'conversion_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.28.
|
|
3
|
+
"version": "0.28.2",
|
|
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,7 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"zod": "^4.4.3",
|
|
27
|
-
"@rawdash/core": "0.28.
|
|
27
|
+
"@rawdash/core": "0.28.2"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"fast-check": "^4.8.0",
|