@rawdash/connector-meta-ads 0.27.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 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. `v21.0`). Defaults to `v21.0`. |
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
 
@@ -40,19 +40,22 @@ A long-lived System User access token from Meta Business Manager, scoped with `a
40
40
  - Endpoint: `GET /{ad_account_id}/insights?level=campaign&time_increment=1`
41
41
  - Unit: USD
42
42
  - Granularity: day
43
- - Dimensions: `date`, `campaignId`, `campaignName`, `impressions`, `clicks`, `spend`, `reach`, `conversions`, `conversion_value`
44
- - 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`.
43
+ - Dimensions: `date`, `campaignId`, `campaignName`
44
+ - Measures: `impressions`, `clicks`, `reach`, `conversions`, `conversion_value`
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.
45
46
  - **`meta_adset_insights`** _(metric)_ - Daily adset-level Meta Ads insights - same fields as the campaign roll-up, bucketed by adset.
46
47
  - Endpoint: `GET /{ad_account_id}/insights?level=adset&time_increment=1`
47
48
  - Unit: USD
48
49
  - Granularity: day
49
- - Dimensions: `date`, `campaignId`, `campaignName`, `adsetId`, `adsetName`, `impressions`, `clicks`, `spend`, `reach`, `conversions`, `conversion_value`
50
+ - Dimensions: `date`, `campaignId`, `campaignName`, `adsetId`, `adsetName`
51
+ - Measures: `impressions`, `clicks`, `reach`, `conversions`, `conversion_value`
50
52
  - Primary value is `spend`. Includes campaign_id/campaign_name so adset rows are easy to roll up to their parent campaign.
51
53
  - **`meta_ad_insights`** _(metric)_ - Daily ad-level Meta Ads insights - same fields as the adset roll-up, bucketed by ad.
52
54
  - Endpoint: `GET /{ad_account_id}/insights?level=ad&time_increment=1`
53
55
  - Unit: USD
54
56
  - Granularity: day
55
- - Dimensions: `date`, `campaignId`, `campaignName`, `adsetId`, `adsetName`, `adId`, `adName`, `impressions`, `clicks`, `spend`, `reach`, `conversions`, `conversion_value`
57
+ - Dimensions: `date`, `campaignId`, `campaignName`, `adsetId`, `adsetName`, `adId`, `adName`
58
+ - Measures: `impressions`, `clicks`, `reach`, `conversions`, `conversion_value`
56
59
  - Primary value is `spend`. Cardinality is the highest of the three insights resources - opt in via `resources: [..., "ad_insights"]` only when you need per-ad breakdowns.
57
60
 
58
61
  ## Example
@@ -88,7 +91,7 @@ export default defineConfig({
88
91
  connector: metaAds,
89
92
  shape: 'metric',
90
93
  name: 'meta_campaign_insights',
91
- field: 'spend',
94
+ field: 'value',
92
95
  fn: 'sum',
93
96
  }),
94
97
  },
@@ -100,7 +103,7 @@ export default defineConfig({
100
103
  connector: metaAds,
101
104
  shape: 'metric',
102
105
  name: 'meta_campaign_insights',
103
- field: 'spend',
106
+ field: 'value',
104
107
  fn: 'sum',
105
108
  }),
106
109
  },
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { BaseConnector, ConnectorContext, SyncOptions, StorageHandle, SyncResult, ConnectorDoc } from '@rawdash/core';
1
+ import { BaseConnector, ConnectorContext, SyncOptions, StorageHandle, SyncResult, ConnectorDoc, MetricSample } from '@rawdash/core';
2
2
  import { z } from 'zod';
3
3
 
4
4
  declare const configFields: z.ZodObject<{
@@ -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 every entry in the upstream `actions` array; `conversion_value` is the sum of every entry in `action_values`.";
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).";
@@ -72,24 +72,22 @@ declare const metaAdsResources: {
72
72
  }, {
73
73
  readonly name: "campaignName";
74
74
  readonly description: "Meta campaign name.";
75
- }, {
75
+ }];
76
+ readonly measures: [{
76
77
  readonly name: "impressions";
77
78
  readonly description: "Total impressions on the day.";
78
79
  }, {
79
80
  readonly name: "clicks";
80
81
  readonly description: "Total clicks on the day.";
81
- }, {
82
- readonly name: "spend";
83
- readonly description: "Total spend (account currency).";
84
82
  }, {
85
83
  readonly name: "reach";
86
84
  readonly description: "Unique reach on the day.";
87
85
  }, {
88
86
  readonly name: "conversions";
89
- readonly description: "Total attributed actions.";
87
+ readonly description: "Total attributed conversion events on the day.";
90
88
  }, {
91
89
  readonly name: "conversion_value";
92
- readonly description: "Total attributed action value (account currency).";
90
+ readonly description: "Total attributed conversion value (account currency).";
93
91
  }];
94
92
  readonly responses: {
95
93
  readonly campaign_insights: z.ZodArray<z.ZodObject<{
@@ -101,11 +99,11 @@ declare const metaAdsResources: {
101
99
  clicks: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
102
100
  spend: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
103
101
  reach: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
104
- actions: z.ZodOptional<z.ZodArray<z.ZodObject<{
102
+ conversions: z.ZodOptional<z.ZodArray<z.ZodObject<{
105
103
  action_type: z.ZodOptional<z.ZodString>;
106
104
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
107
105
  }, z.core.$strip>>>;
108
- action_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
106
+ conversion_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
109
107
  action_type: z.ZodOptional<z.ZodString>;
110
108
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
111
109
  }, z.core.$strip>>>;
@@ -134,24 +132,22 @@ declare const metaAdsResources: {
134
132
  }, {
135
133
  readonly name: "adsetName";
136
134
  readonly description: "Meta adset name.";
137
- }, {
135
+ }];
136
+ readonly measures: [{
138
137
  readonly name: "impressions";
139
138
  readonly description: "Total impressions on the day.";
140
139
  }, {
141
140
  readonly name: "clicks";
142
141
  readonly description: "Total clicks on the day.";
143
- }, {
144
- readonly name: "spend";
145
- readonly description: "Total spend (account currency).";
146
142
  }, {
147
143
  readonly name: "reach";
148
144
  readonly description: "Unique reach on the day.";
149
145
  }, {
150
146
  readonly name: "conversions";
151
- readonly description: "Total attributed actions.";
147
+ readonly description: "Total attributed conversion events on the day.";
152
148
  }, {
153
149
  readonly name: "conversion_value";
154
- readonly description: "Total attributed action value (account currency).";
150
+ readonly description: "Total attributed conversion value (account currency).";
155
151
  }];
156
152
  readonly responses: {
157
153
  readonly adset_insights: z.ZodArray<z.ZodObject<{
@@ -165,11 +161,11 @@ declare const metaAdsResources: {
165
161
  clicks: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
166
162
  spend: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
167
163
  reach: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
168
- actions: z.ZodOptional<z.ZodArray<z.ZodObject<{
164
+ conversions: z.ZodOptional<z.ZodArray<z.ZodObject<{
169
165
  action_type: z.ZodOptional<z.ZodString>;
170
166
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
171
167
  }, z.core.$strip>>>;
172
- action_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
168
+ conversion_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
173
169
  action_type: z.ZodOptional<z.ZodString>;
174
170
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
175
171
  }, z.core.$strip>>>;
@@ -204,24 +200,22 @@ declare const metaAdsResources: {
204
200
  }, {
205
201
  readonly name: "adName";
206
202
  readonly description: "Meta ad name.";
207
- }, {
203
+ }];
204
+ readonly measures: [{
208
205
  readonly name: "impressions";
209
206
  readonly description: "Total impressions on the day.";
210
207
  }, {
211
208
  readonly name: "clicks";
212
209
  readonly description: "Total clicks on the day.";
213
- }, {
214
- readonly name: "spend";
215
- readonly description: "Total spend (account currency).";
216
210
  }, {
217
211
  readonly name: "reach";
218
212
  readonly description: "Unique reach on the day.";
219
213
  }, {
220
214
  readonly name: "conversions";
221
- readonly description: "Total attributed actions.";
215
+ readonly description: "Total attributed conversion events on the day.";
222
216
  }, {
223
217
  readonly name: "conversion_value";
224
- readonly description: "Total attributed action value (account currency).";
218
+ readonly description: "Total attributed conversion value (account currency).";
225
219
  }];
226
220
  readonly responses: {
227
221
  readonly ad_insights: z.ZodArray<z.ZodObject<{
@@ -237,11 +231,11 @@ declare const metaAdsResources: {
237
231
  clicks: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
238
232
  spend: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
239
233
  reach: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
240
- actions: z.ZodOptional<z.ZodArray<z.ZodObject<{
234
+ conversions: z.ZodOptional<z.ZodArray<z.ZodObject<{
241
235
  action_type: z.ZodOptional<z.ZodString>;
242
236
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
243
237
  }, z.core.$strip>>>;
244
- action_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
238
+ conversion_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
245
239
  action_type: z.ZodOptional<z.ZodString>;
246
240
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
247
241
  }, z.core.$strip>>>;
@@ -271,8 +265,8 @@ interface MetaInsightsBase {
271
265
  clicks?: string | number;
272
266
  spend?: string | number;
273
267
  reach?: string | number;
274
- actions?: MetaActionEntry[];
275
- action_values?: MetaActionEntry[];
268
+ conversions?: MetaActionEntry[];
269
+ conversion_values?: MetaActionEntry[];
276
270
  }
277
271
  interface MetaCampaignInsight extends MetaInsightsBase {
278
272
  campaign_id: string;
@@ -292,12 +286,7 @@ declare function campaignToEntity(row: MetaCampaign): {
292
286
  attributes: Record<string, string | number | null>;
293
287
  updated_at: number;
294
288
  };
295
- declare function insightRowToMetricSample(row: MetaCampaignInsight | MetaAdsetInsight | MetaAdInsight, phase: 'campaign_insights' | 'adset_insights' | 'ad_insights'): {
296
- name: string;
297
- ts: number;
298
- value: number;
299
- attributes: Record<string, string | number | null>;
300
- };
289
+ declare function insightRowToMetricSample(row: MetaCampaignInsight | MetaAdsetInsight | MetaAdInsight, phase: 'campaign_insights' | 'adset_insights' | 'ad_insights'): MetricSample;
301
290
  declare const id = "meta-ads";
302
291
  declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCredentials> {
303
292
  static readonly id = "meta-ads";
@@ -331,7 +320,7 @@ declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCre
331
320
  readonly endpoint: "GET /{ad_account_id}/insights?level=campaign&time_increment=1";
332
321
  readonly unit: "USD";
333
322
  readonly granularity: "day";
334
- readonly notes: "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`.";
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.";
335
324
  readonly dimensions: [{
336
325
  readonly name: "date";
337
326
  readonly description: "Calendar day of the metric sample (UTC).";
@@ -341,24 +330,22 @@ declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCre
341
330
  }, {
342
331
  readonly name: "campaignName";
343
332
  readonly description: "Meta campaign name.";
344
- }, {
333
+ }];
334
+ readonly measures: [{
345
335
  readonly name: "impressions";
346
336
  readonly description: "Total impressions on the day.";
347
337
  }, {
348
338
  readonly name: "clicks";
349
339
  readonly description: "Total clicks on the day.";
350
- }, {
351
- readonly name: "spend";
352
- readonly description: "Total spend (account currency).";
353
340
  }, {
354
341
  readonly name: "reach";
355
342
  readonly description: "Unique reach on the day.";
356
343
  }, {
357
344
  readonly name: "conversions";
358
- readonly description: "Total attributed actions.";
345
+ readonly description: "Total attributed conversion events on the day.";
359
346
  }, {
360
347
  readonly name: "conversion_value";
361
- readonly description: "Total attributed action value (account currency).";
348
+ readonly description: "Total attributed conversion value (account currency).";
362
349
  }];
363
350
  readonly responses: {
364
351
  readonly campaign_insights: z.ZodArray<z.ZodObject<{
@@ -370,11 +357,11 @@ declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCre
370
357
  clicks: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
371
358
  spend: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
372
359
  reach: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
373
- actions: z.ZodOptional<z.ZodArray<z.ZodObject<{
360
+ conversions: z.ZodOptional<z.ZodArray<z.ZodObject<{
374
361
  action_type: z.ZodOptional<z.ZodString>;
375
362
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
376
363
  }, z.core.$strip>>>;
377
- action_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
364
+ conversion_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
378
365
  action_type: z.ZodOptional<z.ZodString>;
379
366
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
380
367
  }, z.core.$strip>>>;
@@ -403,24 +390,22 @@ declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCre
403
390
  }, {
404
391
  readonly name: "adsetName";
405
392
  readonly description: "Meta adset name.";
406
- }, {
393
+ }];
394
+ readonly measures: [{
407
395
  readonly name: "impressions";
408
396
  readonly description: "Total impressions on the day.";
409
397
  }, {
410
398
  readonly name: "clicks";
411
399
  readonly description: "Total clicks on the day.";
412
- }, {
413
- readonly name: "spend";
414
- readonly description: "Total spend (account currency).";
415
400
  }, {
416
401
  readonly name: "reach";
417
402
  readonly description: "Unique reach on the day.";
418
403
  }, {
419
404
  readonly name: "conversions";
420
- readonly description: "Total attributed actions.";
405
+ readonly description: "Total attributed conversion events on the day.";
421
406
  }, {
422
407
  readonly name: "conversion_value";
423
- readonly description: "Total attributed action value (account currency).";
408
+ readonly description: "Total attributed conversion value (account currency).";
424
409
  }];
425
410
  readonly responses: {
426
411
  readonly adset_insights: z.ZodArray<z.ZodObject<{
@@ -434,11 +419,11 @@ declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCre
434
419
  clicks: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
435
420
  spend: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
436
421
  reach: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
437
- actions: z.ZodOptional<z.ZodArray<z.ZodObject<{
422
+ conversions: z.ZodOptional<z.ZodArray<z.ZodObject<{
438
423
  action_type: z.ZodOptional<z.ZodString>;
439
424
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
440
425
  }, z.core.$strip>>>;
441
- action_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
426
+ conversion_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
442
427
  action_type: z.ZodOptional<z.ZodString>;
443
428
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
444
429
  }, z.core.$strip>>>;
@@ -473,24 +458,22 @@ declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCre
473
458
  }, {
474
459
  readonly name: "adName";
475
460
  readonly description: "Meta ad name.";
476
- }, {
461
+ }];
462
+ readonly measures: [{
477
463
  readonly name: "impressions";
478
464
  readonly description: "Total impressions on the day.";
479
465
  }, {
480
466
  readonly name: "clicks";
481
467
  readonly description: "Total clicks on the day.";
482
- }, {
483
- readonly name: "spend";
484
- readonly description: "Total spend (account currency).";
485
468
  }, {
486
469
  readonly name: "reach";
487
470
  readonly description: "Unique reach on the day.";
488
471
  }, {
489
472
  readonly name: "conversions";
490
- readonly description: "Total attributed actions.";
473
+ readonly description: "Total attributed conversion events on the day.";
491
474
  }, {
492
475
  readonly name: "conversion_value";
493
- readonly description: "Total attributed action value (account currency).";
476
+ readonly description: "Total attributed conversion value (account currency).";
494
477
  }];
495
478
  readonly responses: {
496
479
  readonly ad_insights: z.ZodArray<z.ZodObject<{
@@ -506,11 +489,11 @@ declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCre
506
489
  clicks: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
507
490
  spend: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
508
491
  reach: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
509
- actions: z.ZodOptional<z.ZodArray<z.ZodObject<{
492
+ conversions: z.ZodOptional<z.ZodArray<z.ZodObject<{
510
493
  action_type: z.ZodOptional<z.ZodString>;
511
494
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
512
495
  }, z.core.$strip>>>;
513
- action_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
496
+ conversion_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
514
497
  action_type: z.ZodOptional<z.ZodString>;
515
498
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
516
499
  }, z.core.$strip>>>;
@@ -540,11 +523,11 @@ declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCre
540
523
  clicks: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
541
524
  spend: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
542
525
  reach: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
543
- actions: z.ZodOptional<z.ZodArray<z.ZodObject<{
526
+ conversions: z.ZodOptional<z.ZodArray<z.ZodObject<{
544
527
  action_type: z.ZodOptional<z.ZodString>;
545
528
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
546
529
  }, z.core.$strip>>>;
547
- action_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
530
+ conversion_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
548
531
  action_type: z.ZodOptional<z.ZodString>;
549
532
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
550
533
  }, z.core.$strip>>>;
@@ -561,11 +544,11 @@ declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCre
561
544
  clicks: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
562
545
  spend: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
563
546
  reach: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
564
- actions: z.ZodOptional<z.ZodArray<z.ZodObject<{
547
+ conversions: z.ZodOptional<z.ZodArray<z.ZodObject<{
565
548
  action_type: z.ZodOptional<z.ZodString>;
566
549
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
567
550
  }, z.core.$strip>>>;
568
- action_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
551
+ conversion_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
569
552
  action_type: z.ZodOptional<z.ZodString>;
570
553
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
571
554
  }, z.core.$strip>>>;
@@ -584,11 +567,11 @@ declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCre
584
567
  clicks: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
585
568
  spend: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
586
569
  reach: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
587
- actions: z.ZodOptional<z.ZodArray<z.ZodObject<{
570
+ conversions: z.ZodOptional<z.ZodArray<z.ZodObject<{
588
571
  action_type: z.ZodOptional<z.ZodString>;
589
572
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
590
573
  }, z.core.$strip>>>;
591
- action_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
574
+ conversion_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
592
575
  action_type: z.ZodOptional<z.ZodString>;
593
576
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
594
577
  }, z.core.$strip>>>;
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ import {
12
12
  defineConnectorDoc,
13
13
  defineResources,
14
14
  makeChunkedCursorGuard,
15
+ metricSample,
15
16
  paginateChunked,
16
17
  schemasFromResources,
17
18
  selectActivePhases
@@ -33,10 +34,10 @@ var configFields = defineConfigFields(
33
34
  placeholder: "EAAB...",
34
35
  secret: true
35
36
  }),
36
- apiVersion: z.string().regex(/^v\d+\.\d+$/, "API version must look like `v21.0`").optional().meta({
37
+ apiVersion: z.string().regex(/^v\d+\.\d+$/, "API version must look like `v25.0`").optional().meta({
37
38
  label: "Graph API version",
38
- description: "Pin a specific Meta Graph API version (e.g. `v21.0`). Defaults to `v21.0`.",
39
- placeholder: "v21.0"
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"
40
41
  }),
41
42
  lookbackDays: z.number().int().positive().optional().meta({
42
43
  label: "Lookback days (full sync)",
@@ -96,7 +97,7 @@ var PHASE_ORDER = [
96
97
  "ad_insights"
97
98
  ];
98
99
  var isMetaAdsSyncCursor = makeChunkedCursorGuard(PHASE_ORDER);
99
- var DEFAULT_API_VERSION = "v21.0";
100
+ var DEFAULT_API_VERSION = "v25.0";
100
101
  var BASE_URL = "https://graph.facebook.com";
101
102
  var PAGE_LIMIT = 100;
102
103
  var MS_PER_DAY = 24 * 60 * 60 * 1e3;
@@ -206,8 +207,8 @@ var insightsBaseShape = {
206
207
  clicks: z.union([z.string(), z.number()]).optional(),
207
208
  spend: z.union([z.string(), z.number()]).optional(),
208
209
  reach: z.union([z.string(), z.number()]).optional(),
209
- actions: z.array(actionEntrySchema).optional(),
210
- action_values: z.array(actionEntrySchema).optional()
210
+ conversions: z.array(actionEntrySchema).optional(),
211
+ conversion_values: z.array(actionEntrySchema).optional()
211
212
  };
212
213
  var campaignInsightSchema = z.object({
213
214
  ...insightsBaseShape,
@@ -261,19 +262,23 @@ var metaAdsResources = defineResources({
261
262
  endpoint: "GET /{ad_account_id}/insights?level=campaign&time_increment=1",
262
263
  unit: "USD",
263
264
  granularity: "day",
264
- notes: "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`.",
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.",
265
266
  dimensions: [
266
267
  { name: "date", description: "Calendar day of the metric sample (UTC)." },
267
268
  { name: "campaignId", description: "Meta campaign id." },
268
- { name: "campaignName", description: "Meta campaign name." },
269
+ { name: "campaignName", description: "Meta campaign name." }
270
+ ],
271
+ measures: [
269
272
  { name: "impressions", description: "Total impressions on the day." },
270
273
  { name: "clicks", description: "Total clicks on the day." },
271
- { name: "spend", description: "Total spend (account currency)." },
272
274
  { name: "reach", description: "Unique reach on the day." },
273
- { name: "conversions", description: "Total attributed actions." },
275
+ {
276
+ name: "conversions",
277
+ description: "Total attributed conversion events on the day."
278
+ },
274
279
  {
275
280
  name: "conversion_value",
276
- description: "Total attributed action value (account currency)."
281
+ description: "Total attributed conversion value (account currency)."
277
282
  }
278
283
  ],
279
284
  responses: { campaign_insights: campaignInsightsSchema }
@@ -290,15 +295,19 @@ var metaAdsResources = defineResources({
290
295
  { name: "campaignId", description: "Parent campaign id." },
291
296
  { name: "campaignName", description: "Parent campaign name." },
292
297
  { name: "adsetId", description: "Meta adset id." },
293
- { name: "adsetName", description: "Meta adset name." },
298
+ { name: "adsetName", description: "Meta adset name." }
299
+ ],
300
+ measures: [
294
301
  { name: "impressions", description: "Total impressions on the day." },
295
302
  { name: "clicks", description: "Total clicks on the day." },
296
- { name: "spend", description: "Total spend (account currency)." },
297
303
  { name: "reach", description: "Unique reach on the day." },
298
- { name: "conversions", description: "Total attributed actions." },
304
+ {
305
+ name: "conversions",
306
+ description: "Total attributed conversion events on the day."
307
+ },
299
308
  {
300
309
  name: "conversion_value",
301
- description: "Total attributed action value (account currency)."
310
+ description: "Total attributed conversion value (account currency)."
302
311
  }
303
312
  ],
304
313
  responses: { adset_insights: adsetInsightsSchema }
@@ -317,15 +326,19 @@ var metaAdsResources = defineResources({
317
326
  { name: "adsetId", description: "Parent adset id." },
318
327
  { name: "adsetName", description: "Parent adset name." },
319
328
  { name: "adId", description: "Meta ad id." },
320
- { name: "adName", description: "Meta ad name." },
329
+ { name: "adName", description: "Meta ad name." }
330
+ ],
331
+ measures: [
321
332
  { name: "impressions", description: "Total impressions on the day." },
322
333
  { name: "clicks", description: "Total clicks on the day." },
323
- { name: "spend", description: "Total spend (account currency)." },
324
334
  { name: "reach", description: "Unique reach on the day." },
325
- { name: "conversions", description: "Total attributed actions." },
335
+ {
336
+ name: "conversions",
337
+ description: "Total attributed conversion events on the day."
338
+ },
326
339
  {
327
340
  name: "conversion_value",
328
- description: "Total attributed action value (account currency)."
341
+ description: "Total attributed conversion value (account currency)."
329
342
  }
330
343
  ],
331
344
  responses: { ad_insights: adInsightsSchema }
@@ -351,40 +364,57 @@ function campaignToEntity(row) {
351
364
  function buildInsightAttributes(row) {
352
365
  const impressions = parseNumber(row.impressions);
353
366
  const clicks = parseNumber(row.clicks);
354
- const spend = parseNumber(row.spend);
355
367
  const reach = parseNumber(row.reach);
356
- const conversions = (row.actions ?? []).length ? sumActionValues(row.actions) : 0;
357
- const conversionValue = sumActionValues(row.action_values);
368
+ const conversions = sumActionValues(row.conversions);
369
+ const conversionValue = sumActionValues(row.conversion_values);
358
370
  return {
359
371
  date: row.date_start,
360
372
  impressions,
361
373
  clicks,
362
- spend,
363
374
  reach,
364
375
  conversions,
365
376
  conversion_value: conversionValue
366
377
  };
367
378
  }
368
379
  function insightRowToMetricSample(row, phase) {
369
- const attributes = buildInsightAttributes(row);
370
- attributes["campaignId"] = row.campaign_id;
371
- attributes["campaignName"] = row.campaign_name ?? null;
372
- if (phase !== "campaign_insights") {
373
- const ar = row;
374
- attributes["adsetId"] = ar.adset_id;
375
- attributes["adsetName"] = ar.adset_name ?? null;
376
- }
377
- if (phase === "ad_insights") {
378
- const ar = row;
379
- attributes["adId"] = ar.ad_id;
380
- attributes["adName"] = ar.ad_name ?? null;
380
+ const base = buildInsightAttributes(row);
381
+ const ts = metaDateToMs(row.date_start);
382
+ const value = parseNumber(row.spend);
383
+ const campaignAttributes = {
384
+ ...base,
385
+ campaignId: row.campaign_id,
386
+ campaignName: row.campaign_name ?? null
387
+ };
388
+ if (phase === "campaign_insights") {
389
+ return metricSample(metaAdsResources, "meta_campaign_insights", {
390
+ ts,
391
+ value,
392
+ attributes: campaignAttributes
393
+ });
381
394
  }
382
- return {
383
- name: METRIC_NAME[phase],
384
- ts: metaDateToMs(row.date_start),
385
- value: parseNumber(row.spend),
386
- attributes
395
+ const adsetRow = row;
396
+ const adsetAttributes = {
397
+ ...campaignAttributes,
398
+ adsetId: adsetRow.adset_id,
399
+ adsetName: adsetRow.adset_name ?? null
387
400
  };
401
+ if (phase === "adset_insights") {
402
+ return metricSample(metaAdsResources, "meta_adset_insights", {
403
+ ts,
404
+ value,
405
+ attributes: adsetAttributes
406
+ });
407
+ }
408
+ const adRow = row;
409
+ return metricSample(metaAdsResources, "meta_ad_insights", {
410
+ ts,
411
+ value,
412
+ attributes: {
413
+ ...adsetAttributes,
414
+ adId: adRow.ad_id,
415
+ adName: adRow.ad_name ?? null
416
+ }
417
+ });
388
418
  }
389
419
  var CAMPAIGN_FIELDS = [
390
420
  "id",
@@ -406,8 +436,8 @@ var COMMON_INSIGHT_FIELDS = [
406
436
  "clicks",
407
437
  "spend",
408
438
  "reach",
409
- "actions",
410
- "action_values"
439
+ "conversions",
440
+ "conversion_values"
411
441
  ];
412
442
  var INSIGHT_FIELDS = {
413
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 StorageHandle,\n type SyncOptions,\n type SyncResult,\n defineConfigFields,\n defineConnectorDoc,\n defineResources,\n makeChunkedCursorGuard,\n paginateChunked,\n schemasFromResources,\n selectActivePhases,\n} from '@rawdash/core';\nimport { z } from 'zod';\n\nexport const configFields = defineConfigFields(\n z.object({\n adAccountId: z\n .string()\n .trim()\n .regex(\n /^act_\\d+$/,\n 'Ad account ID must look like `act_<digits>` (e.g. `act_1234567890`)',\n )\n .meta({\n label: 'Ad account ID',\n description:\n 'Meta Marketing API ad account ID. Find it in Ads Manager → Settings → Account info; it always starts with `act_`.',\n placeholder: 'act_1234567890',\n }),\n accessToken: z.object({ $secret: z.string() }).meta({\n label: 'System user access token',\n description:\n 'Long-lived System User access token from Meta Business Manager with `ads_read` (and, for newer accounts, `read_insights`) scopes on the chosen ad account.',\n placeholder: 'EAAB...',\n secret: true,\n }),\n apiVersion: z\n .string()\n .regex(/^v\\d+\\.\\d+$/, 'API version must look like `v21.0`')\n .optional()\n .meta({\n label: 'Graph API version',\n description:\n 'Pin a specific Meta Graph API version (e.g. `v21.0`). Defaults to `v21.0`.',\n placeholder: 'v21.0',\n }),\n lookbackDays: z.number().int().positive().optional().meta({\n label: 'Lookback days (full sync)',\n description:\n 'How many calendar days of insights to fetch on a full sync. Defaults to 90.',\n placeholder: '90',\n }),\n resources: z\n .array(\n z.enum([\n 'campaigns',\n 'campaign_insights',\n 'adset_insights',\n 'ad_insights',\n ]),\n )\n .nonempty()\n .optional()\n .meta({\n label: 'Resources',\n description:\n 'Which Meta resources to sync. Omit to sync all. Ad-level insights are the most expensive - leave them out if you only need campaign or adset rollups.',\n }),\n }),\n);\n\nexport const doc: ConnectorDoc = defineConnectorDoc({\n displayName: 'Meta Ads',\n category: 'marketing',\n brandColor: '#0866FF',\n tagline:\n 'Sync Meta (Facebook + Instagram) ad campaigns plus daily campaign, adset, and ad-level insights - spend, impressions, clicks, reach, conversions, and conversion value.',\n vendor: {\n name: 'Meta',\n domain: 'meta.com',\n apiDocs: 'https://developers.facebook.com/docs/marketing-api/insights',\n website: 'https://business.facebook.com',\n },\n auth: {\n summary:\n 'A long-lived System User access token from Meta Business Manager, scoped with `ads_read` (and `read_insights` on newer accounts) for the target ad account.',\n setup: [\n 'In Meta Business Manager → Business Settings → Users → System Users, create a System User (or reuse an existing one) and assign it to the ad account with at least the Advertiser role.',\n 'Generate a System User access token for the System User; pick `ads_read` and (where available) `read_insights` as the scopes. Choose the longest available expiry - System User tokens can be made effectively non-expiring.',\n 'Find the ad account ID in Ads Manager → Settings → Account info; it always starts with `act_`.',\n 'Store the token as a secret and reference it from the connector config as `accessToken: secret(\"META_ACCESS_TOKEN\")` alongside `adAccountId: \"act_<id>\"`.',\n ],\n },\n rateLimit:\n 'Meta enforces per-app and per-ad-account budgets surfaced through the `X-Business-Use-Case-Usage` header. Sync at most every few hours per ad account; very large accounts may need a daily cadence.',\n limitations: [\n 'Insights are always fetched at daily granularity. Sub-daily breakdowns are not supported.',\n 'Insights for the most recent 30 days are re-fetched on every incremental sync because Meta keeps attributing conversions after the event date.',\n 'Creative-level breakdowns (publisher_platform, placement, demographics) are intentionally out of scope to keep the metric cardinality bounded.',\n ],\n});\n\nexport interface MetaAdsSettings {\n adAccountId: string;\n apiVersion?: string;\n lookbackDays?: number;\n resources?: readonly MetaAdsResource[];\n}\n\nconst metaAdsCredentials = {\n accessToken: {\n description: 'Meta System User access token',\n auth: 'required' as const,\n },\n} satisfies CredentialsSchema;\n\ntype MetaAdsCredentials = typeof metaAdsCredentials;\n\nconst PHASE_ORDER = [\n 'campaigns',\n 'campaign_insights',\n 'adset_insights',\n 'ad_insights',\n] as const;\n\ntype MetaAdsPhase = (typeof PHASE_ORDER)[number];\n\nexport type MetaAdsResource = MetaAdsPhase;\n\nconst isMetaAdsSyncCursor = makeChunkedCursorGuard(PHASE_ORDER);\n\nconst DEFAULT_API_VERSION = 'v21.0';\nconst BASE_URL = 'https://graph.facebook.com';\nconst PAGE_LIMIT = 100;\n\nconst MS_PER_DAY = 24 * 60 * 60 * 1000;\nconst DEFAULT_LOOKBACK_DAYS = 90;\nconst INCREMENTAL_LOOKBACK_DAYS = 30;\n\nconst INSIGHTS_PHASES: ReadonlySet<MetaAdsPhase> = new Set([\n 'campaign_insights',\n 'adset_insights',\n 'ad_insights',\n]);\n\nconst PHASE_TO_LEVEL: Record<\n 'campaign_insights' | 'adset_insights' | 'ad_insights',\n 'campaign' | 'adset' | 'ad'\n> = {\n campaign_insights: 'campaign',\n adset_insights: 'adset',\n ad_insights: 'ad',\n};\n\nconst METRIC_NAME: Record<MetaAdsPhase, string> = {\n campaigns: 'meta_campaign',\n campaign_insights: 'meta_campaign_insights',\n adset_insights: 'meta_adset_insights',\n ad_insights: 'meta_ad_insights',\n};\n\nconst CAMPAIGN_ENTITY_TYPE = 'meta_campaign';\n\nfunction toMetaDate(date: Date): string {\n const y = date.getUTCFullYear();\n const m = String(date.getUTCMonth() + 1).padStart(2, '0');\n const d = String(date.getUTCDate()).padStart(2, '0');\n return `${y}-${m}-${d}`;\n}\n\nfunction metaDateToMs(metaDate: string): number {\n const [y, m, d] = metaDate.split('-').map((part) => Number(part));\n if (\n !Number.isFinite(y) ||\n !Number.isFinite(m) ||\n !Number.isFinite(d) ||\n y === undefined ||\n m === undefined ||\n d === undefined\n ) {\n return 0;\n }\n return Date.UTC(y, m - 1, d);\n}\n\ninterface MetaDateRange {\n since: string;\n until: string;\n}\n\nfunction getDateRange(\n options: SyncOptions,\n lookbackDays: number,\n): MetaDateRange {\n const now = Date.now();\n const until = toMetaDate(new Date(now));\n if (options.mode === 'latest' && options.since) {\n const startMs = now - (INCREMENTAL_LOOKBACK_DAYS - 1) * MS_PER_DAY;\n return { since: toMetaDate(new Date(startMs)), until };\n }\n if (options.since) {\n const sinceMs = new Date(options.since).getTime();\n if (Number.isFinite(sinceMs)) {\n const days = Math.max(1, Math.ceil((now - sinceMs) / MS_PER_DAY));\n const cappedDays = Math.min(days, lookbackDays);\n const startMs = now - (cappedDays - 1) * MS_PER_DAY;\n return { since: toMetaDate(new Date(startMs)), until };\n }\n }\n const startMs = now - (lookbackDays - 1) * MS_PER_DAY;\n return { since: toMetaDate(new Date(startMs)), until };\n}\n\nfunction parseNumber(value: unknown): number {\n if (typeof value === 'number' && Number.isFinite(value)) {\n return value;\n }\n if (typeof value === 'string' && value.trim() !== '') {\n const n = Number(value);\n return Number.isFinite(n) ? n : 0;\n }\n return 0;\n}\n\nfunction parseOptionalNumber(value: unknown): number | null {\n if (value === null || value === undefined) {\n return null;\n }\n if (typeof value === 'number' && Number.isFinite(value)) {\n return value;\n }\n if (typeof value === 'string' && value.trim() !== '') {\n const n = Number(value);\n return Number.isFinite(n) ? n : null;\n }\n return null;\n}\n\nfunction sumActionValues(\n entries: ReadonlyArray<{ value?: unknown }> | undefined,\n): number {\n if (!entries) {\n return 0;\n }\n let total = 0;\n for (const entry of entries) {\n total += parseNumber(entry.value);\n }\n return total;\n}\n\nconst actionEntrySchema = z.object({\n action_type: z.string().optional(),\n value: z.union([z.string(), z.number()]).optional(),\n});\n\nconst campaignSchema = z.object({\n id: z.string().min(1),\n name: z.string().nullish(),\n objective: z.string().nullish(),\n status: z.string().nullish(),\n effective_status: z.string().nullish(),\n daily_budget: z.union([z.string(), z.number()]).nullish(),\n lifetime_budget: z.union([z.string(), z.number()]).nullish(),\n created_time: z.string().nullish(),\n updated_time: z.string().nullish(),\n});\n\nconst insightsBaseShape = {\n date_start: z.string(),\n date_stop: z.string().optional(),\n impressions: z.union([z.string(), z.number()]).optional(),\n clicks: z.union([z.string(), z.number()]).optional(),\n spend: z.union([z.string(), z.number()]).optional(),\n reach: z.union([z.string(), z.number()]).optional(),\n actions: z.array(actionEntrySchema).optional(),\n action_values: z.array(actionEntrySchema).optional(),\n};\n\nconst campaignInsightSchema = z.object({\n ...insightsBaseShape,\n campaign_id: z.string().min(1),\n campaign_name: z.string().nullish(),\n});\n\nconst adsetInsightSchema = z.object({\n ...insightsBaseShape,\n campaign_id: z.string().min(1),\n campaign_name: z.string().nullish(),\n adset_id: z.string().min(1),\n adset_name: z.string().nullish(),\n});\n\nconst adInsightSchema = z.object({\n ...insightsBaseShape,\n campaign_id: z.string().min(1),\n campaign_name: z.string().nullish(),\n adset_id: z.string().min(1),\n adset_name: z.string().nullish(),\n ad_id: z.string().min(1),\n ad_name: z.string().nullish(),\n});\n\nconst campaignsSchema = z.array(campaignSchema);\nconst campaignInsightsSchema = z.array(campaignInsightSchema);\nconst adsetInsightsSchema = z.array(adsetInsightSchema);\nconst adInsightsSchema = z.array(adInsightSchema);\n\nexport const metaAdsResources = defineResources({\n meta_campaign: {\n shape: 'entity',\n filterable: [\n {\n field: 'effectiveStatus',\n ops: ['eq'],\n values: [\n 'ACTIVE',\n 'PAUSED',\n 'DELETED',\n 'ARCHIVED',\n 'IN_PROCESS',\n 'WITH_ISSUES',\n ],\n },\n ],\n description:\n 'Meta ad campaigns with name, objective, status, and budget. Upserted by id; one row per campaign in the ad account.',\n endpoint: 'GET /{ad_account_id}/campaigns',\n responses: { campaigns: campaignsSchema },\n },\n meta_campaign_insights: {\n shape: 'metric',\n description:\n 'Daily campaign-level Meta Ads insights - spend (primary value), impressions, clicks, reach, conversions, and conversion value bucketed by campaign.',\n endpoint: 'GET /{ad_account_id}/insights?level=campaign&time_increment=1',\n unit: 'USD',\n granularity: 'day',\n notes:\n 'Primary value is `spend`. `conversions` is the sum of every entry in the upstream `actions` array; `conversion_value` is the sum of every entry in `action_values`.',\n dimensions: [\n { name: 'date', description: 'Calendar day of the metric sample (UTC).' },\n { name: 'campaignId', description: 'Meta campaign id.' },\n { name: 'campaignName', description: 'Meta campaign name.' },\n { name: 'impressions', description: 'Total impressions on the day.' },\n { name: 'clicks', description: 'Total clicks on the day.' },\n { name: 'spend', description: 'Total spend (account currency).' },\n { name: 'reach', description: 'Unique reach on the day.' },\n { name: 'conversions', description: 'Total attributed actions.' },\n {\n name: 'conversion_value',\n description: 'Total attributed action value (account currency).',\n },\n ],\n responses: { campaign_insights: campaignInsightsSchema },\n },\n meta_adset_insights: {\n shape: 'metric',\n description:\n 'Daily adset-level Meta Ads insights - same fields as the campaign roll-up, bucketed by adset.',\n endpoint: 'GET /{ad_account_id}/insights?level=adset&time_increment=1',\n unit: 'USD',\n granularity: 'day',\n notes:\n 'Primary value is `spend`. Includes campaign_id/campaign_name so adset rows are easy to roll up to their parent campaign.',\n dimensions: [\n { name: 'date', description: 'Calendar day of the metric sample (UTC).' },\n { name: 'campaignId', description: 'Parent campaign id.' },\n { name: 'campaignName', description: 'Parent campaign name.' },\n { name: 'adsetId', description: 'Meta adset id.' },\n { name: 'adsetName', description: 'Meta adset name.' },\n { name: 'impressions', description: 'Total impressions on the day.' },\n { name: 'clicks', description: 'Total clicks on the day.' },\n { name: 'spend', description: 'Total spend (account currency).' },\n { name: 'reach', description: 'Unique reach on the day.' },\n { name: 'conversions', description: 'Total attributed actions.' },\n {\n name: 'conversion_value',\n description: 'Total attributed action value (account currency).',\n },\n ],\n responses: { adset_insights: adsetInsightsSchema },\n },\n meta_ad_insights: {\n shape: 'metric',\n description:\n 'Daily ad-level Meta Ads insights - same fields as the adset roll-up, bucketed by ad.',\n endpoint: 'GET /{ad_account_id}/insights?level=ad&time_increment=1',\n unit: 'USD',\n granularity: 'day',\n notes:\n 'Primary value is `spend`. Cardinality is the highest of the three insights resources - opt in via `resources: [..., \"ad_insights\"]` only when you need per-ad breakdowns.',\n dimensions: [\n { name: 'date', description: 'Calendar day of the metric sample (UTC).' },\n { name: 'campaignId', description: 'Parent campaign id.' },\n { name: 'campaignName', description: 'Parent campaign name.' },\n { name: 'adsetId', description: 'Parent adset id.' },\n { name: 'adsetName', description: 'Parent adset name.' },\n { name: 'adId', description: 'Meta ad id.' },\n { name: 'adName', description: 'Meta ad name.' },\n { name: 'impressions', description: 'Total impressions on the day.' },\n { name: 'clicks', description: 'Total clicks on the day.' },\n { name: 'spend', description: 'Total spend (account currency).' },\n { name: 'reach', description: 'Unique reach on the day.' },\n { name: 'conversions', description: 'Total attributed actions.' },\n {\n name: 'conversion_value',\n description: 'Total attributed action value (account currency).',\n },\n ],\n responses: { ad_insights: adInsightsSchema },\n },\n});\n\nexport interface MetaActionEntry {\n action_type?: string;\n value?: string | number;\n}\n\nexport interface MetaCampaign {\n id: string;\n name?: string | null;\n objective?: string | null;\n status?: string | null;\n effective_status?: string | null;\n daily_budget?: string | number | null;\n lifetime_budget?: string | number | null;\n created_time?: string | null;\n updated_time?: string | null;\n}\n\ninterface MetaInsightsBase {\n date_start: string;\n date_stop?: string;\n impressions?: string | number;\n clicks?: string | number;\n spend?: string | number;\n reach?: string | number;\n actions?: MetaActionEntry[];\n action_values?: MetaActionEntry[];\n}\n\nexport interface MetaCampaignInsight extends MetaInsightsBase {\n campaign_id: string;\n campaign_name?: string | null;\n}\n\nexport interface MetaAdsetInsight extends MetaCampaignInsight {\n adset_id: string;\n adset_name?: string | null;\n}\n\nexport interface MetaAdInsight extends MetaAdsetInsight {\n ad_id: string;\n ad_name?: string | null;\n}\n\ninterface MetaPagedResponse<T> {\n data?: T[];\n paging?: {\n cursors?: { after?: string; before?: string };\n next?: string;\n };\n}\n\nexport function campaignToEntity(row: MetaCampaign): {\n type: string;\n id: string;\n attributes: Record<string, string | number | null>;\n updated_at: number;\n} {\n const updatedAt = row.updated_time\n ? new Date(row.updated_time).getTime()\n : row.created_time\n ? new Date(row.created_time).getTime()\n : 0;\n return {\n type: CAMPAIGN_ENTITY_TYPE,\n id: row.id,\n attributes: {\n name: row.name ?? null,\n objective: row.objective ?? null,\n status: row.status ?? null,\n effectiveStatus: row.effective_status ?? null,\n dailyBudget: parseOptionalNumber(row.daily_budget),\n lifetimeBudget: parseOptionalNumber(row.lifetime_budget),\n createdAt: row.created_time ? new Date(row.created_time).getTime() : null,\n },\n updated_at: Number.isFinite(updatedAt) ? updatedAt : 0,\n };\n}\n\nfunction buildInsightAttributes(\n row: MetaInsightsBase,\n): Record<string, string | number | null> {\n const impressions = parseNumber(row.impressions);\n const clicks = parseNumber(row.clicks);\n const spend = parseNumber(row.spend);\n const reach = parseNumber(row.reach);\n const conversions = (row.actions ?? []).length\n ? sumActionValues(row.actions)\n : 0;\n const conversionValue = sumActionValues(row.action_values);\n return {\n date: row.date_start,\n impressions,\n clicks,\n spend,\n reach,\n conversions,\n conversion_value: conversionValue,\n };\n}\n\nexport function insightRowToMetricSample(\n row: MetaCampaignInsight | MetaAdsetInsight | MetaAdInsight,\n phase: 'campaign_insights' | 'adset_insights' | 'ad_insights',\n): {\n name: string;\n ts: number;\n value: number;\n attributes: Record<string, string | number | null>;\n} {\n const attributes = buildInsightAttributes(row);\n attributes['campaignId'] = row.campaign_id;\n attributes['campaignName'] = row.campaign_name ?? null;\n if (phase !== 'campaign_insights') {\n const ar = row as MetaAdsetInsight;\n attributes['adsetId'] = ar.adset_id;\n attributes['adsetName'] = ar.adset_name ?? null;\n }\n if (phase === 'ad_insights') {\n const ar = row as MetaAdInsight;\n attributes['adId'] = ar.ad_id;\n attributes['adName'] = ar.ad_name ?? null;\n }\n return {\n name: METRIC_NAME[phase],\n ts: metaDateToMs(row.date_start),\n value: parseNumber(row.spend),\n attributes,\n };\n}\n\nconst CAMPAIGN_FIELDS = [\n 'id',\n 'name',\n 'objective',\n 'status',\n 'effective_status',\n 'daily_budget',\n 'lifetime_budget',\n 'created_time',\n 'updated_time',\n].join(',');\n\nconst COMMON_INSIGHT_FIELDS = [\n 'date_start',\n 'date_stop',\n 'campaign_id',\n 'campaign_name',\n 'impressions',\n 'clicks',\n 'spend',\n 'reach',\n 'actions',\n 'action_values',\n];\n\nconst INSIGHT_FIELDS: Record<\n 'campaign_insights' | 'adset_insights' | 'ad_insights',\n string\n> = {\n campaign_insights: COMMON_INSIGHT_FIELDS.join(','),\n adset_insights: [...COMMON_INSIGHT_FIELDS, 'adset_id', 'adset_name'].join(\n ',',\n ),\n ad_insights: [\n ...COMMON_INSIGHT_FIELDS,\n 'adset_id',\n 'adset_name',\n 'ad_id',\n 'ad_name',\n ].join(','),\n};\n\nexport const id = 'meta-ads';\n\nfunction pushableEq(\n filter: FilterClause[] | undefined,\n field: string,\n): string | null {\n if (!filter) {\n return null;\n }\n for (const clause of filter) {\n if (\n 'field' in clause &&\n clause.field === field &&\n clause.op === 'eq' &&\n typeof clause.value === 'string'\n ) {\n return clause.value;\n }\n }\n return null;\n}\n\nexport class MetaAdsConnector extends BaseConnector<\n MetaAdsSettings,\n MetaAdsCredentials\n> {\n static readonly id = id;\n\n static readonly resources = metaAdsResources;\n\n static readonly schemas = schemasFromResources(metaAdsResources);\n\n static create(input: unknown, ctx?: ConnectorContext): MetaAdsConnector {\n const parsed = configFields.parse(input);\n return new MetaAdsConnector(\n {\n adAccountId: parsed.adAccountId,\n apiVersion: parsed.apiVersion,\n lookbackDays: parsed.lookbackDays,\n resources: parsed.resources,\n },\n { accessToken: parsed.accessToken },\n ctx,\n );\n }\n\n readonly id = id;\n override readonly credentials = metaAdsCredentials;\n\n private buildHeaders(): Record<string, string> {\n return {\n Authorization: `Bearer ${this.creds.accessToken}`,\n 'User-Agent': connectorUserAgent('meta-ads'),\n };\n }\n\n private apiVersion(): string {\n return this.settings.apiVersion ?? DEFAULT_API_VERSION;\n }\n\n private accountBase(): string {\n return `${BASE_URL}/${this.apiVersion()}/${this.settings.adAccountId}`;\n }\n\n private singleSpec(\n options: SyncOptions,\n resource: string,\n ): FetchSpec | undefined {\n const specs = options.fetchSpecs?.[resource];\n return specs && specs.length === 1 ? specs[0] : undefined;\n }\n\n private async fetchCampaignsPage(\n after: string | null,\n options: SyncOptions,\n signal: AbortSignal | undefined,\n ): Promise<{ items: unknown[]; next: string | null }> {\n const url = new URL(`${this.accountBase()}/campaigns`);\n url.searchParams.set('fields', CAMPAIGN_FIELDS);\n url.searchParams.set('limit', String(PAGE_LIMIT));\n const filter = this.singleSpec(options, CAMPAIGN_ENTITY_TYPE)?.filter;\n const effectiveStatus = pushableEq(filter, 'effectiveStatus');\n if (effectiveStatus !== null) {\n url.searchParams.set(\n 'effective_status',\n JSON.stringify([effectiveStatus]),\n );\n }\n if (after) {\n url.searchParams.set('after', after);\n }\n const res = await this.get<MetaPagedResponse<MetaCampaign>>(\n url.toString(),\n {\n resource: 'campaigns',\n headers: this.buildHeaders(),\n signal,\n },\n );\n return {\n items: res.body.data ?? [],\n next: res.body.paging?.cursors?.after ?? null,\n };\n }\n\n private async fetchInsightsPage(\n phase: 'campaign_insights' | 'adset_insights' | 'ad_insights',\n dateRange: MetaDateRange,\n after: string | null,\n signal: AbortSignal | undefined,\n ): Promise<{ items: unknown[]; next: string | null }> {\n const url = new URL(`${this.accountBase()}/insights`);\n url.searchParams.set('level', PHASE_TO_LEVEL[phase]);\n url.searchParams.set('time_increment', '1');\n url.searchParams.set('fields', INSIGHT_FIELDS[phase]);\n url.searchParams.set(\n 'time_range',\n JSON.stringify({ since: dateRange.since, until: dateRange.until }),\n );\n url.searchParams.set('limit', String(PAGE_LIMIT));\n if (after) {\n url.searchParams.set('after', after);\n }\n const res = await this.get<MetaPagedResponse<unknown>>(url.toString(), {\n resource: phase,\n headers: this.buildHeaders(),\n signal,\n });\n return {\n items: res.body.data ?? [],\n next: res.body.paging?.cursors?.after ?? null,\n };\n }\n\n private async writeCampaigns(\n storage: StorageHandle,\n items: MetaCampaign[],\n ): Promise<void> {\n for (const row of items) {\n await storage.entity(campaignToEntity(row));\n }\n }\n\n private async writeInsights(\n storage: StorageHandle,\n phase: 'campaign_insights' | 'adset_insights' | 'ad_insights',\n items: Array<MetaCampaignInsight | MetaAdsetInsight | MetaAdInsight>,\n ): Promise<void> {\n for (const row of items) {\n await storage.metric(insightRowToMetricSample(row, phase));\n }\n }\n\n private async clearScopeOnFirstPage(\n storage: StorageHandle,\n phase: MetaAdsPhase,\n isFull: boolean,\n ): Promise<void> {\n if (INSIGHTS_PHASES.has(phase)) {\n await storage.metrics([], { names: [METRIC_NAME[phase]] });\n return;\n }\n if (!isFull) {\n return;\n }\n await storage.entities([], { types: [CAMPAIGN_ENTITY_TYPE] });\n }\n\n async sync(\n options: SyncOptions,\n storage: StorageHandle,\n signal?: AbortSignal,\n ): Promise<SyncResult> {\n const cursor = isMetaAdsSyncCursor(options.cursor)\n ? options.cursor\n : undefined;\n const isFull = options.mode === 'full';\n const lookbackDays = this.settings.lookbackDays ?? DEFAULT_LOOKBACK_DAYS;\n const dateRange = getDateRange(options, lookbackDays);\n\n const phases = selectActivePhases<MetaAdsResource, MetaAdsPhase>(\n (r) => r,\n PHASE_ORDER,\n this.settings.resources,\n );\n\n return paginateChunked<MetaAdsPhase, string>({\n phases,\n cursor,\n signal,\n logger: this.logger,\n fetchPage: async (phase, page, sig) => {\n if (phase === 'campaigns') {\n return this.fetchCampaignsPage(page, options, sig);\n }\n return this.fetchInsightsPage(phase, dateRange, page, sig);\n },\n writeBatch: async (phase, items, page) => {\n if (page === null) {\n await this.clearScopeOnFirstPage(storage, phase, isFull);\n }\n if (phase === 'campaigns') {\n await this.writeCampaigns(storage, items as MetaCampaign[]);\n return;\n }\n await this.writeInsights(\n storage,\n phase,\n items as Array<\n MetaCampaignInsight | MetaAdsetInsight | MetaAdInsight\n >,\n );\n },\n });\n }\n}\n","import { MetaAdsConnector } from './meta-ads';\n\nexport {\n campaignToEntity,\n configFields,\n doc,\n id,\n insightRowToMetricSample,\n MetaAdsConnector,\n metaAdsResources as resources,\n} from './meta-ads';\nexport type {\n MetaAdsResource,\n MetaAdsSettings,\n MetaActionEntry,\n MetaAdInsight,\n MetaAdsetInsight,\n MetaCampaign,\n MetaCampaignInsight,\n} from './meta-ads';\nexport default MetaAdsConnector;\n"],"mappings":"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","names":["startMs"]}
1
+ {"version":3,"sources":["../../../connector-shared/src/errors.ts","../../../connector-shared/src/retry.ts","../../../connector-shared/src/version.ts","../../../connector-shared/src/request.ts","../../../connector-shared/src/rate-limit.ts","../../../connector-shared/src/map-concurrent.ts","../../../connector-shared/src/sanitize.ts","../../../connector-shared/src/epoch.ts","../../../connector-shared/src/pagination.ts","../../../connector-shared/src/logger.ts","../src/meta-ads.ts","../src/index.ts"],"sourcesContent":["import type { HttpResponse } from './types';\n\nexport type HttpErrorKind =\n | 'transient'\n | 'rate_limit'\n | 'auth'\n | 'upstream_bug'\n | 'client_bug';\n\nexport abstract class HttpClientError extends Error {\n abstract readonly kind: HttpErrorKind;\n readonly response?: HttpResponse;\n\n constructor(message: string, response?: HttpResponse) {\n super(message);\n this.name = new.target.name;\n this.response = response;\n }\n}\n\nexport class TransientError extends HttpClientError {\n readonly kind = 'transient' as const;\n}\n\nexport class RateLimitError extends HttpClientError {\n readonly kind = 'rate_limit' as const;\n readonly retryAfter?: Date;\n\n constructor(message: string, response?: HttpResponse, retryAfter?: Date) {\n super(message, response);\n this.retryAfter = retryAfter;\n }\n}\n\nexport class AuthError extends HttpClientError {\n readonly kind = 'auth' as const;\n}\n\nexport class UpstreamBugError extends HttpClientError {\n readonly kind = 'upstream_bug' as const;\n}\n\nexport class ClientBugError extends HttpClientError {\n readonly kind = 'client_bug' as const;\n}\n\nexport function classifyStatus(status: number): HttpErrorKind {\n if (status === 429) {\n return 'rate_limit';\n }\n if (status === 401 || status === 403) {\n return 'auth';\n }\n if (status === 408) {\n return 'transient';\n }\n if (status >= 500) {\n return 'upstream_bug';\n }\n if (status >= 400) {\n return 'client_bug';\n }\n return 'client_bug';\n}\n\nexport function errorForStatus(\n message: string,\n response: HttpResponse,\n retryAfter?: Date,\n): HttpClientError {\n const kind = classifyStatus(response.status);\n switch (kind) {\n case 'rate_limit':\n return new RateLimitError(message, response, retryAfter);\n case 'auth':\n return new AuthError(message, response);\n case 'transient':\n return new TransientError(message, response);\n case 'upstream_bug':\n return new UpstreamBugError(message, response);\n case 'client_bug':\n return new ClientBugError(message, response);\n }\n}\n","import { HttpClientError, RateLimitError, TransientError } from './errors';\n\nexport interface RetryPolicy {\n maxAttempts?: number;\n initialDelayMs?: number;\n maxDelayMs?: number;\n retryOn?: (status: number | null, err?: Error) => boolean;\n}\n\nexport const defaultRetryOn = (status: number | null, err?: Error): boolean => {\n if (err instanceof RateLimitError) {\n return true;\n }\n if (err instanceof TransientError) {\n return true;\n }\n if (status === null) {\n return err instanceof Error && !(err instanceof HttpClientError);\n }\n if (status === 408 || status === 429) {\n return true;\n }\n if (status >= 500) {\n return true;\n }\n return false;\n};\n\nexport function backoffDelayMs(\n attempt: number,\n policy: Required<Pick<RetryPolicy, 'initialDelayMs' | 'maxDelayMs'>>,\n): number {\n const base = policy.initialDelayMs * 2 ** attempt;\n const jitter = base * 0.25 * Math.random();\n return Math.min(base + jitter, policy.maxDelayMs);\n}\n\nexport function parseRetryAfter(\n headerValue: string | null,\n now: Date = new Date(),\n): Date | undefined {\n if (!headerValue) {\n return undefined;\n }\n const trimmed = headerValue.trim();\n if (/^\\d+$/.test(trimmed)) {\n return new Date(now.getTime() + Number(trimmed) * 1000);\n }\n const parsed = Date.parse(trimmed);\n if (Number.isNaN(parsed)) {\n return undefined;\n }\n return new Date(parsed);\n}\n\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n if (signal?.aborted) {\n return Promise.reject(signal.reason ?? new Error('Aborted'));\n }\n return new Promise<void>((resolve, reject) => {\n const onAbort = () => {\n clearTimeout(timer);\n reject(signal!.reason ?? new Error('Aborted'));\n };\n const timer = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n}\n","export const HTTP_CLIENT_VERSION = '0.0.0';\n\nexport const DEFAULT_USER_AGENT = `rawdash-connector/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;\n\nexport function connectorUserAgent(connectorId: string): string {\n return `rawdash-connector-${connectorId}/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;\n}\n","import {\n AuthError,\n ClientBugError,\n HttpClientError,\n RateLimitError,\n TransientError,\n UpstreamBugError,\n errorForStatus,\n} from './errors';\nimport { defaultRetryOn, parseRetryAfter, sleep } from './retry';\nimport type { FetchLike, HttpMethod, HttpRequest, HttpResponse } from './types';\nimport { DEFAULT_USER_AGENT } from './version';\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\nconst DEFAULT_MAX_ATTEMPTS = 3;\nconst DEFAULT_INITIAL_DELAY_MS = 1000;\nconst DEFAULT_MAX_DELAY_MS = 60_000;\nconst OBSERVER_TIMEOUT_MS = 250;\n\nexport interface RequestObservation {\n url: string;\n method: HttpMethod;\n status: number;\n resource: string;\n requestId: string;\n body: unknown;\n}\n\nexport type RequestObserver = (\n event: RequestObservation,\n) => void | Promise<void>;\n\nexport interface RequestOptions {\n fetch?: FetchLike;\n observer?: RequestObserver;\n resource: string;\n requestId?: string;\n}\n\nasync function notifyObserver(\n observer: RequestObserver,\n event: RequestObservation,\n): Promise<void> {\n let result: void | Promise<void>;\n try {\n result = observer(event);\n } catch (err) {\n console.warn('[connector-shared] request observer threw:', err);\n return;\n }\n if (!(result instanceof Promise)) {\n return;\n }\n const guarded = result.catch((err) => {\n console.warn('[connector-shared] request observer rejected:', err);\n });\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<void>((resolve) => {\n timer = setTimeout(resolve, OBSERVER_TIMEOUT_MS);\n });\n try {\n await Promise.race([guarded, timeout]);\n } finally {\n if (timer) {\n clearTimeout(timer);\n }\n }\n}\n\nfunction newRequestId(): string {\n const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;\n if (c?.randomUUID) {\n return c.randomUUID();\n }\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\nfunction mergeHeaders(\n defaults: Record<string, string>,\n overrides: Record<string, string> | undefined,\n): Record<string, string> {\n const merged: Record<string, string> = {};\n for (const [k, v] of Object.entries(defaults)) {\n merged[k.toLowerCase()] = v;\n }\n if (overrides) {\n for (const [k, v] of Object.entries(overrides)) {\n merged[k.toLowerCase()] = v;\n }\n }\n return merged;\n}\n\nfunction linkTimeoutSignal(\n parent: AbortSignal | undefined,\n timeoutMs: number,\n): { signal: AbortSignal; cancel: () => void } {\n const controller = new AbortController();\n const onParentAbort = () => {\n controller.abort(parent?.reason);\n };\n if (parent) {\n if (parent.aborted) {\n controller.abort(parent.reason);\n } else {\n parent.addEventListener('abort', onParentAbort, { once: true });\n }\n }\n const timer = setTimeout(() => {\n controller.abort(new Error(`Request timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n return {\n signal: controller.signal,\n cancel: () => {\n clearTimeout(timer);\n if (parent) {\n parent.removeEventListener('abort', onParentAbort);\n }\n },\n };\n}\n\nasync function readBody(\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.27.0",
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,15 +24,15 @@
24
24
  },
25
25
  "dependencies": {
26
26
  "zod": "^4.4.3",
27
- "@rawdash/core": "0.27.0"
27
+ "@rawdash/core": "0.28.2"
28
28
  },
29
29
  "devDependencies": {
30
30
  "fast-check": "^4.8.0",
31
31
  "tsup": "^8.0.0",
32
32
  "typescript": "^5.7.2",
33
33
  "vitest": "^4.1.4",
34
- "@rawdash/connector-test-utils": "0.0.10",
35
- "@rawdash/connector-shared": "0.3.1"
34
+ "@rawdash/connector-shared": "0.3.1",
35
+ "@rawdash/connector-test-utils": "0.0.10"
36
36
  },
37
37
  "scripts": {
38
38
  "build": "tsup",