@rawdash/connector-meta-ads 0.28.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md 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
 
@@ -42,7 +42,7 @@ A long-lived System User access token from Meta Business Manager, scoped with `a
42
42
  - Granularity: day
43
43
  - Dimensions: `date`, `campaignId`, `campaignName`
44
44
  - Measures: `impressions`, `clicks`, `reach`, `conversions`, `conversion_value`
45
- - Primary value is `spend`. `conversions` is the sum of every entry in the upstream `actions` array; `conversion_value` is the sum of every entry in `action_values`.
45
+ - Primary value is `spend`. `conversions` is the sum of the upstream `conversions` field (conversion events only, default attribution window); `conversion_value` is the sum of the upstream `conversion_values` field.
46
46
  - **`meta_adset_insights`** _(metric)_ - Daily adset-level Meta Ads insights - same fields as the campaign roll-up, bucketed by adset.
47
47
  - Endpoint: `GET /{ad_account_id}/insights?level=adset&time_increment=1`
48
48
  - Unit: USD
package/dist/index.d.ts CHANGED
@@ -62,7 +62,7 @@ declare const metaAdsResources: {
62
62
  readonly endpoint: "GET /{ad_account_id}/insights?level=campaign&time_increment=1";
63
63
  readonly unit: "USD";
64
64
  readonly granularity: "day";
65
- readonly notes: "Primary value is `spend`. `conversions` is the sum of 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).";
@@ -84,10 +84,10 @@ declare const metaAdsResources: {
84
84
  readonly description: "Unique reach on the day.";
85
85
  }, {
86
86
  readonly name: "conversions";
87
- readonly description: "Total attributed actions.";
87
+ readonly description: "Total attributed conversion events on the day.";
88
88
  }, {
89
89
  readonly name: "conversion_value";
90
- readonly description: "Total attributed action value (account currency).";
90
+ readonly description: "Total attributed conversion value (account currency).";
91
91
  }];
92
92
  readonly responses: {
93
93
  readonly campaign_insights: z.ZodArray<z.ZodObject<{
@@ -99,11 +99,11 @@ declare const metaAdsResources: {
99
99
  clicks: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
100
100
  spend: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
101
101
  reach: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
102
- actions: z.ZodOptional<z.ZodArray<z.ZodObject<{
102
+ conversions: z.ZodOptional<z.ZodArray<z.ZodObject<{
103
103
  action_type: z.ZodOptional<z.ZodString>;
104
104
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
105
105
  }, z.core.$strip>>>;
106
- action_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
106
+ conversion_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
107
107
  action_type: z.ZodOptional<z.ZodString>;
108
108
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
109
109
  }, z.core.$strip>>>;
@@ -144,10 +144,10 @@ declare const metaAdsResources: {
144
144
  readonly description: "Unique reach on the day.";
145
145
  }, {
146
146
  readonly name: "conversions";
147
- readonly description: "Total attributed actions.";
147
+ readonly description: "Total attributed conversion events on the day.";
148
148
  }, {
149
149
  readonly name: "conversion_value";
150
- readonly description: "Total attributed action value (account currency).";
150
+ readonly description: "Total attributed conversion value (account currency).";
151
151
  }];
152
152
  readonly responses: {
153
153
  readonly adset_insights: z.ZodArray<z.ZodObject<{
@@ -161,11 +161,11 @@ declare const metaAdsResources: {
161
161
  clicks: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
162
162
  spend: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
163
163
  reach: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
164
- actions: z.ZodOptional<z.ZodArray<z.ZodObject<{
164
+ conversions: z.ZodOptional<z.ZodArray<z.ZodObject<{
165
165
  action_type: z.ZodOptional<z.ZodString>;
166
166
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
167
167
  }, z.core.$strip>>>;
168
- action_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
168
+ conversion_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
169
169
  action_type: z.ZodOptional<z.ZodString>;
170
170
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
171
171
  }, z.core.$strip>>>;
@@ -212,10 +212,10 @@ declare const metaAdsResources: {
212
212
  readonly description: "Unique reach on the day.";
213
213
  }, {
214
214
  readonly name: "conversions";
215
- readonly description: "Total attributed actions.";
215
+ readonly description: "Total attributed conversion events on the day.";
216
216
  }, {
217
217
  readonly name: "conversion_value";
218
- readonly description: "Total attributed action value (account currency).";
218
+ readonly description: "Total attributed conversion value (account currency).";
219
219
  }];
220
220
  readonly responses: {
221
221
  readonly ad_insights: z.ZodArray<z.ZodObject<{
@@ -231,11 +231,11 @@ declare const metaAdsResources: {
231
231
  clicks: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
232
232
  spend: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
233
233
  reach: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
234
- actions: z.ZodOptional<z.ZodArray<z.ZodObject<{
234
+ conversions: z.ZodOptional<z.ZodArray<z.ZodObject<{
235
235
  action_type: z.ZodOptional<z.ZodString>;
236
236
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
237
237
  }, z.core.$strip>>>;
238
- action_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
238
+ conversion_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
239
239
  action_type: z.ZodOptional<z.ZodString>;
240
240
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
241
241
  }, z.core.$strip>>>;
@@ -265,8 +265,8 @@ interface MetaInsightsBase {
265
265
  clicks?: string | number;
266
266
  spend?: string | number;
267
267
  reach?: string | number;
268
- actions?: MetaActionEntry[];
269
- action_values?: MetaActionEntry[];
268
+ conversions?: MetaActionEntry[];
269
+ conversion_values?: MetaActionEntry[];
270
270
  }
271
271
  interface MetaCampaignInsight extends MetaInsightsBase {
272
272
  campaign_id: string;
@@ -320,7 +320,7 @@ declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCre
320
320
  readonly endpoint: "GET /{ad_account_id}/insights?level=campaign&time_increment=1";
321
321
  readonly unit: "USD";
322
322
  readonly granularity: "day";
323
- readonly notes: "Primary value is `spend`. `conversions` is the sum of 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.";
324
324
  readonly dimensions: [{
325
325
  readonly name: "date";
326
326
  readonly description: "Calendar day of the metric sample (UTC).";
@@ -342,10 +342,10 @@ declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCre
342
342
  readonly description: "Unique reach on the day.";
343
343
  }, {
344
344
  readonly name: "conversions";
345
- readonly description: "Total attributed actions.";
345
+ readonly description: "Total attributed conversion events on the day.";
346
346
  }, {
347
347
  readonly name: "conversion_value";
348
- readonly description: "Total attributed action value (account currency).";
348
+ readonly description: "Total attributed conversion value (account currency).";
349
349
  }];
350
350
  readonly responses: {
351
351
  readonly campaign_insights: z.ZodArray<z.ZodObject<{
@@ -357,11 +357,11 @@ declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCre
357
357
  clicks: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
358
358
  spend: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
359
359
  reach: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
360
- actions: z.ZodOptional<z.ZodArray<z.ZodObject<{
360
+ conversions: z.ZodOptional<z.ZodArray<z.ZodObject<{
361
361
  action_type: z.ZodOptional<z.ZodString>;
362
362
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
363
363
  }, z.core.$strip>>>;
364
- action_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
364
+ conversion_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
365
365
  action_type: z.ZodOptional<z.ZodString>;
366
366
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
367
367
  }, z.core.$strip>>>;
@@ -402,10 +402,10 @@ declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCre
402
402
  readonly description: "Unique reach on the day.";
403
403
  }, {
404
404
  readonly name: "conversions";
405
- readonly description: "Total attributed actions.";
405
+ readonly description: "Total attributed conversion events on the day.";
406
406
  }, {
407
407
  readonly name: "conversion_value";
408
- readonly description: "Total attributed action value (account currency).";
408
+ readonly description: "Total attributed conversion value (account currency).";
409
409
  }];
410
410
  readonly responses: {
411
411
  readonly adset_insights: z.ZodArray<z.ZodObject<{
@@ -419,11 +419,11 @@ declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCre
419
419
  clicks: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
420
420
  spend: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
421
421
  reach: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
422
- actions: z.ZodOptional<z.ZodArray<z.ZodObject<{
422
+ conversions: z.ZodOptional<z.ZodArray<z.ZodObject<{
423
423
  action_type: z.ZodOptional<z.ZodString>;
424
424
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
425
425
  }, z.core.$strip>>>;
426
- action_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
426
+ conversion_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
427
427
  action_type: z.ZodOptional<z.ZodString>;
428
428
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
429
429
  }, z.core.$strip>>>;
@@ -470,10 +470,10 @@ declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCre
470
470
  readonly description: "Unique reach on the day.";
471
471
  }, {
472
472
  readonly name: "conversions";
473
- readonly description: "Total attributed actions.";
473
+ readonly description: "Total attributed conversion events on the day.";
474
474
  }, {
475
475
  readonly name: "conversion_value";
476
- readonly description: "Total attributed action value (account currency).";
476
+ readonly description: "Total attributed conversion value (account currency).";
477
477
  }];
478
478
  readonly responses: {
479
479
  readonly ad_insights: z.ZodArray<z.ZodObject<{
@@ -489,11 +489,11 @@ declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCre
489
489
  clicks: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
490
490
  spend: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
491
491
  reach: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
492
- actions: z.ZodOptional<z.ZodArray<z.ZodObject<{
492
+ conversions: z.ZodOptional<z.ZodArray<z.ZodObject<{
493
493
  action_type: z.ZodOptional<z.ZodString>;
494
494
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
495
495
  }, z.core.$strip>>>;
496
- action_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
496
+ conversion_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
497
497
  action_type: z.ZodOptional<z.ZodString>;
498
498
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
499
499
  }, z.core.$strip>>>;
@@ -523,11 +523,11 @@ declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCre
523
523
  clicks: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
524
524
  spend: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
525
525
  reach: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
526
- actions: z.ZodOptional<z.ZodArray<z.ZodObject<{
526
+ conversions: z.ZodOptional<z.ZodArray<z.ZodObject<{
527
527
  action_type: z.ZodOptional<z.ZodString>;
528
528
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
529
529
  }, z.core.$strip>>>;
530
- action_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
530
+ conversion_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
531
531
  action_type: z.ZodOptional<z.ZodString>;
532
532
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
533
533
  }, z.core.$strip>>>;
@@ -544,11 +544,11 @@ declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCre
544
544
  clicks: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
545
545
  spend: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
546
546
  reach: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
547
- actions: z.ZodOptional<z.ZodArray<z.ZodObject<{
547
+ conversions: z.ZodOptional<z.ZodArray<z.ZodObject<{
548
548
  action_type: z.ZodOptional<z.ZodString>;
549
549
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
550
550
  }, z.core.$strip>>>;
551
- action_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
551
+ conversion_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
552
552
  action_type: z.ZodOptional<z.ZodString>;
553
553
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
554
554
  }, z.core.$strip>>>;
@@ -567,11 +567,11 @@ declare class MetaAdsConnector extends BaseConnector<MetaAdsSettings, MetaAdsCre
567
567
  clicks: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
568
568
  spend: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
569
569
  reach: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
570
- actions: z.ZodOptional<z.ZodArray<z.ZodObject<{
570
+ conversions: z.ZodOptional<z.ZodArray<z.ZodObject<{
571
571
  action_type: z.ZodOptional<z.ZodString>;
572
572
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
573
573
  }, z.core.$strip>>>;
574
- action_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
574
+ conversion_values: z.ZodOptional<z.ZodArray<z.ZodObject<{
575
575
  action_type: z.ZodOptional<z.ZodString>;
576
576
  value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
577
577
  }, z.core.$strip>>>;
package/dist/index.js CHANGED
@@ -34,10 +34,10 @@ var configFields = defineConfigFields(
34
34
  placeholder: "EAAB...",
35
35
  secret: true
36
36
  }),
37
- apiVersion: z.string().regex(/^v\d+\.\d+$/, "API version must look like `v21.0`").optional().meta({
37
+ apiVersion: z.string().regex(/^v\d+\.\d+$/, "API version must look like `v25.0`").optional().meta({
38
38
  label: "Graph API version",
39
- description: "Pin a specific Meta Graph API version (e.g. `v21.0`). Defaults to `v21.0`.",
40
- 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"
41
41
  }),
42
42
  lookbackDays: z.number().int().positive().optional().meta({
43
43
  label: "Lookback days (full sync)",
@@ -97,7 +97,7 @@ var PHASE_ORDER = [
97
97
  "ad_insights"
98
98
  ];
99
99
  var isMetaAdsSyncCursor = makeChunkedCursorGuard(PHASE_ORDER);
100
- var DEFAULT_API_VERSION = "v21.0";
100
+ var DEFAULT_API_VERSION = "v25.0";
101
101
  var BASE_URL = "https://graph.facebook.com";
102
102
  var PAGE_LIMIT = 100;
103
103
  var MS_PER_DAY = 24 * 60 * 60 * 1e3;
@@ -207,8 +207,8 @@ var insightsBaseShape = {
207
207
  clicks: z.union([z.string(), z.number()]).optional(),
208
208
  spend: z.union([z.string(), z.number()]).optional(),
209
209
  reach: z.union([z.string(), z.number()]).optional(),
210
- actions: z.array(actionEntrySchema).optional(),
211
- action_values: z.array(actionEntrySchema).optional()
210
+ conversions: z.array(actionEntrySchema).optional(),
211
+ conversion_values: z.array(actionEntrySchema).optional()
212
212
  };
213
213
  var campaignInsightSchema = z.object({
214
214
  ...insightsBaseShape,
@@ -262,7 +262,7 @@ var metaAdsResources = defineResources({
262
262
  endpoint: "GET /{ad_account_id}/insights?level=campaign&time_increment=1",
263
263
  unit: "USD",
264
264
  granularity: "day",
265
- notes: "Primary value is `spend`. `conversions` is the sum of 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.",
266
266
  dimensions: [
267
267
  { name: "date", description: "Calendar day of the metric sample (UTC)." },
268
268
  { name: "campaignId", description: "Meta campaign id." },
@@ -272,10 +272,13 @@ var metaAdsResources = defineResources({
272
272
  { name: "impressions", description: "Total impressions on the day." },
273
273
  { name: "clicks", description: "Total clicks on the day." },
274
274
  { name: "reach", description: "Unique reach on the day." },
275
- { name: "conversions", description: "Total attributed actions." },
275
+ {
276
+ name: "conversions",
277
+ description: "Total attributed conversion events on the day."
278
+ },
276
279
  {
277
280
  name: "conversion_value",
278
- description: "Total attributed action value (account currency)."
281
+ description: "Total attributed conversion value (account currency)."
279
282
  }
280
283
  ],
281
284
  responses: { campaign_insights: campaignInsightsSchema }
@@ -298,10 +301,13 @@ var metaAdsResources = defineResources({
298
301
  { name: "impressions", description: "Total impressions on the day." },
299
302
  { name: "clicks", description: "Total clicks on the day." },
300
303
  { name: "reach", description: "Unique reach on the day." },
301
- { name: "conversions", description: "Total attributed actions." },
304
+ {
305
+ name: "conversions",
306
+ description: "Total attributed conversion events on the day."
307
+ },
302
308
  {
303
309
  name: "conversion_value",
304
- description: "Total attributed action value (account currency)."
310
+ description: "Total attributed conversion value (account currency)."
305
311
  }
306
312
  ],
307
313
  responses: { adset_insights: adsetInsightsSchema }
@@ -326,10 +332,13 @@ var metaAdsResources = defineResources({
326
332
  { name: "impressions", description: "Total impressions on the day." },
327
333
  { name: "clicks", description: "Total clicks on the day." },
328
334
  { name: "reach", description: "Unique reach on the day." },
329
- { name: "conversions", description: "Total attributed actions." },
335
+ {
336
+ name: "conversions",
337
+ description: "Total attributed conversion events on the day."
338
+ },
330
339
  {
331
340
  name: "conversion_value",
332
- description: "Total attributed action value (account currency)."
341
+ description: "Total attributed conversion value (account currency)."
333
342
  }
334
343
  ],
335
344
  responses: { ad_insights: adInsightsSchema }
@@ -356,8 +365,8 @@ function buildInsightAttributes(row) {
356
365
  const impressions = parseNumber(row.impressions);
357
366
  const clicks = parseNumber(row.clicks);
358
367
  const reach = parseNumber(row.reach);
359
- const conversions = (row.actions ?? []).length ? sumActionValues(row.actions) : 0;
360
- const conversionValue = sumActionValues(row.action_values);
368
+ const conversions = sumActionValues(row.conversions);
369
+ const conversionValue = sumActionValues(row.conversion_values);
361
370
  return {
362
371
  date: row.date_start,
363
372
  impressions,
@@ -427,8 +436,8 @@ var COMMON_INSIGHT_FIELDS = [
427
436
  "clicks",
428
437
  "spend",
429
438
  "reach",
430
- "actions",
431
- "action_values"
439
+ "conversions",
440
+ "conversion_values"
432
441
  ];
433
442
  var INSIGHT_FIELDS = {
434
443
  campaign_insights: COMMON_INSIGHT_FIELDS.join(","),
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../connector-shared/src/errors.ts","../../../connector-shared/src/retry.ts","../../../connector-shared/src/version.ts","../../../connector-shared/src/request.ts","../../../connector-shared/src/rate-limit.ts","../../../connector-shared/src/map-concurrent.ts","../../../connector-shared/src/sanitize.ts","../../../connector-shared/src/epoch.ts","../../../connector-shared/src/pagination.ts","../../../connector-shared/src/logger.ts","../src/meta-ads.ts","../src/index.ts"],"sourcesContent":["import type { HttpResponse } from './types';\n\nexport type HttpErrorKind =\n | 'transient'\n | 'rate_limit'\n | 'auth'\n | 'upstream_bug'\n | 'client_bug';\n\nexport abstract class HttpClientError extends Error {\n abstract readonly kind: HttpErrorKind;\n readonly response?: HttpResponse;\n\n constructor(message: string, response?: HttpResponse) {\n super(message);\n this.name = new.target.name;\n this.response = response;\n }\n}\n\nexport class TransientError extends HttpClientError {\n readonly kind = 'transient' as const;\n}\n\nexport class RateLimitError extends HttpClientError {\n readonly kind = 'rate_limit' as const;\n readonly retryAfter?: Date;\n\n constructor(message: string, response?: HttpResponse, retryAfter?: Date) {\n super(message, response);\n this.retryAfter = retryAfter;\n }\n}\n\nexport class AuthError extends HttpClientError {\n readonly kind = 'auth' as const;\n}\n\nexport class UpstreamBugError extends HttpClientError {\n readonly kind = 'upstream_bug' as const;\n}\n\nexport class ClientBugError extends HttpClientError {\n readonly kind = 'client_bug' as const;\n}\n\nexport function classifyStatus(status: number): HttpErrorKind {\n if (status === 429) {\n return 'rate_limit';\n }\n if (status === 401 || status === 403) {\n return 'auth';\n }\n if (status === 408) {\n return 'transient';\n }\n if (status >= 500) {\n return 'upstream_bug';\n }\n if (status >= 400) {\n return 'client_bug';\n }\n return 'client_bug';\n}\n\nexport function errorForStatus(\n message: string,\n response: HttpResponse,\n retryAfter?: Date,\n): HttpClientError {\n const kind = classifyStatus(response.status);\n switch (kind) {\n case 'rate_limit':\n return new RateLimitError(message, response, retryAfter);\n case 'auth':\n return new AuthError(message, response);\n case 'transient':\n return new TransientError(message, response);\n case 'upstream_bug':\n return new UpstreamBugError(message, response);\n case 'client_bug':\n return new ClientBugError(message, response);\n }\n}\n","import { HttpClientError, RateLimitError, TransientError } from './errors';\n\nexport interface RetryPolicy {\n maxAttempts?: number;\n initialDelayMs?: number;\n maxDelayMs?: number;\n retryOn?: (status: number | null, err?: Error) => boolean;\n}\n\nexport const defaultRetryOn = (status: number | null, err?: Error): boolean => {\n if (err instanceof RateLimitError) {\n return true;\n }\n if (err instanceof TransientError) {\n return true;\n }\n if (status === null) {\n return err instanceof Error && !(err instanceof HttpClientError);\n }\n if (status === 408 || status === 429) {\n return true;\n }\n if (status >= 500) {\n return true;\n }\n return false;\n};\n\nexport function backoffDelayMs(\n attempt: number,\n policy: Required<Pick<RetryPolicy, 'initialDelayMs' | 'maxDelayMs'>>,\n): number {\n const base = policy.initialDelayMs * 2 ** attempt;\n const jitter = base * 0.25 * Math.random();\n return Math.min(base + jitter, policy.maxDelayMs);\n}\n\nexport function parseRetryAfter(\n headerValue: string | null,\n now: Date = new Date(),\n): Date | undefined {\n if (!headerValue) {\n return undefined;\n }\n const trimmed = headerValue.trim();\n if (/^\\d+$/.test(trimmed)) {\n return new Date(now.getTime() + Number(trimmed) * 1000);\n }\n const parsed = Date.parse(trimmed);\n if (Number.isNaN(parsed)) {\n return undefined;\n }\n return new Date(parsed);\n}\n\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n if (signal?.aborted) {\n return Promise.reject(signal.reason ?? new Error('Aborted'));\n }\n return new Promise<void>((resolve, reject) => {\n const onAbort = () => {\n clearTimeout(timer);\n reject(signal!.reason ?? new Error('Aborted'));\n };\n const timer = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n}\n","export const HTTP_CLIENT_VERSION = '0.0.0';\n\nexport const DEFAULT_USER_AGENT = `rawdash-connector/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;\n\nexport function connectorUserAgent(connectorId: string): string {\n return `rawdash-connector-${connectorId}/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;\n}\n","import {\n AuthError,\n ClientBugError,\n HttpClientError,\n RateLimitError,\n TransientError,\n UpstreamBugError,\n errorForStatus,\n} from './errors';\nimport { defaultRetryOn, parseRetryAfter, sleep } from './retry';\nimport type { FetchLike, HttpMethod, HttpRequest, HttpResponse } from './types';\nimport { DEFAULT_USER_AGENT } from './version';\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\nconst DEFAULT_MAX_ATTEMPTS = 3;\nconst DEFAULT_INITIAL_DELAY_MS = 1000;\nconst DEFAULT_MAX_DELAY_MS = 60_000;\nconst OBSERVER_TIMEOUT_MS = 250;\n\nexport interface RequestObservation {\n url: string;\n method: HttpMethod;\n status: number;\n resource: string;\n requestId: string;\n body: unknown;\n}\n\nexport type RequestObserver = (\n event: RequestObservation,\n) => void | Promise<void>;\n\nexport interface RequestOptions {\n fetch?: FetchLike;\n observer?: RequestObserver;\n resource: string;\n requestId?: string;\n}\n\nasync function notifyObserver(\n observer: RequestObserver,\n event: RequestObservation,\n): Promise<void> {\n let result: void | Promise<void>;\n try {\n result = observer(event);\n } catch (err) {\n console.warn('[connector-shared] request observer threw:', err);\n return;\n }\n if (!(result instanceof Promise)) {\n return;\n }\n const guarded = result.catch((err) => {\n console.warn('[connector-shared] request observer rejected:', err);\n });\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<void>((resolve) => {\n timer = setTimeout(resolve, OBSERVER_TIMEOUT_MS);\n });\n try {\n await Promise.race([guarded, timeout]);\n } finally {\n if (timer) {\n clearTimeout(timer);\n }\n }\n}\n\nfunction newRequestId(): string {\n const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;\n if (c?.randomUUID) {\n return c.randomUUID();\n }\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\nfunction mergeHeaders(\n defaults: Record<string, string>,\n overrides: Record<string, string> | undefined,\n): Record<string, string> {\n const merged: Record<string, string> = {};\n for (const [k, v] of Object.entries(defaults)) {\n merged[k.toLowerCase()] = v;\n }\n if (overrides) {\n for (const [k, v] of Object.entries(overrides)) {\n merged[k.toLowerCase()] = v;\n }\n }\n return merged;\n}\n\nfunction linkTimeoutSignal(\n parent: AbortSignal | undefined,\n timeoutMs: number,\n): { signal: AbortSignal; cancel: () => void } {\n const controller = new AbortController();\n const onParentAbort = () => {\n controller.abort(parent?.reason);\n };\n if (parent) {\n if (parent.aborted) {\n controller.abort(parent.reason);\n } else {\n parent.addEventListener('abort', onParentAbort, { once: true });\n }\n }\n const timer = setTimeout(() => {\n controller.abort(new Error(`Request timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n return {\n signal: controller.signal,\n cancel: () => {\n clearTimeout(timer);\n if (parent) {\n parent.removeEventListener('abort', onParentAbort);\n }\n },\n };\n}\n\nasync function readBody(res: Response, parseJson: boolean): Promise<unknown> {\n if (res.status === 204 || res.status === 205) {\n return null;\n }\n const contentType = res.headers.get('content-type') ?? '';\n if (parseJson && contentType.includes('application/json')) {\n const text = await res.text();\n if (text.length === 0) {\n return null;\n }\n return JSON.parse(text);\n }\n return res.text();\n}\n\nexport async function request<T = unknown>(\n req: HttpRequest,\n options: RequestOptions,\n): Promise<HttpResponse<T>> {\n const fetchImpl: FetchLike = options.fetch ?? (globalThis.fetch as FetchLike);\n const retry = req.retry ?? {};\n const maxAttempts = retry.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;\n const initialDelayMs = retry.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS;\n const maxDelayMs = retry.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;\n const retryOn = retry.retryOn ?? defaultRetryOn;\n const timeoutMs = req.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const parseJson = req.parseJson ?? true;\n\n const headers = mergeHeaders(\n {\n 'User-Agent': DEFAULT_USER_AGENT,\n Accept: 'application/json',\n },\n req.headers,\n );\n\n let lastErr: Error | undefined;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n req.signal?.throwIfAborted();\n\n const { signal, cancel } = linkTimeoutSignal(req.signal, timeoutMs);\n let res: Response;\n try {\n res = await fetchImpl(req.url, {\n method: req.method ?? 'GET',\n headers,\n body: req.body as RequestInit['body'],\n signal,\n });\n } catch (err) {\n cancel();\n if (req.signal?.aborted) {\n throw req.signal.reason ?? err;\n }\n const error = err instanceof Error ? err : new Error(String(err));\n lastErr = error;\n if (attempt < maxAttempts - 1 && retryOn(null, error)) {\n const delay = computeDelay(attempt, initialDelayMs, maxDelayMs);\n await sleep(delay, req.signal);\n continue;\n }\n throw new TransientError(error.message);\n }\n cancel();\n\n const body = await readBody(res, parseJson);\n const httpResponse: HttpResponse<T> = {\n status: res.status,\n headers: res.headers,\n body: body as T,\n };\n if (req.rateLimit) {\n const state = req.rateLimit.parse(res.headers);\n if (state) {\n httpResponse.rateLimitState = state;\n }\n }\n\n if (options.observer) {\n await notifyObserver(options.observer, {\n url: req.url,\n method: req.method ?? 'GET',\n status: res.status,\n resource: options.resource,\n requestId: options.requestId ?? newRequestId(),\n body,\n });\n }\n\n if (res.ok) {\n return httpResponse;\n }\n\n const retryAfter = parseRetryAfter(res.headers.get('retry-after'));\n const message = `HTTP ${res.status} ${res.statusText} for ${req.method ?? 'GET'} ${req.url}`;\n const err = errorForStatus(message, httpResponse, retryAfter);\n\n if (\n attempt < maxAttempts - 1 &&\n retryOn(res.status, err) &&\n !(err instanceof AuthError) &&\n !(err instanceof ClientBugError)\n ) {\n lastErr = err;\n let delay = computeDelay(attempt, initialDelayMs, maxDelayMs);\n if (err instanceof RateLimitError && retryAfter) {\n const wait = retryAfter.getTime() - Date.now();\n if (wait > 0) {\n delay = Math.min(wait, maxDelayMs);\n }\n }\n await sleep(delay, req.signal);\n continue;\n }\n\n throw err;\n }\n\n throw lastErr ?? new UpstreamBugError('Exhausted retry attempts');\n}\n\nfunction computeDelay(\n attempt: number,\n initialDelayMs: number,\n maxDelayMs: number,\n): number {\n const base = initialDelayMs * 2 ** attempt;\n const jitter = base * 0.25 * Math.random();\n return Math.min(base + jitter, maxDelayMs);\n}\n\nexport { HttpClientError };\n","export interface RateLimitState {\n remaining: number;\n resetAt: Date;\n}\n\nexport interface RateLimitPolicy {\n parse(headers: Headers): RateLimitState | null;\n}\n\nexport interface StandardRateLimitPolicyConfig {\n remainingHeader: string;\n resetHeader: string;\n resetUnit: 's' | 'ms';\n resetFallbackMs?: number;\n}\n\nexport function standardRateLimitPolicy(\n config: StandardRateLimitPolicyConfig,\n): RateLimitPolicy {\n const { remainingHeader, resetHeader, resetUnit, resetFallbackMs } = config;\n const multiplier = resetUnit === 's' ? 1000 : 1;\n return {\n parse(h) {\n const remainingRaw = h.get(remainingHeader);\n if (remainingRaw === null || remainingRaw.trim() === '') {\n return null;\n }\n const remaining = Number(remainingRaw);\n if (!Number.isFinite(remaining)) {\n return null;\n }\n const resetRaw = h.get(resetHeader);\n if (resetRaw === null) {\n if (resetFallbackMs === undefined) {\n return null;\n }\n return {\n remaining,\n resetAt: new Date(Date.now() + resetFallbackMs),\n };\n }\n if (resetRaw.trim() === '') {\n return null;\n }\n const reset = Number(resetRaw);\n if (!Number.isFinite(reset) || reset < 0) {\n return null;\n }\n const resetMs = reset * multiplier;\n if (!Number.isFinite(resetMs)) {\n return null;\n }\n return { remaining, resetAt: new Date(resetMs) };\n },\n };\n}\n","export async function mapWithConcurrency<T, R>(\n items: readonly T[],\n concurrency: number,\n fn: (item: T, index: number) => Promise<R>,\n): Promise<R[]> {\n const results = new Array<R>(items.length);\n if (items.length === 0) {\n return results;\n }\n const normalized = Number.isFinite(concurrency) ? Math.floor(concurrency) : 1;\n const limit = Math.max(1, Math.min(normalized, items.length));\n let next = 0;\n let failed = false;\n\n async function worker(): Promise<void> {\n while (!failed) {\n const i = next++;\n if (i >= items.length) {\n return;\n }\n try {\n results[i] = await fn(items[i]!, i);\n } catch (err) {\n failed = true;\n throw err;\n }\n }\n }\n\n const workers: Promise<void>[] = [];\n for (let w = 0; w < limit; w++) {\n workers.push(worker());\n }\n await Promise.all(workers);\n return results;\n}\n","export interface SanitizeAllowedUrlOptions {\n url: string | null;\n host: string;\n pathname: string;\n protocol?: 'https:' | 'http:';\n}\n\nexport function sanitizeAllowedUrl(\n options: SanitizeAllowedUrlOptions,\n): string | null {\n const { url, host, pathname, protocol = 'https:' } = options;\n if (url === null) {\n return null;\n }\n try {\n const u = new URL(url);\n if (u.protocol !== protocol || u.host !== host || u.pathname !== pathname) {\n return null;\n }\n return u.toString();\n } catch {\n return null;\n }\n}\n","export type EpochUnit = 'ms' | 's' | 'iso';\n\nexport function parseEpoch(\n value: number | string | null | undefined,\n unit: EpochUnit,\n): number | null {\n if (value === null || value === undefined) {\n return null;\n }\n if (unit === 'iso') {\n if (typeof value !== 'string') {\n return null;\n }\n const ms = new Date(value).getTime();\n return Number.isFinite(ms) ? ms : null;\n }\n if (typeof value === 'string' && value.trim() === '') {\n return null;\n }\n const n = typeof value === 'number' ? value : Number(value);\n if (!Number.isFinite(n)) {\n return null;\n }\n const result = unit === 's' ? n * 1000 : n;\n return Number.isFinite(result) ? result : null;\n}\n","import { request } from './request';\nimport type { HttpRequest } from './types';\n\nexport function parseLinkHeader(header: string | null): Record<string, string> {\n if (!header) {\n return {};\n }\n const result: Record<string, string> = {};\n for (const part of header.split(',')) {\n const match = part.match(/<([^>]+)>\\s*;\\s*rel=\"([^\"]+)\"/);\n if (match) {\n result[match[2]!] = match[1]!;\n }\n }\n return result;\n}\n\nexport async function* paginateLink<T>(\n initial: HttpRequest,\n parse: (body: unknown) => T[],\n options: { resource: string },\n): AsyncIterable<T> {\n let next: string | null = initial.url;\n while (next) {\n const res: Awaited<ReturnType<typeof request>> = await request(\n {\n ...initial,\n url: next,\n },\n { resource: options.resource },\n );\n for (const item of parse(res.body)) {\n yield item;\n }\n const links = parseLinkHeader(res.headers.get('link'));\n next = links['next'] ?? null;\n }\n}\n\nexport async function* paginateCursor<T>(\n initial: HttpRequest,\n parse: (body: unknown) => { items: T[]; nextCursor: string | null },\n buildNext: (req: HttpRequest, cursor: string) => HttpRequest,\n options: { resource: string },\n): AsyncIterable<T> {\n let req: HttpRequest = initial;\n while (true) {\n const res = await request(req, { resource: options.resource });\n const { items, nextCursor } = parse(res.body);\n for (const item of items) {\n yield item;\n }\n if (!nextCursor) {\n return;\n }\n req = buildNext(req, nextCursor);\n }\n}\n\nexport async function* paginatePage<T>(\n initial: HttpRequest,\n parse: (body: unknown) => { items: T[]; hasMore: boolean },\n buildPage: (req: HttpRequest, page: number) => HttpRequest,\n options: { resource: string },\n): AsyncIterable<T> {\n let page = 1;\n while (true) {\n const req = page === 1 ? initial : buildPage(initial, page);\n const res = await request(req, { resource: options.resource });\n const { items, hasMore } = parse(res.body);\n for (const item of items) {\n yield item;\n }\n if (!hasMore || items.length === 0) {\n return;\n }\n page++;\n }\n}\n","export type LogFields = Record<string, unknown>;\n\nexport interface ConnectorLogger {\n info(event: string, fields?: LogFields): void;\n warn(event: string, fields?: LogFields): void;\n}\n\nexport interface ConnectorLoggerOptions {\n scope: string;\n}\n\nconst MAX_VALUE_LEN = 120;\n\nfunction truncate(s: string, max = MAX_VALUE_LEN): string {\n if (s.length <= max) {\n return s;\n }\n return `${s.slice(0, max - 1)}…`;\n}\n\nfunction formatValue(value: unknown): string {\n if (value === null) {\n return 'null';\n }\n if (value === undefined) {\n return '';\n }\n if (typeof value === 'number' || typeof value === 'boolean') {\n return String(value);\n }\n if (typeof value === 'string') {\n const t = truncate(value);\n if (/[\\s\"=]/.test(t)) {\n return JSON.stringify(t);\n }\n return t;\n }\n if (typeof value === 'bigint') {\n return value.toString();\n }\n let json: string | undefined;\n try {\n json = JSON.stringify(value);\n } catch {\n json = undefined;\n }\n return truncate(json ?? String(value));\n}\n\nexport function formatLogFields(fields?: LogFields): string {\n if (!fields) {\n return '';\n }\n const parts: string[] = [];\n for (const [k, v] of Object.entries(fields)) {\n if (v === undefined) {\n continue;\n }\n parts.push(`${k}=${formatValue(v)}`);\n }\n return parts.length > 0 ? ` ${parts.join(' ')}` : '';\n}\n\nexport function formatLogLine(\n scope: string,\n event: string,\n fields?: LogFields,\n): string {\n return `[${scope}] ${event}${formatLogFields(fields)}`;\n}\n\nexport function createDefaultConnectorLogger(\n opts: ConnectorLoggerOptions,\n): ConnectorLogger {\n return {\n info(event, fields) {\n console.info(formatLogLine(opts.scope, event, fields));\n },\n warn(event, fields) {\n console.warn(formatLogLine(opts.scope, event, fields));\n },\n };\n}\n\nconst NOOP_LOGGER: ConnectorLogger = {\n info() {},\n warn() {},\n};\n\nexport function noopConnectorLogger(): ConnectorLogger {\n return NOOP_LOGGER;\n}\n","import { connectorUserAgent } from '@rawdash/connector-shared';\nimport {\n BaseConnector,\n type ConnectorContext,\n type ConnectorDoc,\n type CredentialsSchema,\n type FetchSpec,\n type FilterClause,\n type MetricSample,\n type StorageHandle,\n type SyncOptions,\n type SyncResult,\n defineConfigFields,\n defineConnectorDoc,\n defineResources,\n makeChunkedCursorGuard,\n metricSample,\n paginateChunked,\n schemasFromResources,\n selectActivePhases,\n} from '@rawdash/core';\nimport { z } from 'zod';\n\nexport const configFields = defineConfigFields(\n z.object({\n adAccountId: z\n .string()\n .trim()\n .regex(\n /^act_\\d+$/,\n 'Ad account ID must look like `act_<digits>` (e.g. `act_1234567890`)',\n )\n .meta({\n label: 'Ad account ID',\n description:\n 'Meta Marketing API ad account ID. Find it in Ads Manager → Settings → Account info; it always starts with `act_`.',\n placeholder: 'act_1234567890',\n }),\n accessToken: z.object({ $secret: z.string() }).meta({\n label: 'System user access token',\n description:\n 'Long-lived System User access token from Meta Business Manager with `ads_read` (and, for newer accounts, `read_insights`) scopes on the chosen ad account.',\n placeholder: 'EAAB...',\n secret: true,\n }),\n apiVersion: z\n .string()\n .regex(/^v\\d+\\.\\d+$/, 'API version must look like `v21.0`')\n .optional()\n .meta({\n label: 'Graph API version',\n description:\n 'Pin a specific Meta Graph API version (e.g. `v21.0`). Defaults to `v21.0`.',\n placeholder: 'v21.0',\n }),\n lookbackDays: z.number().int().positive().optional().meta({\n label: 'Lookback days (full sync)',\n description:\n 'How many calendar days of insights to fetch on a full sync. Defaults to 90.',\n placeholder: '90',\n }),\n resources: z\n .array(\n z.enum([\n 'campaigns',\n 'campaign_insights',\n 'adset_insights',\n 'ad_insights',\n ]),\n )\n .nonempty()\n .optional()\n .meta({\n label: 'Resources',\n description:\n 'Which Meta resources to sync. Omit to sync all. Ad-level insights are the most expensive - leave them out if you only need campaign or adset rollups.',\n }),\n }),\n);\n\nexport const doc: ConnectorDoc = defineConnectorDoc({\n displayName: 'Meta Ads',\n category: 'marketing',\n brandColor: '#0866FF',\n tagline:\n 'Sync Meta (Facebook + Instagram) ad campaigns plus daily campaign, adset, and ad-level insights - spend, impressions, clicks, reach, conversions, and conversion value.',\n vendor: {\n name: 'Meta',\n domain: 'meta.com',\n apiDocs: 'https://developers.facebook.com/docs/marketing-api/insights',\n website: 'https://business.facebook.com',\n },\n auth: {\n summary:\n 'A long-lived System User access token from Meta Business Manager, scoped with `ads_read` (and `read_insights` on newer accounts) for the target ad account.',\n setup: [\n 'In Meta Business Manager → Business Settings → Users → System Users, create a System User (or reuse an existing one) and assign it to the ad account with at least the Advertiser role.',\n 'Generate a System User access token for the System User; pick `ads_read` and (where available) `read_insights` as the scopes. Choose the longest available expiry - System User tokens can be made effectively non-expiring.',\n 'Find the ad account ID in Ads Manager → Settings → Account info; it always starts with `act_`.',\n 'Store the token as a secret and reference it from the connector config as `accessToken: secret(\"META_ACCESS_TOKEN\")` alongside `adAccountId: \"act_<id>\"`.',\n ],\n },\n rateLimit:\n 'Meta enforces per-app and per-ad-account budgets surfaced through the `X-Business-Use-Case-Usage` header. Sync at most every few hours per ad account; very large accounts may need a daily cadence.',\n limitations: [\n 'Insights are always fetched at daily granularity. Sub-daily breakdowns are not supported.',\n 'Insights for the most recent 30 days are re-fetched on every incremental sync because Meta keeps attributing conversions after the event date.',\n 'Creative-level breakdowns (publisher_platform, placement, demographics) are intentionally out of scope to keep the metric cardinality bounded.',\n ],\n});\n\nexport interface MetaAdsSettings {\n adAccountId: string;\n apiVersion?: string;\n lookbackDays?: number;\n resources?: readonly MetaAdsResource[];\n}\n\nconst metaAdsCredentials = {\n accessToken: {\n description: 'Meta System User access token',\n auth: 'required' as const,\n },\n} satisfies CredentialsSchema;\n\ntype MetaAdsCredentials = typeof metaAdsCredentials;\n\nconst PHASE_ORDER = [\n 'campaigns',\n 'campaign_insights',\n 'adset_insights',\n 'ad_insights',\n] as const;\n\ntype MetaAdsPhase = (typeof PHASE_ORDER)[number];\n\nexport type MetaAdsResource = MetaAdsPhase;\n\nconst isMetaAdsSyncCursor = makeChunkedCursorGuard(PHASE_ORDER);\n\nconst DEFAULT_API_VERSION = 'v21.0';\nconst BASE_URL = 'https://graph.facebook.com';\nconst PAGE_LIMIT = 100;\n\nconst MS_PER_DAY = 24 * 60 * 60 * 1000;\nconst DEFAULT_LOOKBACK_DAYS = 90;\nconst INCREMENTAL_LOOKBACK_DAYS = 30;\n\nconst INSIGHTS_PHASES: ReadonlySet<MetaAdsPhase> = new Set([\n 'campaign_insights',\n 'adset_insights',\n 'ad_insights',\n]);\n\nconst PHASE_TO_LEVEL: Record<\n 'campaign_insights' | 'adset_insights' | 'ad_insights',\n 'campaign' | 'adset' | 'ad'\n> = {\n campaign_insights: 'campaign',\n adset_insights: 'adset',\n ad_insights: 'ad',\n};\n\nconst METRIC_NAME: Record<MetaAdsPhase, string> = {\n campaigns: 'meta_campaign',\n campaign_insights: 'meta_campaign_insights',\n adset_insights: 'meta_adset_insights',\n ad_insights: 'meta_ad_insights',\n};\n\nconst CAMPAIGN_ENTITY_TYPE = 'meta_campaign';\n\nfunction toMetaDate(date: Date): string {\n const y = date.getUTCFullYear();\n const m = String(date.getUTCMonth() + 1).padStart(2, '0');\n const d = String(date.getUTCDate()).padStart(2, '0');\n return `${y}-${m}-${d}`;\n}\n\nfunction metaDateToMs(metaDate: string): number {\n const [y, m, d] = metaDate.split('-').map((part) => Number(part));\n if (\n !Number.isFinite(y) ||\n !Number.isFinite(m) ||\n !Number.isFinite(d) ||\n y === undefined ||\n m === undefined ||\n d === undefined\n ) {\n return 0;\n }\n return Date.UTC(y, m - 1, d);\n}\n\ninterface MetaDateRange {\n since: string;\n until: string;\n}\n\nfunction getDateRange(\n options: SyncOptions,\n lookbackDays: number,\n): MetaDateRange {\n const now = Date.now();\n const until = toMetaDate(new Date(now));\n if (options.mode === 'latest' && options.since) {\n const startMs = now - (INCREMENTAL_LOOKBACK_DAYS - 1) * MS_PER_DAY;\n return { since: toMetaDate(new Date(startMs)), until };\n }\n if (options.since) {\n const sinceMs = new Date(options.since).getTime();\n if (Number.isFinite(sinceMs)) {\n const days = Math.max(1, Math.ceil((now - sinceMs) / MS_PER_DAY));\n const cappedDays = Math.min(days, lookbackDays);\n const startMs = now - (cappedDays - 1) * MS_PER_DAY;\n return { since: toMetaDate(new Date(startMs)), until };\n }\n }\n const startMs = now - (lookbackDays - 1) * MS_PER_DAY;\n return { since: toMetaDate(new Date(startMs)), until };\n}\n\nfunction parseNumber(value: unknown): number {\n if (typeof value === 'number' && Number.isFinite(value)) {\n return value;\n }\n if (typeof value === 'string' && value.trim() !== '') {\n const n = Number(value);\n return Number.isFinite(n) ? n : 0;\n }\n return 0;\n}\n\nfunction parseOptionalNumber(value: unknown): number | null {\n if (value === null || value === undefined) {\n return null;\n }\n if (typeof value === 'number' && Number.isFinite(value)) {\n return value;\n }\n if (typeof value === 'string' && value.trim() !== '') {\n const n = Number(value);\n return Number.isFinite(n) ? n : null;\n }\n return null;\n}\n\nfunction sumActionValues(\n entries: ReadonlyArray<{ value?: unknown }> | undefined,\n): number {\n if (!entries) {\n return 0;\n }\n let total = 0;\n for (const entry of entries) {\n total += parseNumber(entry.value);\n }\n return total;\n}\n\nconst actionEntrySchema = z.object({\n action_type: z.string().optional(),\n value: z.union([z.string(), z.number()]).optional(),\n});\n\nconst campaignSchema = z.object({\n id: z.string().min(1),\n name: z.string().nullish(),\n objective: z.string().nullish(),\n status: z.string().nullish(),\n effective_status: z.string().nullish(),\n daily_budget: z.union([z.string(), z.number()]).nullish(),\n lifetime_budget: z.union([z.string(), z.number()]).nullish(),\n created_time: z.string().nullish(),\n updated_time: z.string().nullish(),\n});\n\nconst insightsBaseShape = {\n date_start: z.string(),\n date_stop: z.string().optional(),\n impressions: z.union([z.string(), z.number()]).optional(),\n clicks: z.union([z.string(), z.number()]).optional(),\n spend: z.union([z.string(), z.number()]).optional(),\n reach: z.union([z.string(), z.number()]).optional(),\n actions: z.array(actionEntrySchema).optional(),\n action_values: z.array(actionEntrySchema).optional(),\n};\n\nconst campaignInsightSchema = z.object({\n ...insightsBaseShape,\n campaign_id: z.string().min(1),\n campaign_name: z.string().nullish(),\n});\n\nconst adsetInsightSchema = z.object({\n ...insightsBaseShape,\n campaign_id: z.string().min(1),\n campaign_name: z.string().nullish(),\n adset_id: z.string().min(1),\n adset_name: z.string().nullish(),\n});\n\nconst adInsightSchema = z.object({\n ...insightsBaseShape,\n campaign_id: z.string().min(1),\n campaign_name: z.string().nullish(),\n adset_id: z.string().min(1),\n adset_name: z.string().nullish(),\n ad_id: z.string().min(1),\n ad_name: z.string().nullish(),\n});\n\nconst campaignsSchema = z.array(campaignSchema);\nconst campaignInsightsSchema = z.array(campaignInsightSchema);\nconst adsetInsightsSchema = z.array(adsetInsightSchema);\nconst adInsightsSchema = z.array(adInsightSchema);\n\nexport const metaAdsResources = defineResources({\n meta_campaign: {\n shape: 'entity',\n filterable: [\n {\n field: 'effectiveStatus',\n ops: ['eq'],\n values: [\n 'ACTIVE',\n 'PAUSED',\n 'DELETED',\n 'ARCHIVED',\n 'IN_PROCESS',\n 'WITH_ISSUES',\n ],\n },\n ],\n description:\n 'Meta ad campaigns with name, objective, status, and budget. Upserted by id; one row per campaign in the ad account.',\n endpoint: 'GET /{ad_account_id}/campaigns',\n responses: { campaigns: campaignsSchema },\n },\n meta_campaign_insights: {\n shape: 'metric',\n description:\n 'Daily campaign-level Meta Ads insights - spend (primary value), impressions, clicks, reach, conversions, and conversion value bucketed by campaign.',\n endpoint: 'GET /{ad_account_id}/insights?level=campaign&time_increment=1',\n unit: 'USD',\n granularity: 'day',\n notes:\n 'Primary value is `spend`. `conversions` is the sum of every entry in the upstream `actions` array; `conversion_value` is the sum of every entry in `action_values`.',\n dimensions: [\n { name: 'date', description: 'Calendar day of the metric sample (UTC).' },\n { name: 'campaignId', description: 'Meta campaign id.' },\n { name: 'campaignName', description: 'Meta campaign name.' },\n ],\n measures: [\n { name: 'impressions', description: 'Total impressions on the day.' },\n { name: 'clicks', description: 'Total clicks on the day.' },\n { name: 'reach', description: 'Unique reach on the day.' },\n { name: 'conversions', description: 'Total attributed actions.' },\n {\n name: 'conversion_value',\n description: 'Total attributed action value (account currency).',\n },\n ],\n responses: { campaign_insights: campaignInsightsSchema },\n },\n meta_adset_insights: {\n shape: 'metric',\n description:\n 'Daily adset-level Meta Ads insights - same fields as the campaign roll-up, bucketed by adset.',\n endpoint: 'GET /{ad_account_id}/insights?level=adset&time_increment=1',\n unit: 'USD',\n granularity: 'day',\n notes:\n 'Primary value is `spend`. Includes campaign_id/campaign_name so adset rows are easy to roll up to their parent campaign.',\n dimensions: [\n { name: 'date', description: 'Calendar day of the metric sample (UTC).' },\n { name: 'campaignId', description: 'Parent campaign id.' },\n { name: 'campaignName', description: 'Parent campaign name.' },\n { name: 'adsetId', description: 'Meta adset id.' },\n { name: 'adsetName', description: 'Meta adset name.' },\n ],\n measures: [\n { name: 'impressions', description: 'Total impressions on the day.' },\n { name: 'clicks', description: 'Total clicks on the day.' },\n { name: 'reach', description: 'Unique reach on the day.' },\n { name: 'conversions', description: 'Total attributed actions.' },\n {\n name: 'conversion_value',\n description: 'Total attributed action value (account currency).',\n },\n ],\n responses: { adset_insights: adsetInsightsSchema },\n },\n meta_ad_insights: {\n shape: 'metric',\n description:\n 'Daily ad-level Meta Ads insights - same fields as the adset roll-up, bucketed by ad.',\n endpoint: 'GET /{ad_account_id}/insights?level=ad&time_increment=1',\n unit: 'USD',\n granularity: 'day',\n notes:\n 'Primary value is `spend`. Cardinality is the highest of the three insights resources - opt in via `resources: [..., \"ad_insights\"]` only when you need per-ad breakdowns.',\n dimensions: [\n { name: 'date', description: 'Calendar day of the metric sample (UTC).' },\n { name: 'campaignId', description: 'Parent campaign id.' },\n { name: 'campaignName', description: 'Parent campaign name.' },\n { name: 'adsetId', description: 'Parent adset id.' },\n { name: 'adsetName', description: 'Parent adset name.' },\n { name: 'adId', description: 'Meta ad id.' },\n { name: 'adName', description: 'Meta ad name.' },\n ],\n measures: [\n { name: 'impressions', description: 'Total impressions on the day.' },\n { name: 'clicks', description: 'Total clicks on the day.' },\n { name: 'reach', description: 'Unique reach on the day.' },\n { name: 'conversions', description: 'Total attributed actions.' },\n {\n name: 'conversion_value',\n description: 'Total attributed action value (account currency).',\n },\n ],\n responses: { ad_insights: adInsightsSchema },\n },\n});\n\nexport interface MetaActionEntry {\n action_type?: string;\n value?: string | number;\n}\n\nexport interface MetaCampaign {\n id: string;\n name?: string | null;\n objective?: string | null;\n status?: string | null;\n effective_status?: string | null;\n daily_budget?: string | number | null;\n lifetime_budget?: string | number | null;\n created_time?: string | null;\n updated_time?: string | null;\n}\n\ninterface MetaInsightsBase {\n date_start: string;\n date_stop?: string;\n impressions?: string | number;\n clicks?: string | number;\n spend?: string | number;\n reach?: string | number;\n actions?: MetaActionEntry[];\n action_values?: MetaActionEntry[];\n}\n\nexport interface MetaCampaignInsight extends MetaInsightsBase {\n campaign_id: string;\n campaign_name?: string | null;\n}\n\nexport interface MetaAdsetInsight extends MetaCampaignInsight {\n adset_id: string;\n adset_name?: string | null;\n}\n\nexport interface MetaAdInsight extends MetaAdsetInsight {\n ad_id: string;\n ad_name?: string | null;\n}\n\ninterface MetaPagedResponse<T> {\n data?: T[];\n paging?: {\n cursors?: { after?: string; before?: string };\n next?: string;\n };\n}\n\nexport function campaignToEntity(row: MetaCampaign): {\n type: string;\n id: string;\n attributes: Record<string, string | number | null>;\n updated_at: number;\n} {\n const updatedAt = row.updated_time\n ? new Date(row.updated_time).getTime()\n : row.created_time\n ? new Date(row.created_time).getTime()\n : 0;\n return {\n type: CAMPAIGN_ENTITY_TYPE,\n id: row.id,\n attributes: {\n name: row.name ?? null,\n objective: row.objective ?? null,\n status: row.status ?? null,\n effectiveStatus: row.effective_status ?? null,\n dailyBudget: parseOptionalNumber(row.daily_budget),\n lifetimeBudget: parseOptionalNumber(row.lifetime_budget),\n createdAt: row.created_time ? new Date(row.created_time).getTime() : null,\n },\n updated_at: Number.isFinite(updatedAt) ? updatedAt : 0,\n };\n}\n\nfunction buildInsightAttributes(\n row: MetaInsightsBase,\n): Record<string, string | number | null> {\n const impressions = parseNumber(row.impressions);\n const clicks = parseNumber(row.clicks);\n const reach = parseNumber(row.reach);\n const conversions = (row.actions ?? []).length\n ? sumActionValues(row.actions)\n : 0;\n const conversionValue = sumActionValues(row.action_values);\n return {\n date: row.date_start,\n impressions,\n clicks,\n reach,\n conversions,\n conversion_value: conversionValue,\n };\n}\n\nexport function insightRowToMetricSample(\n row: MetaCampaignInsight | MetaAdsetInsight | MetaAdInsight,\n phase: 'campaign_insights' | 'adset_insights' | 'ad_insights',\n): MetricSample {\n const base = buildInsightAttributes(row);\n const ts = metaDateToMs(row.date_start);\n const value = parseNumber(row.spend);\n const campaignAttributes = {\n ...base,\n campaignId: row.campaign_id,\n campaignName: row.campaign_name ?? null,\n };\n if (phase === 'campaign_insights') {\n return metricSample(metaAdsResources, 'meta_campaign_insights', {\n ts,\n value,\n attributes: campaignAttributes,\n });\n }\n const adsetRow = row as MetaAdsetInsight;\n const adsetAttributes = {\n ...campaignAttributes,\n adsetId: adsetRow.adset_id,\n adsetName: adsetRow.adset_name ?? null,\n };\n if (phase === 'adset_insights') {\n return metricSample(metaAdsResources, 'meta_adset_insights', {\n ts,\n value,\n attributes: adsetAttributes,\n });\n }\n const adRow = row as MetaAdInsight;\n return metricSample(metaAdsResources, 'meta_ad_insights', {\n ts,\n value,\n attributes: {\n ...adsetAttributes,\n adId: adRow.ad_id,\n adName: adRow.ad_name ?? null,\n },\n });\n}\n\nconst CAMPAIGN_FIELDS = [\n 'id',\n 'name',\n 'objective',\n 'status',\n 'effective_status',\n 'daily_budget',\n 'lifetime_budget',\n 'created_time',\n 'updated_time',\n].join(',');\n\nconst COMMON_INSIGHT_FIELDS = [\n 'date_start',\n 'date_stop',\n 'campaign_id',\n 'campaign_name',\n 'impressions',\n 'clicks',\n 'spend',\n 'reach',\n 'actions',\n 'action_values',\n];\n\nconst INSIGHT_FIELDS: Record<\n 'campaign_insights' | 'adset_insights' | 'ad_insights',\n string\n> = {\n campaign_insights: COMMON_INSIGHT_FIELDS.join(','),\n adset_insights: [...COMMON_INSIGHT_FIELDS, 'adset_id', 'adset_name'].join(\n ',',\n ),\n ad_insights: [\n ...COMMON_INSIGHT_FIELDS,\n 'adset_id',\n 'adset_name',\n 'ad_id',\n 'ad_name',\n ].join(','),\n};\n\nexport const id = 'meta-ads';\n\nfunction pushableEq(\n filter: FilterClause[] | undefined,\n field: string,\n): string | null {\n if (!filter) {\n return null;\n }\n for (const clause of filter) {\n if (\n 'field' in clause &&\n clause.field === field &&\n clause.op === 'eq' &&\n typeof clause.value === 'string'\n ) {\n return clause.value;\n }\n }\n return null;\n}\n\nexport class MetaAdsConnector extends BaseConnector<\n MetaAdsSettings,\n MetaAdsCredentials\n> {\n static readonly id = id;\n\n static readonly resources = metaAdsResources;\n\n static readonly schemas = schemasFromResources(metaAdsResources);\n\n static create(input: unknown, ctx?: ConnectorContext): MetaAdsConnector {\n const parsed = configFields.parse(input);\n return new MetaAdsConnector(\n {\n adAccountId: parsed.adAccountId,\n apiVersion: parsed.apiVersion,\n lookbackDays: parsed.lookbackDays,\n resources: parsed.resources,\n },\n { accessToken: parsed.accessToken },\n ctx,\n );\n }\n\n readonly id = id;\n override readonly credentials = metaAdsCredentials;\n\n private buildHeaders(): Record<string, string> {\n return {\n Authorization: `Bearer ${this.creds.accessToken}`,\n 'User-Agent': connectorUserAgent('meta-ads'),\n };\n }\n\n private apiVersion(): string {\n return this.settings.apiVersion ?? DEFAULT_API_VERSION;\n }\n\n private accountBase(): string {\n return `${BASE_URL}/${this.apiVersion()}/${this.settings.adAccountId}`;\n }\n\n private singleSpec(\n options: SyncOptions,\n resource: string,\n ): FetchSpec | undefined {\n const specs = options.fetchSpecs?.[resource];\n return specs && specs.length === 1 ? specs[0] : undefined;\n }\n\n private async fetchCampaignsPage(\n after: string | null,\n options: SyncOptions,\n signal: AbortSignal | undefined,\n ): Promise<{ items: unknown[]; next: string | null }> {\n const url = new URL(`${this.accountBase()}/campaigns`);\n url.searchParams.set('fields', CAMPAIGN_FIELDS);\n url.searchParams.set('limit', String(PAGE_LIMIT));\n const filter = this.singleSpec(options, CAMPAIGN_ENTITY_TYPE)?.filter;\n const effectiveStatus = pushableEq(filter, 'effectiveStatus');\n if (effectiveStatus !== null) {\n url.searchParams.set(\n 'effective_status',\n JSON.stringify([effectiveStatus]),\n );\n }\n if (after) {\n url.searchParams.set('after', after);\n }\n const res = await this.get<MetaPagedResponse<MetaCampaign>>(\n url.toString(),\n {\n resource: 'campaigns',\n headers: this.buildHeaders(),\n signal,\n },\n );\n return {\n items: res.body.data ?? [],\n next: res.body.paging?.cursors?.after ?? null,\n };\n }\n\n private async fetchInsightsPage(\n phase: 'campaign_insights' | 'adset_insights' | 'ad_insights',\n dateRange: MetaDateRange,\n after: string | null,\n signal: AbortSignal | undefined,\n ): Promise<{ items: unknown[]; next: string | null }> {\n const url = new URL(`${this.accountBase()}/insights`);\n url.searchParams.set('level', PHASE_TO_LEVEL[phase]);\n url.searchParams.set('time_increment', '1');\n url.searchParams.set('fields', INSIGHT_FIELDS[phase]);\n url.searchParams.set(\n 'time_range',\n JSON.stringify({ since: dateRange.since, until: dateRange.until }),\n );\n url.searchParams.set('limit', String(PAGE_LIMIT));\n if (after) {\n url.searchParams.set('after', after);\n }\n const res = await this.get<MetaPagedResponse<unknown>>(url.toString(), {\n resource: phase,\n headers: this.buildHeaders(),\n signal,\n });\n return {\n items: res.body.data ?? [],\n next: res.body.paging?.cursors?.after ?? null,\n };\n }\n\n private async writeCampaigns(\n storage: StorageHandle,\n items: MetaCampaign[],\n ): Promise<void> {\n for (const row of items) {\n await storage.entity(campaignToEntity(row));\n }\n }\n\n private async writeInsights(\n storage: StorageHandle,\n phase: 'campaign_insights' | 'adset_insights' | 'ad_insights',\n items: Array<MetaCampaignInsight | MetaAdsetInsight | MetaAdInsight>,\n ): Promise<void> {\n for (const row of items) {\n await storage.metric(insightRowToMetricSample(row, phase));\n }\n }\n\n private async clearScopeOnFirstPage(\n storage: StorageHandle,\n phase: MetaAdsPhase,\n isFull: boolean,\n ): Promise<void> {\n if (INSIGHTS_PHASES.has(phase)) {\n await storage.metrics([], { names: [METRIC_NAME[phase]] });\n return;\n }\n if (!isFull) {\n return;\n }\n await storage.entities([], { types: [CAMPAIGN_ENTITY_TYPE] });\n }\n\n async sync(\n options: SyncOptions,\n storage: StorageHandle,\n signal?: AbortSignal,\n ): Promise<SyncResult> {\n const cursor = isMetaAdsSyncCursor(options.cursor)\n ? options.cursor\n : undefined;\n const isFull = options.mode === 'full';\n const lookbackDays = this.settings.lookbackDays ?? DEFAULT_LOOKBACK_DAYS;\n const dateRange = getDateRange(options, lookbackDays);\n\n const phases = selectActivePhases<MetaAdsResource, MetaAdsPhase>(\n (r) => r,\n PHASE_ORDER,\n this.settings.resources,\n );\n\n return paginateChunked<MetaAdsPhase, string>({\n phases,\n cursor,\n signal,\n logger: this.logger,\n fetchPage: async (phase, page, sig) => {\n if (phase === 'campaigns') {\n return this.fetchCampaignsPage(page, options, sig);\n }\n return this.fetchInsightsPage(phase, dateRange, page, sig);\n },\n writeBatch: async (phase, items, page) => {\n if (page === null) {\n await this.clearScopeOnFirstPage(storage, phase, isFull);\n }\n if (phase === 'campaigns') {\n await this.writeCampaigns(storage, items as MetaCampaign[]);\n return;\n }\n await this.writeInsights(\n storage,\n phase,\n items as Array<\n MetaCampaignInsight | MetaAdsetInsight | MetaAdInsight\n >,\n );\n },\n });\n }\n}\n","import { MetaAdsConnector } from './meta-ads';\n\nexport {\n campaignToEntity,\n configFields,\n doc,\n id,\n insightRowToMetricSample,\n MetaAdsConnector,\n metaAdsResources as resources,\n} from './meta-ads';\nexport type {\n MetaAdsResource,\n MetaAdsSettings,\n MetaActionEntry,\n MetaAdInsight,\n MetaAdsetInsight,\n MetaCampaign,\n MetaCampaignInsight,\n} from './meta-ads';\nexport default MetaAdsConnector;\n"],"mappings":"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","names":["startMs"]}
1
+ {"version":3,"sources":["../../../connector-shared/src/errors.ts","../../../connector-shared/src/retry.ts","../../../connector-shared/src/version.ts","../../../connector-shared/src/request.ts","../../../connector-shared/src/rate-limit.ts","../../../connector-shared/src/map-concurrent.ts","../../../connector-shared/src/sanitize.ts","../../../connector-shared/src/epoch.ts","../../../connector-shared/src/pagination.ts","../../../connector-shared/src/logger.ts","../src/meta-ads.ts","../src/index.ts"],"sourcesContent":["import type { HttpResponse } from './types';\n\nexport type HttpErrorKind =\n | 'transient'\n | 'rate_limit'\n | 'auth'\n | 'upstream_bug'\n | 'client_bug';\n\nexport abstract class HttpClientError extends Error {\n abstract readonly kind: HttpErrorKind;\n readonly response?: HttpResponse;\n\n constructor(message: string, response?: HttpResponse) {\n super(message);\n this.name = new.target.name;\n this.response = response;\n }\n}\n\nexport class TransientError extends HttpClientError {\n readonly kind = 'transient' as const;\n}\n\nexport class RateLimitError extends HttpClientError {\n readonly kind = 'rate_limit' as const;\n readonly retryAfter?: Date;\n\n constructor(message: string, response?: HttpResponse, retryAfter?: Date) {\n super(message, response);\n this.retryAfter = retryAfter;\n }\n}\n\nexport class AuthError extends HttpClientError {\n readonly kind = 'auth' as const;\n}\n\nexport class UpstreamBugError extends HttpClientError {\n readonly kind = 'upstream_bug' as const;\n}\n\nexport class ClientBugError extends HttpClientError {\n readonly kind = 'client_bug' as const;\n}\n\nexport function classifyStatus(status: number): HttpErrorKind {\n if (status === 429) {\n return 'rate_limit';\n }\n if (status === 401 || status === 403) {\n return 'auth';\n }\n if (status === 408) {\n return 'transient';\n }\n if (status >= 500) {\n return 'upstream_bug';\n }\n if (status >= 400) {\n return 'client_bug';\n }\n return 'client_bug';\n}\n\nexport function errorForStatus(\n message: string,\n response: HttpResponse,\n retryAfter?: Date,\n): HttpClientError {\n const kind = classifyStatus(response.status);\n switch (kind) {\n case 'rate_limit':\n return new RateLimitError(message, response, retryAfter);\n case 'auth':\n return new AuthError(message, response);\n case 'transient':\n return new TransientError(message, response);\n case 'upstream_bug':\n return new UpstreamBugError(message, response);\n case 'client_bug':\n return new ClientBugError(message, response);\n }\n}\n","import { HttpClientError, RateLimitError, TransientError } from './errors';\n\nexport interface RetryPolicy {\n maxAttempts?: number;\n initialDelayMs?: number;\n maxDelayMs?: number;\n retryOn?: (status: number | null, err?: Error) => boolean;\n}\n\nexport const defaultRetryOn = (status: number | null, err?: Error): boolean => {\n if (err instanceof RateLimitError) {\n return true;\n }\n if (err instanceof TransientError) {\n return true;\n }\n if (status === null) {\n return err instanceof Error && !(err instanceof HttpClientError);\n }\n if (status === 408 || status === 429) {\n return true;\n }\n if (status >= 500) {\n return true;\n }\n return false;\n};\n\nexport function backoffDelayMs(\n attempt: number,\n policy: Required<Pick<RetryPolicy, 'initialDelayMs' | 'maxDelayMs'>>,\n): number {\n const base = policy.initialDelayMs * 2 ** attempt;\n const jitter = base * 0.25 * Math.random();\n return Math.min(base + jitter, policy.maxDelayMs);\n}\n\nexport function parseRetryAfter(\n headerValue: string | null,\n now: Date = new Date(),\n): Date | undefined {\n if (!headerValue) {\n return undefined;\n }\n const trimmed = headerValue.trim();\n if (/^\\d+$/.test(trimmed)) {\n return new Date(now.getTime() + Number(trimmed) * 1000);\n }\n const parsed = Date.parse(trimmed);\n if (Number.isNaN(parsed)) {\n return undefined;\n }\n return new Date(parsed);\n}\n\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n if (signal?.aborted) {\n return Promise.reject(signal.reason ?? new Error('Aborted'));\n }\n return new Promise<void>((resolve, reject) => {\n const onAbort = () => {\n clearTimeout(timer);\n reject(signal!.reason ?? new Error('Aborted'));\n };\n const timer = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n}\n","export const HTTP_CLIENT_VERSION = '0.0.0';\n\nexport const DEFAULT_USER_AGENT = `rawdash-connector/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;\n\nexport function connectorUserAgent(connectorId: string): string {\n return `rawdash-connector-${connectorId}/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;\n}\n","import {\n AuthError,\n ClientBugError,\n HttpClientError,\n RateLimitError,\n TransientError,\n UpstreamBugError,\n errorForStatus,\n} from './errors';\nimport { defaultRetryOn, parseRetryAfter, sleep } from './retry';\nimport type { FetchLike, HttpMethod, HttpRequest, HttpResponse } from './types';\nimport { DEFAULT_USER_AGENT } from './version';\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\nconst DEFAULT_MAX_ATTEMPTS = 3;\nconst DEFAULT_INITIAL_DELAY_MS = 1000;\nconst DEFAULT_MAX_DELAY_MS = 60_000;\nconst OBSERVER_TIMEOUT_MS = 250;\n\nexport interface RequestObservation {\n url: string;\n method: HttpMethod;\n status: number;\n resource: string;\n requestId: string;\n body: unknown;\n}\n\nexport type RequestObserver = (\n event: RequestObservation,\n) => void | Promise<void>;\n\nexport interface RequestOptions {\n fetch?: FetchLike;\n observer?: RequestObserver;\n resource: string;\n requestId?: string;\n}\n\nasync function notifyObserver(\n observer: RequestObserver,\n event: RequestObservation,\n): Promise<void> {\n let result: void | Promise<void>;\n try {\n result = observer(event);\n } catch (err) {\n console.warn('[connector-shared] request observer threw:', err);\n return;\n }\n if (!(result instanceof Promise)) {\n return;\n }\n const guarded = result.catch((err) => {\n console.warn('[connector-shared] request observer rejected:', err);\n });\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<void>((resolve) => {\n timer = setTimeout(resolve, OBSERVER_TIMEOUT_MS);\n });\n try {\n await Promise.race([guarded, timeout]);\n } finally {\n if (timer) {\n clearTimeout(timer);\n }\n }\n}\n\nfunction newRequestId(): string {\n const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;\n if (c?.randomUUID) {\n return c.randomUUID();\n }\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\nfunction mergeHeaders(\n defaults: Record<string, string>,\n overrides: Record<string, string> | undefined,\n): Record<string, string> {\n const merged: Record<string, string> = {};\n for (const [k, v] of Object.entries(defaults)) {\n merged[k.toLowerCase()] = v;\n }\n if (overrides) {\n for (const [k, v] of Object.entries(overrides)) {\n merged[k.toLowerCase()] = v;\n }\n }\n return merged;\n}\n\nfunction linkTimeoutSignal(\n parent: AbortSignal | undefined,\n timeoutMs: number,\n): { signal: AbortSignal; cancel: () => void } {\n const controller = new AbortController();\n const onParentAbort = () => {\n controller.abort(parent?.reason);\n };\n if (parent) {\n if (parent.aborted) {\n controller.abort(parent.reason);\n } else {\n parent.addEventListener('abort', onParentAbort, { once: true });\n }\n }\n const timer = setTimeout(() => {\n controller.abort(new Error(`Request timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n return {\n signal: controller.signal,\n cancel: () => {\n clearTimeout(timer);\n if (parent) {\n parent.removeEventListener('abort', onParentAbort);\n }\n },\n };\n}\n\nasync function readBody(\n res: Response,\n parseJson: boolean,\n binary: boolean,\n): Promise<unknown> {\n if (res.status === 204 || res.status === 205) {\n return null;\n }\n if (binary) {\n return new Uint8Array(await res.arrayBuffer());\n }\n const contentType = res.headers.get('content-type') ?? '';\n if (parseJson && contentType.includes('application/json')) {\n const text = await res.text();\n if (text.length === 0) {\n return null;\n }\n return JSON.parse(text);\n }\n return res.text();\n}\n\nexport async function request<T = unknown>(\n req: HttpRequest,\n options: RequestOptions,\n): Promise<HttpResponse<T>> {\n const fetchImpl: FetchLike = options.fetch ?? (globalThis.fetch as FetchLike);\n const retry = req.retry ?? {};\n const maxAttempts = retry.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;\n const initialDelayMs = retry.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS;\n const maxDelayMs = retry.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;\n const retryOn = retry.retryOn ?? defaultRetryOn;\n const timeoutMs = req.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const parseJson = req.parseJson ?? true;\n const binary = req.binary ?? false;\n\n const headers = mergeHeaders(\n {\n 'User-Agent': DEFAULT_USER_AGENT,\n Accept: 'application/json',\n },\n req.headers,\n );\n\n let lastErr: Error | undefined;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n req.signal?.throwIfAborted();\n\n const { signal, cancel } = linkTimeoutSignal(req.signal, timeoutMs);\n let res: Response;\n try {\n res = await fetchImpl(req.url, {\n method: req.method ?? 'GET',\n headers,\n body: req.body as RequestInit['body'],\n signal,\n });\n } catch (err) {\n cancel();\n if (req.signal?.aborted) {\n throw req.signal.reason ?? err;\n }\n const error = err instanceof Error ? err : new Error(String(err));\n lastErr = error;\n if (attempt < maxAttempts - 1 && retryOn(null, error)) {\n const delay = computeDelay(attempt, initialDelayMs, maxDelayMs);\n await sleep(delay, req.signal);\n continue;\n }\n throw new TransientError(error.message);\n }\n cancel();\n\n const body = await readBody(res, parseJson, binary);\n const httpResponse: HttpResponse<T> = {\n status: res.status,\n headers: res.headers,\n body: body as T,\n };\n if (req.rateLimit) {\n const state = req.rateLimit.parse(res.headers);\n if (state) {\n httpResponse.rateLimitState = state;\n }\n }\n\n if (options.observer) {\n await notifyObserver(options.observer, {\n url: req.url,\n method: req.method ?? 'GET',\n status: res.status,\n resource: options.resource,\n requestId: options.requestId ?? newRequestId(),\n body,\n });\n }\n\n if (res.ok) {\n return httpResponse;\n }\n\n const retryAfter = parseRetryAfter(res.headers.get('retry-after'));\n const message = `HTTP ${res.status} ${res.statusText} for ${req.method ?? 'GET'} ${req.url}`;\n const err = errorForStatus(message, httpResponse, retryAfter);\n\n if (\n attempt < maxAttempts - 1 &&\n retryOn(res.status, err) &&\n !(err instanceof AuthError) &&\n !(err instanceof ClientBugError)\n ) {\n lastErr = err;\n let delay = computeDelay(attempt, initialDelayMs, maxDelayMs);\n if (err instanceof RateLimitError && retryAfter) {\n const wait = retryAfter.getTime() - Date.now();\n if (wait > 0) {\n delay = Math.min(wait, maxDelayMs);\n }\n }\n await sleep(delay, req.signal);\n continue;\n }\n\n throw err;\n }\n\n throw lastErr ?? new UpstreamBugError('Exhausted retry attempts');\n}\n\nfunction computeDelay(\n attempt: number,\n initialDelayMs: number,\n maxDelayMs: number,\n): number {\n const base = initialDelayMs * 2 ** attempt;\n const jitter = base * 0.25 * Math.random();\n return Math.min(base + jitter, maxDelayMs);\n}\n\nexport { HttpClientError };\n","export interface RateLimitState {\n remaining: number;\n resetAt: Date;\n}\n\nexport interface RateLimitPolicy {\n parse(headers: Headers): RateLimitState | null;\n}\n\nexport interface StandardRateLimitPolicyConfig {\n remainingHeader: string;\n resetHeader: string;\n resetUnit: 's' | 'ms';\n resetFallbackMs?: number;\n}\n\nexport function standardRateLimitPolicy(\n config: StandardRateLimitPolicyConfig,\n): RateLimitPolicy {\n const { remainingHeader, resetHeader, resetUnit, resetFallbackMs } = config;\n const multiplier = resetUnit === 's' ? 1000 : 1;\n return {\n parse(h) {\n const remainingRaw = h.get(remainingHeader);\n if (remainingRaw === null || remainingRaw.trim() === '') {\n return null;\n }\n const remaining = Number(remainingRaw);\n if (!Number.isFinite(remaining)) {\n return null;\n }\n const resetRaw = h.get(resetHeader);\n if (resetRaw === null) {\n if (resetFallbackMs === undefined) {\n return null;\n }\n return {\n remaining,\n resetAt: new Date(Date.now() + resetFallbackMs),\n };\n }\n if (resetRaw.trim() === '') {\n return null;\n }\n const reset = Number(resetRaw);\n if (!Number.isFinite(reset) || reset < 0) {\n return null;\n }\n const resetMs = reset * multiplier;\n if (!Number.isFinite(resetMs)) {\n return null;\n }\n return { remaining, resetAt: new Date(resetMs) };\n },\n };\n}\n","export async function mapWithConcurrency<T, R>(\n items: readonly T[],\n concurrency: number,\n fn: (item: T, index: number) => Promise<R>,\n): Promise<R[]> {\n const results = new Array<R>(items.length);\n if (items.length === 0) {\n return results;\n }\n const normalized = Number.isFinite(concurrency) ? Math.floor(concurrency) : 1;\n const limit = Math.max(1, Math.min(normalized, items.length));\n let next = 0;\n let failed = false;\n\n async function worker(): Promise<void> {\n while (!failed) {\n const i = next++;\n if (i >= items.length) {\n return;\n }\n try {\n results[i] = await fn(items[i]!, i);\n } catch (err) {\n failed = true;\n throw err;\n }\n }\n }\n\n const workers: Promise<void>[] = [];\n for (let w = 0; w < limit; w++) {\n workers.push(worker());\n }\n await Promise.all(workers);\n return results;\n}\n","export interface SanitizeAllowedUrlOptions {\n url: string | null;\n host: string;\n pathname: string;\n protocol?: 'https:' | 'http:';\n}\n\nexport function sanitizeAllowedUrl(\n options: SanitizeAllowedUrlOptions,\n): string | null {\n const { url, host, pathname, protocol = 'https:' } = options;\n if (url === null) {\n return null;\n }\n try {\n const u = new URL(url);\n if (u.protocol !== protocol || u.host !== host || u.pathname !== pathname) {\n return null;\n }\n return u.toString();\n } catch {\n return null;\n }\n}\n","export type EpochUnit = 'ms' | 's' | 'iso';\n\nexport function parseEpoch(\n value: number | string | null | undefined,\n unit: EpochUnit,\n): number | null {\n if (value === null || value === undefined) {\n return null;\n }\n if (unit === 'iso') {\n if (typeof value !== 'string') {\n return null;\n }\n const ms = new Date(value).getTime();\n return Number.isFinite(ms) ? ms : null;\n }\n if (typeof value === 'string' && value.trim() === '') {\n return null;\n }\n const n = typeof value === 'number' ? value : Number(value);\n if (!Number.isFinite(n)) {\n return null;\n }\n const result = unit === 's' ? n * 1000 : n;\n return Number.isFinite(result) ? result : null;\n}\n","import { request } from './request';\nimport type { HttpRequest } from './types';\n\nexport function parseLinkHeader(header: string | null): Record<string, string> {\n if (!header) {\n return {};\n }\n const result: Record<string, string> = {};\n for (const part of header.split(',')) {\n const match = part.match(/<([^>]+)>\\s*;\\s*rel=\"([^\"]+)\"/);\n if (match) {\n result[match[2]!] = match[1]!;\n }\n }\n return result;\n}\n\nexport async function* paginateLink<T>(\n initial: HttpRequest,\n parse: (body: unknown) => T[],\n options: { resource: string },\n): AsyncIterable<T> {\n let next: string | null = initial.url;\n while (next) {\n const res: Awaited<ReturnType<typeof request>> = await request(\n {\n ...initial,\n url: next,\n },\n { resource: options.resource },\n );\n for (const item of parse(res.body)) {\n yield item;\n }\n const links = parseLinkHeader(res.headers.get('link'));\n next = links['next'] ?? null;\n }\n}\n\nexport async function* paginateCursor<T>(\n initial: HttpRequest,\n parse: (body: unknown) => { items: T[]; nextCursor: string | null },\n buildNext: (req: HttpRequest, cursor: string) => HttpRequest,\n options: { resource: string },\n): AsyncIterable<T> {\n let req: HttpRequest = initial;\n while (true) {\n const res = await request(req, { resource: options.resource });\n const { items, nextCursor } = parse(res.body);\n for (const item of items) {\n yield item;\n }\n if (!nextCursor) {\n return;\n }\n req = buildNext(req, nextCursor);\n }\n}\n\nexport async function* paginatePage<T>(\n initial: HttpRequest,\n parse: (body: unknown) => { items: T[]; hasMore: boolean },\n buildPage: (req: HttpRequest, page: number) => HttpRequest,\n options: { resource: string },\n): AsyncIterable<T> {\n let page = 1;\n while (true) {\n const req = page === 1 ? initial : buildPage(initial, page);\n const res = await request(req, { resource: options.resource });\n const { items, hasMore } = parse(res.body);\n for (const item of items) {\n yield item;\n }\n if (!hasMore || items.length === 0) {\n return;\n }\n page++;\n }\n}\n","export type LogFields = Record<string, unknown>;\n\nexport interface ConnectorLogger {\n info(event: string, fields?: LogFields): void;\n warn(event: string, fields?: LogFields): void;\n}\n\nexport interface ConnectorLoggerOptions {\n scope: string;\n}\n\nconst MAX_VALUE_LEN = 120;\n\nfunction truncate(s: string, max = MAX_VALUE_LEN): string {\n if (s.length <= max) {\n return s;\n }\n return `${s.slice(0, max - 1)}…`;\n}\n\nfunction formatValue(value: unknown): string {\n if (value === null) {\n return 'null';\n }\n if (value === undefined) {\n return '';\n }\n if (typeof value === 'number' || typeof value === 'boolean') {\n return String(value);\n }\n if (typeof value === 'string') {\n const t = truncate(value);\n if (/[\\s\"=]/.test(t)) {\n return JSON.stringify(t);\n }\n return t;\n }\n if (typeof value === 'bigint') {\n return value.toString();\n }\n let json: string | undefined;\n try {\n json = JSON.stringify(value);\n } catch {\n json = undefined;\n }\n return truncate(json ?? String(value));\n}\n\nexport function formatLogFields(fields?: LogFields): string {\n if (!fields) {\n return '';\n }\n const parts: string[] = [];\n for (const [k, v] of Object.entries(fields)) {\n if (v === undefined) {\n continue;\n }\n parts.push(`${k}=${formatValue(v)}`);\n }\n return parts.length > 0 ? ` ${parts.join(' ')}` : '';\n}\n\nexport function formatLogLine(\n scope: string,\n event: string,\n fields?: LogFields,\n): string {\n return `[${scope}] ${event}${formatLogFields(fields)}`;\n}\n\nexport function createDefaultConnectorLogger(\n opts: ConnectorLoggerOptions,\n): ConnectorLogger {\n return {\n info(event, fields) {\n console.info(formatLogLine(opts.scope, event, fields));\n },\n warn(event, fields) {\n console.warn(formatLogLine(opts.scope, event, fields));\n },\n };\n}\n\nconst NOOP_LOGGER: ConnectorLogger = {\n info() {},\n warn() {},\n};\n\nexport function noopConnectorLogger(): ConnectorLogger {\n return NOOP_LOGGER;\n}\n","import { connectorUserAgent } from '@rawdash/connector-shared';\nimport {\n BaseConnector,\n type ConnectorContext,\n type ConnectorDoc,\n type CredentialsSchema,\n type FetchSpec,\n type FilterClause,\n type MetricSample,\n type StorageHandle,\n type SyncOptions,\n type SyncResult,\n defineConfigFields,\n defineConnectorDoc,\n defineResources,\n makeChunkedCursorGuard,\n metricSample,\n paginateChunked,\n schemasFromResources,\n selectActivePhases,\n} from '@rawdash/core';\nimport { z } from 'zod';\n\nexport const configFields = defineConfigFields(\n z.object({\n adAccountId: z\n .string()\n .trim()\n .regex(\n /^act_\\d+$/,\n 'Ad account ID must look like `act_<digits>` (e.g. `act_1234567890`)',\n )\n .meta({\n label: 'Ad account ID',\n description:\n 'Meta Marketing API ad account ID. Find it in Ads Manager → Settings → Account info; it always starts with `act_`.',\n placeholder: 'act_1234567890',\n }),\n accessToken: z.object({ $secret: z.string() }).meta({\n label: 'System user access token',\n description:\n 'Long-lived System User access token from Meta Business Manager with `ads_read` (and, for newer accounts, `read_insights`) scopes on the chosen ad account.',\n placeholder: 'EAAB...',\n secret: true,\n }),\n apiVersion: z\n .string()\n .regex(/^v\\d+\\.\\d+$/, 'API version must look like `v25.0`')\n .optional()\n .meta({\n label: 'Graph API version',\n description:\n 'Pin a specific Meta Graph API version (e.g. `v25.0`). Defaults to `v25.0`. Meta deprecates Marketing API versions on a rolling schedule, so older pins eventually stop working.',\n placeholder: 'v25.0',\n }),\n lookbackDays: z.number().int().positive().optional().meta({\n label: 'Lookback days (full sync)',\n description:\n 'How many calendar days of insights to fetch on a full sync. Defaults to 90.',\n placeholder: '90',\n }),\n resources: z\n .array(\n z.enum([\n 'campaigns',\n 'campaign_insights',\n 'adset_insights',\n 'ad_insights',\n ]),\n )\n .nonempty()\n .optional()\n .meta({\n label: 'Resources',\n description:\n 'Which Meta resources to sync. Omit to sync all. Ad-level insights are the most expensive - leave them out if you only need campaign or adset rollups.',\n }),\n }),\n);\n\nexport const doc: ConnectorDoc = defineConnectorDoc({\n displayName: 'Meta Ads',\n category: 'marketing',\n brandColor: '#0866FF',\n tagline:\n 'Sync Meta (Facebook + Instagram) ad campaigns plus daily campaign, adset, and ad-level insights - spend, impressions, clicks, reach, conversions, and conversion value.',\n vendor: {\n name: 'Meta',\n domain: 'meta.com',\n apiDocs: 'https://developers.facebook.com/docs/marketing-api/insights',\n website: 'https://business.facebook.com',\n },\n auth: {\n summary:\n 'A long-lived System User access token from Meta Business Manager, scoped with `ads_read` (and `read_insights` on newer accounts) for the target ad account.',\n setup: [\n 'In Meta Business Manager → Business Settings → Users → System Users, create a System User (or reuse an existing one) and assign it to the ad account with at least the Advertiser role.',\n 'Generate a System User access token for the System User; pick `ads_read` and (where available) `read_insights` as the scopes. Choose the longest available expiry - System User tokens can be made effectively non-expiring.',\n 'Find the ad account ID in Ads Manager → Settings → Account info; it always starts with `act_`.',\n 'Store the token as a secret and reference it from the connector config as `accessToken: secret(\"META_ACCESS_TOKEN\")` alongside `adAccountId: \"act_<id>\"`.',\n ],\n },\n rateLimit:\n 'Meta enforces per-app and per-ad-account budgets surfaced through the `X-Business-Use-Case-Usage` header. Sync at most every few hours per ad account; very large accounts may need a daily cadence.',\n limitations: [\n 'Insights are always fetched at daily granularity. Sub-daily breakdowns are not supported.',\n 'Insights for the most recent 30 days are re-fetched on every incremental sync because Meta keeps attributing conversions after the event date.',\n 'Creative-level breakdowns (publisher_platform, placement, demographics) are intentionally out of scope to keep the metric cardinality bounded.',\n ],\n});\n\nexport interface MetaAdsSettings {\n adAccountId: string;\n apiVersion?: string;\n lookbackDays?: number;\n resources?: readonly MetaAdsResource[];\n}\n\nconst metaAdsCredentials = {\n accessToken: {\n description: 'Meta System User access token',\n auth: 'required' as const,\n },\n} satisfies CredentialsSchema;\n\ntype MetaAdsCredentials = typeof metaAdsCredentials;\n\nconst PHASE_ORDER = [\n 'campaigns',\n 'campaign_insights',\n 'adset_insights',\n 'ad_insights',\n] as const;\n\ntype MetaAdsPhase = (typeof PHASE_ORDER)[number];\n\nexport type MetaAdsResource = MetaAdsPhase;\n\nconst isMetaAdsSyncCursor = makeChunkedCursorGuard(PHASE_ORDER);\n\nconst DEFAULT_API_VERSION = 'v25.0';\nconst BASE_URL = 'https://graph.facebook.com';\nconst PAGE_LIMIT = 100;\n\nconst MS_PER_DAY = 24 * 60 * 60 * 1000;\nconst DEFAULT_LOOKBACK_DAYS = 90;\nconst INCREMENTAL_LOOKBACK_DAYS = 30;\n\nconst INSIGHTS_PHASES: ReadonlySet<MetaAdsPhase> = new Set([\n 'campaign_insights',\n 'adset_insights',\n 'ad_insights',\n]);\n\nconst PHASE_TO_LEVEL: Record<\n 'campaign_insights' | 'adset_insights' | 'ad_insights',\n 'campaign' | 'adset' | 'ad'\n> = {\n campaign_insights: 'campaign',\n adset_insights: 'adset',\n ad_insights: 'ad',\n};\n\nconst METRIC_NAME: Record<MetaAdsPhase, string> = {\n campaigns: 'meta_campaign',\n campaign_insights: 'meta_campaign_insights',\n adset_insights: 'meta_adset_insights',\n ad_insights: 'meta_ad_insights',\n};\n\nconst CAMPAIGN_ENTITY_TYPE = 'meta_campaign';\n\nfunction toMetaDate(date: Date): string {\n const y = date.getUTCFullYear();\n const m = String(date.getUTCMonth() + 1).padStart(2, '0');\n const d = String(date.getUTCDate()).padStart(2, '0');\n return `${y}-${m}-${d}`;\n}\n\nfunction metaDateToMs(metaDate: string): number {\n const [y, m, d] = metaDate.split('-').map((part) => Number(part));\n if (\n !Number.isFinite(y) ||\n !Number.isFinite(m) ||\n !Number.isFinite(d) ||\n y === undefined ||\n m === undefined ||\n d === undefined\n ) {\n return 0;\n }\n return Date.UTC(y, m - 1, d);\n}\n\ninterface MetaDateRange {\n since: string;\n until: string;\n}\n\nfunction getDateRange(\n options: SyncOptions,\n lookbackDays: number,\n): MetaDateRange {\n const now = Date.now();\n const until = toMetaDate(new Date(now));\n if (options.mode === 'latest' && options.since) {\n const startMs = now - (INCREMENTAL_LOOKBACK_DAYS - 1) * MS_PER_DAY;\n return { since: toMetaDate(new Date(startMs)), until };\n }\n if (options.since) {\n const sinceMs = new Date(options.since).getTime();\n if (Number.isFinite(sinceMs)) {\n const days = Math.max(1, Math.ceil((now - sinceMs) / MS_PER_DAY));\n const cappedDays = Math.min(days, lookbackDays);\n const startMs = now - (cappedDays - 1) * MS_PER_DAY;\n return { since: toMetaDate(new Date(startMs)), until };\n }\n }\n const startMs = now - (lookbackDays - 1) * MS_PER_DAY;\n return { since: toMetaDate(new Date(startMs)), until };\n}\n\nfunction parseNumber(value: unknown): number {\n if (typeof value === 'number' && Number.isFinite(value)) {\n return value;\n }\n if (typeof value === 'string' && value.trim() !== '') {\n const n = Number(value);\n return Number.isFinite(n) ? n : 0;\n }\n return 0;\n}\n\nfunction parseOptionalNumber(value: unknown): number | null {\n if (value === null || value === undefined) {\n return null;\n }\n if (typeof value === 'number' && Number.isFinite(value)) {\n return value;\n }\n if (typeof value === 'string' && value.trim() !== '') {\n const n = Number(value);\n return Number.isFinite(n) ? n : null;\n }\n return null;\n}\n\nfunction sumActionValues(\n entries: ReadonlyArray<{ value?: unknown }> | undefined,\n): number {\n if (!entries) {\n return 0;\n }\n let total = 0;\n for (const entry of entries) {\n total += parseNumber(entry.value);\n }\n return total;\n}\n\nconst actionEntrySchema = z.object({\n action_type: z.string().optional(),\n value: z.union([z.string(), z.number()]).optional(),\n});\n\nconst campaignSchema = z.object({\n id: z.string().min(1),\n name: z.string().nullish(),\n objective: z.string().nullish(),\n status: z.string().nullish(),\n effective_status: z.string().nullish(),\n daily_budget: z.union([z.string(), z.number()]).nullish(),\n lifetime_budget: z.union([z.string(), z.number()]).nullish(),\n created_time: z.string().nullish(),\n updated_time: z.string().nullish(),\n});\n\nconst insightsBaseShape = {\n date_start: z.string(),\n date_stop: z.string().optional(),\n impressions: z.union([z.string(), z.number()]).optional(),\n clicks: z.union([z.string(), z.number()]).optional(),\n spend: z.union([z.string(), z.number()]).optional(),\n reach: z.union([z.string(), z.number()]).optional(),\n conversions: z.array(actionEntrySchema).optional(),\n conversion_values: z.array(actionEntrySchema).optional(),\n};\n\nconst campaignInsightSchema = z.object({\n ...insightsBaseShape,\n campaign_id: z.string().min(1),\n campaign_name: z.string().nullish(),\n});\n\nconst adsetInsightSchema = z.object({\n ...insightsBaseShape,\n campaign_id: z.string().min(1),\n campaign_name: z.string().nullish(),\n adset_id: z.string().min(1),\n adset_name: z.string().nullish(),\n});\n\nconst adInsightSchema = z.object({\n ...insightsBaseShape,\n campaign_id: z.string().min(1),\n campaign_name: z.string().nullish(),\n adset_id: z.string().min(1),\n adset_name: z.string().nullish(),\n ad_id: z.string().min(1),\n ad_name: z.string().nullish(),\n});\n\nconst campaignsSchema = z.array(campaignSchema);\nconst campaignInsightsSchema = z.array(campaignInsightSchema);\nconst adsetInsightsSchema = z.array(adsetInsightSchema);\nconst adInsightsSchema = z.array(adInsightSchema);\n\nexport const metaAdsResources = defineResources({\n meta_campaign: {\n shape: 'entity',\n filterable: [\n {\n field: 'effectiveStatus',\n ops: ['eq'],\n values: [\n 'ACTIVE',\n 'PAUSED',\n 'DELETED',\n 'ARCHIVED',\n 'IN_PROCESS',\n 'WITH_ISSUES',\n ],\n },\n ],\n description:\n 'Meta ad campaigns with name, objective, status, and budget. Upserted by id; one row per campaign in the ad account.',\n endpoint: 'GET /{ad_account_id}/campaigns',\n responses: { campaigns: campaignsSchema },\n },\n meta_campaign_insights: {\n shape: 'metric',\n description:\n 'Daily campaign-level Meta Ads insights - spend (primary value), impressions, clicks, reach, conversions, and conversion value bucketed by campaign.',\n endpoint: 'GET /{ad_account_id}/insights?level=campaign&time_increment=1',\n unit: 'USD',\n granularity: 'day',\n notes:\n 'Primary value is `spend`. `conversions` is the sum of the upstream `conversions` field (conversion events only, default attribution window); `conversion_value` is the sum of the upstream `conversion_values` field.',\n dimensions: [\n { name: 'date', description: 'Calendar day of the metric sample (UTC).' },\n { name: 'campaignId', description: 'Meta campaign id.' },\n { name: 'campaignName', description: 'Meta campaign name.' },\n ],\n measures: [\n { name: 'impressions', description: 'Total impressions on the day.' },\n { name: 'clicks', description: 'Total clicks on the day.' },\n { name: 'reach', description: 'Unique reach on the day.' },\n {\n name: 'conversions',\n description: 'Total attributed conversion events on the day.',\n },\n {\n name: 'conversion_value',\n description: 'Total attributed conversion value (account currency).',\n },\n ],\n responses: { campaign_insights: campaignInsightsSchema },\n },\n meta_adset_insights: {\n shape: 'metric',\n description:\n 'Daily adset-level Meta Ads insights - same fields as the campaign roll-up, bucketed by adset.',\n endpoint: 'GET /{ad_account_id}/insights?level=adset&time_increment=1',\n unit: 'USD',\n granularity: 'day',\n notes:\n 'Primary value is `spend`. Includes campaign_id/campaign_name so adset rows are easy to roll up to their parent campaign.',\n dimensions: [\n { name: 'date', description: 'Calendar day of the metric sample (UTC).' },\n { name: 'campaignId', description: 'Parent campaign id.' },\n { name: 'campaignName', description: 'Parent campaign name.' },\n { name: 'adsetId', description: 'Meta adset id.' },\n { name: 'adsetName', description: 'Meta adset name.' },\n ],\n measures: [\n { name: 'impressions', description: 'Total impressions on the day.' },\n { name: 'clicks', description: 'Total clicks on the day.' },\n { name: 'reach', description: 'Unique reach on the day.' },\n {\n name: 'conversions',\n description: 'Total attributed conversion events on the day.',\n },\n {\n name: 'conversion_value',\n description: 'Total attributed conversion value (account currency).',\n },\n ],\n responses: { adset_insights: adsetInsightsSchema },\n },\n meta_ad_insights: {\n shape: 'metric',\n description:\n 'Daily ad-level Meta Ads insights - same fields as the adset roll-up, bucketed by ad.',\n endpoint: 'GET /{ad_account_id}/insights?level=ad&time_increment=1',\n unit: 'USD',\n granularity: 'day',\n notes:\n 'Primary value is `spend`. Cardinality is the highest of the three insights resources - opt in via `resources: [..., \"ad_insights\"]` only when you need per-ad breakdowns.',\n dimensions: [\n { name: 'date', description: 'Calendar day of the metric sample (UTC).' },\n { name: 'campaignId', description: 'Parent campaign id.' },\n { name: 'campaignName', description: 'Parent campaign name.' },\n { name: 'adsetId', description: 'Parent adset id.' },\n { name: 'adsetName', description: 'Parent adset name.' },\n { name: 'adId', description: 'Meta ad id.' },\n { name: 'adName', description: 'Meta ad name.' },\n ],\n measures: [\n { name: 'impressions', description: 'Total impressions on the day.' },\n { name: 'clicks', description: 'Total clicks on the day.' },\n { name: 'reach', description: 'Unique reach on the day.' },\n {\n name: 'conversions',\n description: 'Total attributed conversion events on the day.',\n },\n {\n name: 'conversion_value',\n description: 'Total attributed conversion value (account currency).',\n },\n ],\n responses: { ad_insights: adInsightsSchema },\n },\n});\n\nexport interface MetaActionEntry {\n action_type?: string;\n value?: string | number;\n}\n\nexport interface MetaCampaign {\n id: string;\n name?: string | null;\n objective?: string | null;\n status?: string | null;\n effective_status?: string | null;\n daily_budget?: string | number | null;\n lifetime_budget?: string | number | null;\n created_time?: string | null;\n updated_time?: string | null;\n}\n\ninterface MetaInsightsBase {\n date_start: string;\n date_stop?: string;\n impressions?: string | number;\n clicks?: string | number;\n spend?: string | number;\n reach?: string | number;\n conversions?: MetaActionEntry[];\n conversion_values?: MetaActionEntry[];\n}\n\nexport interface MetaCampaignInsight extends MetaInsightsBase {\n campaign_id: string;\n campaign_name?: string | null;\n}\n\nexport interface MetaAdsetInsight extends MetaCampaignInsight {\n adset_id: string;\n adset_name?: string | null;\n}\n\nexport interface MetaAdInsight extends MetaAdsetInsight {\n ad_id: string;\n ad_name?: string | null;\n}\n\ninterface MetaPagedResponse<T> {\n data?: T[];\n paging?: {\n cursors?: { after?: string; before?: string };\n next?: string;\n };\n}\n\nexport function campaignToEntity(row: MetaCampaign): {\n type: string;\n id: string;\n attributes: Record<string, string | number | null>;\n updated_at: number;\n} {\n const updatedAt = row.updated_time\n ? new Date(row.updated_time).getTime()\n : row.created_time\n ? new Date(row.created_time).getTime()\n : 0;\n return {\n type: CAMPAIGN_ENTITY_TYPE,\n id: row.id,\n attributes: {\n name: row.name ?? null,\n objective: row.objective ?? null,\n status: row.status ?? null,\n effectiveStatus: row.effective_status ?? null,\n dailyBudget: parseOptionalNumber(row.daily_budget),\n lifetimeBudget: parseOptionalNumber(row.lifetime_budget),\n createdAt: row.created_time ? new Date(row.created_time).getTime() : null,\n },\n updated_at: Number.isFinite(updatedAt) ? updatedAt : 0,\n };\n}\n\nfunction buildInsightAttributes(\n row: MetaInsightsBase,\n): Record<string, string | number | null> {\n const impressions = parseNumber(row.impressions);\n const clicks = parseNumber(row.clicks);\n const reach = parseNumber(row.reach);\n const conversions = sumActionValues(row.conversions);\n const conversionValue = sumActionValues(row.conversion_values);\n return {\n date: row.date_start,\n impressions,\n clicks,\n reach,\n conversions,\n conversion_value: conversionValue,\n };\n}\n\nexport function insightRowToMetricSample(\n row: MetaCampaignInsight | MetaAdsetInsight | MetaAdInsight,\n phase: 'campaign_insights' | 'adset_insights' | 'ad_insights',\n): MetricSample {\n const base = buildInsightAttributes(row);\n const ts = metaDateToMs(row.date_start);\n const value = parseNumber(row.spend);\n const campaignAttributes = {\n ...base,\n campaignId: row.campaign_id,\n campaignName: row.campaign_name ?? null,\n };\n if (phase === 'campaign_insights') {\n return metricSample(metaAdsResources, 'meta_campaign_insights', {\n ts,\n value,\n attributes: campaignAttributes,\n });\n }\n const adsetRow = row as MetaAdsetInsight;\n const adsetAttributes = {\n ...campaignAttributes,\n adsetId: adsetRow.adset_id,\n adsetName: adsetRow.adset_name ?? null,\n };\n if (phase === 'adset_insights') {\n return metricSample(metaAdsResources, 'meta_adset_insights', {\n ts,\n value,\n attributes: adsetAttributes,\n });\n }\n const adRow = row as MetaAdInsight;\n return metricSample(metaAdsResources, 'meta_ad_insights', {\n ts,\n value,\n attributes: {\n ...adsetAttributes,\n adId: adRow.ad_id,\n adName: adRow.ad_name ?? null,\n },\n });\n}\n\nconst CAMPAIGN_FIELDS = [\n 'id',\n 'name',\n 'objective',\n 'status',\n 'effective_status',\n 'daily_budget',\n 'lifetime_budget',\n 'created_time',\n 'updated_time',\n].join(',');\n\nconst COMMON_INSIGHT_FIELDS = [\n 'date_start',\n 'date_stop',\n 'campaign_id',\n 'campaign_name',\n 'impressions',\n 'clicks',\n 'spend',\n 'reach',\n 'conversions',\n 'conversion_values',\n];\n\nconst INSIGHT_FIELDS: Record<\n 'campaign_insights' | 'adset_insights' | 'ad_insights',\n string\n> = {\n campaign_insights: COMMON_INSIGHT_FIELDS.join(','),\n adset_insights: [...COMMON_INSIGHT_FIELDS, 'adset_id', 'adset_name'].join(\n ',',\n ),\n ad_insights: [\n ...COMMON_INSIGHT_FIELDS,\n 'adset_id',\n 'adset_name',\n 'ad_id',\n 'ad_name',\n ].join(','),\n};\n\nexport const id = 'meta-ads';\n\nfunction pushableEq(\n filter: FilterClause[] | undefined,\n field: string,\n): string | null {\n if (!filter) {\n return null;\n }\n for (const clause of filter) {\n if (\n 'field' in clause &&\n clause.field === field &&\n clause.op === 'eq' &&\n typeof clause.value === 'string'\n ) {\n return clause.value;\n }\n }\n return null;\n}\n\nexport class MetaAdsConnector extends BaseConnector<\n MetaAdsSettings,\n MetaAdsCredentials\n> {\n static readonly id = id;\n\n static readonly resources = metaAdsResources;\n\n static readonly schemas = schemasFromResources(metaAdsResources);\n\n static create(input: unknown, ctx?: ConnectorContext): MetaAdsConnector {\n const parsed = configFields.parse(input);\n return new MetaAdsConnector(\n {\n adAccountId: parsed.adAccountId,\n apiVersion: parsed.apiVersion,\n lookbackDays: parsed.lookbackDays,\n resources: parsed.resources,\n },\n { accessToken: parsed.accessToken },\n ctx,\n );\n }\n\n readonly id = id;\n override readonly credentials = metaAdsCredentials;\n\n private buildHeaders(): Record<string, string> {\n return {\n Authorization: `Bearer ${this.creds.accessToken}`,\n 'User-Agent': connectorUserAgent('meta-ads'),\n };\n }\n\n private apiVersion(): string {\n return this.settings.apiVersion ?? DEFAULT_API_VERSION;\n }\n\n private accountBase(): string {\n return `${BASE_URL}/${this.apiVersion()}/${this.settings.adAccountId}`;\n }\n\n private singleSpec(\n options: SyncOptions,\n resource: string,\n ): FetchSpec | undefined {\n const specs = options.fetchSpecs?.[resource];\n return specs && specs.length === 1 ? specs[0] : undefined;\n }\n\n private async fetchCampaignsPage(\n after: string | null,\n options: SyncOptions,\n signal: AbortSignal | undefined,\n ): Promise<{ items: unknown[]; next: string | null }> {\n const url = new URL(`${this.accountBase()}/campaigns`);\n url.searchParams.set('fields', CAMPAIGN_FIELDS);\n url.searchParams.set('limit', String(PAGE_LIMIT));\n const filter = this.singleSpec(options, CAMPAIGN_ENTITY_TYPE)?.filter;\n const effectiveStatus = pushableEq(filter, 'effectiveStatus');\n if (effectiveStatus !== null) {\n url.searchParams.set(\n 'effective_status',\n JSON.stringify([effectiveStatus]),\n );\n }\n if (after) {\n url.searchParams.set('after', after);\n }\n const res = await this.get<MetaPagedResponse<MetaCampaign>>(\n url.toString(),\n {\n resource: 'campaigns',\n headers: this.buildHeaders(),\n signal,\n },\n );\n return {\n items: res.body.data ?? [],\n next: res.body.paging?.cursors?.after ?? null,\n };\n }\n\n private async fetchInsightsPage(\n phase: 'campaign_insights' | 'adset_insights' | 'ad_insights',\n dateRange: MetaDateRange,\n after: string | null,\n signal: AbortSignal | undefined,\n ): Promise<{ items: unknown[]; next: string | null }> {\n const url = new URL(`${this.accountBase()}/insights`);\n url.searchParams.set('level', PHASE_TO_LEVEL[phase]);\n url.searchParams.set('time_increment', '1');\n url.searchParams.set('fields', INSIGHT_FIELDS[phase]);\n url.searchParams.set(\n 'time_range',\n JSON.stringify({ since: dateRange.since, until: dateRange.until }),\n );\n url.searchParams.set('limit', String(PAGE_LIMIT));\n if (after) {\n url.searchParams.set('after', after);\n }\n const res = await this.get<MetaPagedResponse<unknown>>(url.toString(), {\n resource: phase,\n headers: this.buildHeaders(),\n signal,\n });\n return {\n items: res.body.data ?? [],\n next: res.body.paging?.cursors?.after ?? null,\n };\n }\n\n private async writeCampaigns(\n storage: StorageHandle,\n items: MetaCampaign[],\n ): Promise<void> {\n for (const row of items) {\n await storage.entity(campaignToEntity(row));\n }\n }\n\n private async writeInsights(\n storage: StorageHandle,\n phase: 'campaign_insights' | 'adset_insights' | 'ad_insights',\n items: Array<MetaCampaignInsight | MetaAdsetInsight | MetaAdInsight>,\n ): Promise<void> {\n for (const row of items) {\n await storage.metric(insightRowToMetricSample(row, phase));\n }\n }\n\n private async clearScopeOnFirstPage(\n storage: StorageHandle,\n phase: MetaAdsPhase,\n isFull: boolean,\n ): Promise<void> {\n if (INSIGHTS_PHASES.has(phase)) {\n await storage.metrics([], { names: [METRIC_NAME[phase]] });\n return;\n }\n if (!isFull) {\n return;\n }\n await storage.entities([], { types: [CAMPAIGN_ENTITY_TYPE] });\n }\n\n async sync(\n options: SyncOptions,\n storage: StorageHandle,\n signal?: AbortSignal,\n ): Promise<SyncResult> {\n const cursor = isMetaAdsSyncCursor(options.cursor)\n ? options.cursor\n : undefined;\n const isFull = options.mode === 'full';\n const lookbackDays = this.settings.lookbackDays ?? DEFAULT_LOOKBACK_DAYS;\n const dateRange = getDateRange(options, lookbackDays);\n\n const phases = selectActivePhases<MetaAdsResource, MetaAdsPhase>(\n (r) => r,\n PHASE_ORDER,\n this.settings.resources,\n );\n\n return paginateChunked<MetaAdsPhase, string>({\n phases,\n cursor,\n signal,\n logger: this.logger,\n fetchPage: async (phase, page, sig) => {\n if (phase === 'campaigns') {\n return this.fetchCampaignsPage(page, options, sig);\n }\n return this.fetchInsightsPage(phase, dateRange, page, sig);\n },\n writeBatch: async (phase, items, page) => {\n if (page === null) {\n await this.clearScopeOnFirstPage(storage, phase, isFull);\n }\n if (phase === 'campaigns') {\n await this.writeCampaigns(storage, items as MetaCampaign[]);\n return;\n }\n await this.writeInsights(\n storage,\n phase,\n items as Array<\n MetaCampaignInsight | MetaAdsetInsight | MetaAdInsight\n >,\n );\n },\n });\n }\n}\n","import { MetaAdsConnector } from './meta-ads';\n\nexport {\n campaignToEntity,\n configFields,\n doc,\n id,\n insightRowToMetricSample,\n MetaAdsConnector,\n metaAdsResources as resources,\n} from './meta-ads';\nexport type {\n MetaAdsResource,\n MetaAdsSettings,\n MetaActionEntry,\n MetaAdInsight,\n MetaAdsetInsight,\n MetaCampaign,\n MetaCampaignInsight,\n} from './meta-ads';\nexport default MetaAdsConnector;\n"],"mappings":";AEAO,IAAM,sBAAsB;AAE5B,IAAM,qBAAqB,qBAAqB,mBAAmB;AAEnE,SAAS,mBAAmB,aAA6B;AAC9D,SAAO,qBAAqB,WAAW,IAAI,mBAAmB;AAChE;;;AQLA;AAAA,EACE;AAAA,EAUA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,SAAS;AAEX,IAAM,eAAe;AAAA,EAC1B,EAAE,OAAO;AAAA,IACP,aAAa,EACV,OAAO,EACP,KAAK,EACL;AAAA,MACC;AAAA,MACA;AAAA,IACF,EACC,KAAK;AAAA,MACJ,OAAO;AAAA,MACP,aACE;AAAA,MACF,aAAa;AAAA,IACf,CAAC;AAAA,IACH,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK;AAAA,MAClD,OAAO;AAAA,MACP,aACE;AAAA,MACF,aAAa;AAAA,MACb,QAAQ;AAAA,IACV,CAAC;AAAA,IACD,YAAY,EACT,OAAO,EACP,MAAM,eAAe,oCAAoC,EACzD,SAAS,EACT,KAAK;AAAA,MACJ,OAAO;AAAA,MACP,aACE;AAAA,MACF,aAAa;AAAA,IACf,CAAC;AAAA,IACH,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK;AAAA,MACxD,OAAO;AAAA,MACP,aACE;AAAA,MACF,aAAa;AAAA,IACf,CAAC;AAAA,IACD,WAAW,EACR;AAAA,MACC,EAAE,KAAK;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH,EACC,SAAS,EACT,SAAS,EACT,KAAK;AAAA,MACJ,OAAO;AAAA,MACP,aACE;AAAA,IACJ,CAAC;AAAA,EACL,CAAC;AACH;AAEO,IAAM,MAAoB,mBAAmB;AAAA,EAClD,aAAa;AAAA,EACb,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,SACE;AAAA,EACF,QAAQ;AAAA,IACN,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,SAAS;AAAA,EACX;AAAA,EACA,MAAM;AAAA,IACJ,SACE;AAAA,IACF,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA,WACE;AAAA,EACF,aAAa;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF,CAAC;AASD,IAAM,qBAAqB;AAAA,EACzB,aAAa;AAAA,IACX,aAAa;AAAA,IACb,MAAM;AAAA,EACR;AACF;AAIA,IAAM,cAAc;AAAA,EAClB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAMA,IAAM,sBAAsB,uBAAuB,WAAW;AAE9D,IAAM,sBAAsB;AAC5B,IAAM,WAAW;AACjB,IAAM,aAAa;AAEnB,IAAM,aAAa,KAAK,KAAK,KAAK;AAClC,IAAM,wBAAwB;AAC9B,IAAM,4BAA4B;AAElC,IAAM,kBAA6C,oBAAI,IAAI;AAAA,EACzD;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,IAAM,iBAGF;AAAA,EACF,mBAAmB;AAAA,EACnB,gBAAgB;AAAA,EAChB,aAAa;AACf;AAEA,IAAM,cAA4C;AAAA,EAChD,WAAW;AAAA,EACX,mBAAmB;AAAA,EACnB,gBAAgB;AAAA,EAChB,aAAa;AACf;AAEA,IAAM,uBAAuB;AAE7B,SAAS,WAAW,MAAoB;AACtC,QAAM,IAAI,KAAK,eAAe;AAC9B,QAAM,IAAI,OAAO,KAAK,YAAY,IAAI,CAAC,EAAE,SAAS,GAAG,GAAG;AACxD,QAAM,IAAI,OAAO,KAAK,WAAW,CAAC,EAAE,SAAS,GAAG,GAAG;AACnD,SAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;AACvB;AAEA,SAAS,aAAa,UAA0B;AAC9C,QAAM,CAAC,GAAG,GAAG,CAAC,IAAI,SAAS,MAAM,GAAG,EAAE,IAAI,CAAC,SAAS,OAAO,IAAI,CAAC;AAChE,MACE,CAAC,OAAO,SAAS,CAAC,KAClB,CAAC,OAAO,SAAS,CAAC,KAClB,CAAC,OAAO,SAAS,CAAC,KAClB,MAAM,UACN,MAAM,UACN,MAAM,QACN;AACA,WAAO;AAAA,EACT;AACA,SAAO,KAAK,IAAI,GAAG,IAAI,GAAG,CAAC;AAC7B;AAOA,SAAS,aACP,SACA,cACe;AACf,QAAM,MAAM,KAAK,IAAI;AACrB,QAAM,QAAQ,WAAW,IAAI,KAAK,GAAG,CAAC;AACtC,MAAI,QAAQ,SAAS,YAAY,QAAQ,OAAO;AAC9C,UAAMA,WAAU,OAAO,4BAA4B,KAAK;AACxD,WAAO,EAAE,OAAO,WAAW,IAAI,KAAKA,QAAO,CAAC,GAAG,MAAM;AAAA,EACvD;AACA,MAAI,QAAQ,OAAO;AACjB,UAAM,UAAU,IAAI,KAAK,QAAQ,KAAK,EAAE,QAAQ;AAChD,QAAI,OAAO,SAAS,OAAO,GAAG;AAC5B,YAAM,OAAO,KAAK,IAAI,GAAG,KAAK,MAAM,MAAM,WAAW,UAAU,CAAC;AAChE,YAAM,aAAa,KAAK,IAAI,MAAM,YAAY;AAC9C,YAAMA,WAAU,OAAO,aAAa,KAAK;AACzC,aAAO,EAAE,OAAO,WAAW,IAAI,KAAKA,QAAO,CAAC,GAAG,MAAM;AAAA,IACvD;AAAA,EACF;AACA,QAAM,UAAU,OAAO,eAAe,KAAK;AAC3C,SAAO,EAAE,OAAO,WAAW,IAAI,KAAK,OAAO,CAAC,GAAG,MAAM;AACvD;AAEA,SAAS,YAAY,OAAwB;AAC3C,MAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,GAAG;AACvD,WAAO;AAAA,EACT;AACA,MAAI,OAAO,UAAU,YAAY,MAAM,KAAK,MAAM,IAAI;AACpD,UAAM,IAAI,OAAO,KAAK;AACtB,WAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAAA,EAClC;AACA,SAAO;AACT;AAEA,SAAS,oBAAoB,OAA+B;AAC1D,MAAI,UAAU,QAAQ,UAAU,QAAW;AACzC,WAAO;AAAA,EACT;AACA,MAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,GAAG;AACvD,WAAO;AAAA,EACT;AACA,MAAI,OAAO,UAAU,YAAY,MAAM,KAAK,MAAM,IAAI;AACpD,UAAM,IAAI,OAAO,KAAK;AACtB,WAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAAA,EAClC;AACA,SAAO;AACT;AAEA,SAAS,gBACP,SACQ;AACR,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,EACT;AACA,MAAI,QAAQ;AACZ,aAAW,SAAS,SAAS;AAC3B,aAAS,YAAY,MAAM,KAAK;AAAA,EAClC;AACA,SAAO;AACT;AAEA,IAAM,oBAAoB,EAAE,OAAO;AAAA,EACjC,aAAa,EAAE,OAAO,EAAE,SAAS;AAAA,EACjC,OAAO,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE,SAAS;AACpD,CAAC;AAED,IAAM,iBAAiB,EAAE,OAAO;AAAA,EAC9B,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACpB,MAAM,EAAE,OAAO,EAAE,QAAQ;AAAA,EACzB,WAAW,EAAE,OAAO,EAAE,QAAQ;AAAA,EAC9B,QAAQ,EAAE,OAAO,EAAE,QAAQ;AAAA,EAC3B,kBAAkB,EAAE,OAAO,EAAE,QAAQ;AAAA,EACrC,cAAc,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE,QAAQ;AAAA,EACxD,iBAAiB,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE,QAAQ;AAAA,EAC3D,cAAc,EAAE,OAAO,EAAE,QAAQ;AAAA,EACjC,cAAc,EAAE,OAAO,EAAE,QAAQ;AACnC,CAAC;AAED,IAAM,oBAAoB;AAAA,EACxB,YAAY,EAAE,OAAO;AAAA,EACrB,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,aAAa,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE,SAAS;AAAA,EACxD,QAAQ,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE,SAAS;AAAA,EACnD,OAAO,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE,SAAS;AAAA,EAClD,OAAO,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE,SAAS;AAAA,EAClD,aAAa,EAAE,MAAM,iBAAiB,EAAE,SAAS;AAAA,EACjD,mBAAmB,EAAE,MAAM,iBAAiB,EAAE,SAAS;AACzD;AAEA,IAAM,wBAAwB,EAAE,OAAO;AAAA,EACrC,GAAG;AAAA,EACH,aAAa,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC7B,eAAe,EAAE,OAAO,EAAE,QAAQ;AACpC,CAAC;AAED,IAAM,qBAAqB,EAAE,OAAO;AAAA,EAClC,GAAG;AAAA,EACH,aAAa,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC7B,eAAe,EAAE,OAAO,EAAE,QAAQ;AAAA,EAClC,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC1B,YAAY,EAAE,OAAO,EAAE,QAAQ;AACjC,CAAC;AAED,IAAM,kBAAkB,EAAE,OAAO;AAAA,EAC/B,GAAG;AAAA,EACH,aAAa,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC7B,eAAe,EAAE,OAAO,EAAE,QAAQ;AAAA,EAClC,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC1B,YAAY,EAAE,OAAO,EAAE,QAAQ;AAAA,EAC/B,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACvB,SAAS,EAAE,OAAO,EAAE,QAAQ;AAC9B,CAAC;AAED,IAAM,kBAAkB,EAAE,MAAM,cAAc;AAC9C,IAAM,yBAAyB,EAAE,MAAM,qBAAqB;AAC5D,IAAM,sBAAsB,EAAE,MAAM,kBAAkB;AACtD,IAAM,mBAAmB,EAAE,MAAM,eAAe;AAEzC,IAAM,mBAAmB,gBAAgB;AAAA,EAC9C,eAAe;AAAA,IACb,OAAO;AAAA,IACP,YAAY;AAAA,MACV;AAAA,QACE,OAAO;AAAA,QACP,KAAK,CAAC,IAAI;AAAA,QACV,QAAQ;AAAA,UACN;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IACA,aACE;AAAA,IACF,UAAU;AAAA,IACV,WAAW,EAAE,WAAW,gBAAgB;AAAA,EAC1C;AAAA,EACA,wBAAwB;AAAA,IACtB,OAAO;AAAA,IACP,aACE;AAAA,IACF,UAAU;AAAA,IACV,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OACE;AAAA,IACF,YAAY;AAAA,MACV,EAAE,MAAM,QAAQ,aAAa,2CAA2C;AAAA,MACxE,EAAE,MAAM,cAAc,aAAa,oBAAoB;AAAA,MACvD,EAAE,MAAM,gBAAgB,aAAa,sBAAsB;AAAA,IAC7D;AAAA,IACA,UAAU;AAAA,MACR,EAAE,MAAM,eAAe,aAAa,gCAAgC;AAAA,MACpE,EAAE,MAAM,UAAU,aAAa,2BAA2B;AAAA,MAC1D,EAAE,MAAM,SAAS,aAAa,2BAA2B;AAAA,MACzD;AAAA,QACE,MAAM;AAAA,QACN,aAAa;AAAA,MACf;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,aAAa;AAAA,MACf;AAAA,IACF;AAAA,IACA,WAAW,EAAE,mBAAmB,uBAAuB;AAAA,EACzD;AAAA,EACA,qBAAqB;AAAA,IACnB,OAAO;AAAA,IACP,aACE;AAAA,IACF,UAAU;AAAA,IACV,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OACE;AAAA,IACF,YAAY;AAAA,MACV,EAAE,MAAM,QAAQ,aAAa,2CAA2C;AAAA,MACxE,EAAE,MAAM,cAAc,aAAa,sBAAsB;AAAA,MACzD,EAAE,MAAM,gBAAgB,aAAa,wBAAwB;AAAA,MAC7D,EAAE,MAAM,WAAW,aAAa,iBAAiB;AAAA,MACjD,EAAE,MAAM,aAAa,aAAa,mBAAmB;AAAA,IACvD;AAAA,IACA,UAAU;AAAA,MACR,EAAE,MAAM,eAAe,aAAa,gCAAgC;AAAA,MACpE,EAAE,MAAM,UAAU,aAAa,2BAA2B;AAAA,MAC1D,EAAE,MAAM,SAAS,aAAa,2BAA2B;AAAA,MACzD;AAAA,QACE,MAAM;AAAA,QACN,aAAa;AAAA,MACf;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,aAAa;AAAA,MACf;AAAA,IACF;AAAA,IACA,WAAW,EAAE,gBAAgB,oBAAoB;AAAA,EACnD;AAAA,EACA,kBAAkB;AAAA,IAChB,OAAO;AAAA,IACP,aACE;AAAA,IACF,UAAU;AAAA,IACV,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OACE;AAAA,IACF,YAAY;AAAA,MACV,EAAE,MAAM,QAAQ,aAAa,2CAA2C;AAAA,MACxE,EAAE,MAAM,cAAc,aAAa,sBAAsB;AAAA,MACzD,EAAE,MAAM,gBAAgB,aAAa,wBAAwB;AAAA,MAC7D,EAAE,MAAM,WAAW,aAAa,mBAAmB;AAAA,MACnD,EAAE,MAAM,aAAa,aAAa,qBAAqB;AAAA,MACvD,EAAE,MAAM,QAAQ,aAAa,cAAc;AAAA,MAC3C,EAAE,MAAM,UAAU,aAAa,gBAAgB;AAAA,IACjD;AAAA,IACA,UAAU;AAAA,MACR,EAAE,MAAM,eAAe,aAAa,gCAAgC;AAAA,MACpE,EAAE,MAAM,UAAU,aAAa,2BAA2B;AAAA,MAC1D,EAAE,MAAM,SAAS,aAAa,2BAA2B;AAAA,MACzD;AAAA,QACE,MAAM;AAAA,QACN,aAAa;AAAA,MACf;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,aAAa;AAAA,MACf;AAAA,IACF;AAAA,IACA,WAAW,EAAE,aAAa,iBAAiB;AAAA,EAC7C;AACF,CAAC;AAqDM,SAAS,iBAAiB,KAK/B;AACA,QAAM,YAAY,IAAI,eAClB,IAAI,KAAK,IAAI,YAAY,EAAE,QAAQ,IACnC,IAAI,eACF,IAAI,KAAK,IAAI,YAAY,EAAE,QAAQ,IACnC;AACN,SAAO;AAAA,IACL,MAAM;AAAA,IACN,IAAI,IAAI;AAAA,IACR,YAAY;AAAA,MACV,MAAM,IAAI,QAAQ;AAAA,MAClB,WAAW,IAAI,aAAa;AAAA,MAC5B,QAAQ,IAAI,UAAU;AAAA,MACtB,iBAAiB,IAAI,oBAAoB;AAAA,MACzC,aAAa,oBAAoB,IAAI,YAAY;AAAA,MACjD,gBAAgB,oBAAoB,IAAI,eAAe;AAAA,MACvD,WAAW,IAAI,eAAe,IAAI,KAAK,IAAI,YAAY,EAAE,QAAQ,IAAI;AAAA,IACvE;AAAA,IACA,YAAY,OAAO,SAAS,SAAS,IAAI,YAAY;AAAA,EACvD;AACF;AAEA,SAAS,uBACP,KACwC;AACxC,QAAM,cAAc,YAAY,IAAI,WAAW;AAC/C,QAAM,SAAS,YAAY,IAAI,MAAM;AACrC,QAAM,QAAQ,YAAY,IAAI,KAAK;AACnC,QAAM,cAAc,gBAAgB,IAAI,WAAW;AACnD,QAAM,kBAAkB,gBAAgB,IAAI,iBAAiB;AAC7D,SAAO;AAAA,IACL,MAAM,IAAI;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,kBAAkB;AAAA,EACpB;AACF;AAEO,SAAS,yBACd,KACA,OACc;AACd,QAAM,OAAO,uBAAuB,GAAG;AACvC,QAAM,KAAK,aAAa,IAAI,UAAU;AACtC,QAAM,QAAQ,YAAY,IAAI,KAAK;AACnC,QAAM,qBAAqB;AAAA,IACzB,GAAG;AAAA,IACH,YAAY,IAAI;AAAA,IAChB,cAAc,IAAI,iBAAiB;AAAA,EACrC;AACA,MAAI,UAAU,qBAAqB;AACjC,WAAO,aAAa,kBAAkB,0BAA0B;AAAA,MAC9D;AAAA,MACA;AAAA,MACA,YAAY;AAAA,IACd,CAAC;AAAA,EACH;AACA,QAAM,WAAW;AACjB,QAAM,kBAAkB;AAAA,IACtB,GAAG;AAAA,IACH,SAAS,SAAS;AAAA,IAClB,WAAW,SAAS,cAAc;AAAA,EACpC;AACA,MAAI,UAAU,kBAAkB;AAC9B,WAAO,aAAa,kBAAkB,uBAAuB;AAAA,MAC3D;AAAA,MACA;AAAA,MACA,YAAY;AAAA,IACd,CAAC;AAAA,EACH;AACA,QAAM,QAAQ;AACd,SAAO,aAAa,kBAAkB,oBAAoB;AAAA,IACxD;AAAA,IACA;AAAA,IACA,YAAY;AAAA,MACV,GAAG;AAAA,MACH,MAAM,MAAM;AAAA,MACZ,QAAQ,MAAM,WAAW;AAAA,IAC3B;AAAA,EACF,CAAC;AACH;AAEA,IAAM,kBAAkB;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,EAAE,KAAK,GAAG;AAEV,IAAM,wBAAwB;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,IAAM,iBAGF;AAAA,EACF,mBAAmB,sBAAsB,KAAK,GAAG;AAAA,EACjD,gBAAgB,CAAC,GAAG,uBAAuB,YAAY,YAAY,EAAE;AAAA,IACnE;AAAA,EACF;AAAA,EACA,aAAa;AAAA,IACX,GAAG;AAAA,IACH;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,GAAG;AACZ;AAEO,IAAM,KAAK;AAElB,SAAS,WACP,QACA,OACe;AACf,MAAI,CAAC,QAAQ;AACX,WAAO;AAAA,EACT;AACA,aAAW,UAAU,QAAQ;AAC3B,QACE,WAAW,UACX,OAAO,UAAU,SACjB,OAAO,OAAO,QACd,OAAO,OAAO,UAAU,UACxB;AACA,aAAO,OAAO;AAAA,IAChB;AAAA,EACF;AACA,SAAO;AACT;AAEO,IAAM,mBAAN,MAAM,0BAAyB,cAGpC;AAAA,EACA,OAAgB,KAAK;AAAA,EAErB,OAAgB,YAAY;AAAA,EAE5B,OAAgB,UAAU,qBAAqB,gBAAgB;AAAA,EAE/D,OAAO,OAAO,OAAgB,KAA0C;AACtE,UAAM,SAAS,aAAa,MAAM,KAAK;AACvC,WAAO,IAAI;AAAA,MACT;AAAA,QACE,aAAa,OAAO;AAAA,QACpB,YAAY,OAAO;AAAA,QACnB,cAAc,OAAO;AAAA,QACrB,WAAW,OAAO;AAAA,MACpB;AAAA,MACA,EAAE,aAAa,OAAO,YAAY;AAAA,MAClC;AAAA,IACF;AAAA,EACF;AAAA,EAES,KAAK;AAAA,EACI,cAAc;AAAA,EAExB,eAAuC;AAC7C,WAAO;AAAA,MACL,eAAe,UAAU,KAAK,MAAM,WAAW;AAAA,MAC/C,cAAc,mBAAmB,UAAU;AAAA,IAC7C;AAAA,EACF;AAAA,EAEQ,aAAqB;AAC3B,WAAO,KAAK,SAAS,cAAc;AAAA,EACrC;AAAA,EAEQ,cAAsB;AAC5B,WAAO,GAAG,QAAQ,IAAI,KAAK,WAAW,CAAC,IAAI,KAAK,SAAS,WAAW;AAAA,EACtE;AAAA,EAEQ,WACN,SACA,UACuB;AACvB,UAAM,QAAQ,QAAQ,aAAa,QAAQ;AAC3C,WAAO,SAAS,MAAM,WAAW,IAAI,MAAM,CAAC,IAAI;AAAA,EAClD;AAAA,EAEA,MAAc,mBACZ,OACA,SACA,QACoD;AACpD,UAAM,MAAM,IAAI,IAAI,GAAG,KAAK,YAAY,CAAC,YAAY;AACrD,QAAI,aAAa,IAAI,UAAU,eAAe;AAC9C,QAAI,aAAa,IAAI,SAAS,OAAO,UAAU,CAAC;AAChD,UAAM,SAAS,KAAK,WAAW,SAAS,oBAAoB,GAAG;AAC/D,UAAM,kBAAkB,WAAW,QAAQ,iBAAiB;AAC5D,QAAI,oBAAoB,MAAM;AAC5B,UAAI,aAAa;AAAA,QACf;AAAA,QACA,KAAK,UAAU,CAAC,eAAe,CAAC;AAAA,MAClC;AAAA,IACF;AACA,QAAI,OAAO;AACT,UAAI,aAAa,IAAI,SAAS,KAAK;AAAA,IACrC;AACA,UAAM,MAAM,MAAM,KAAK;AAAA,MACrB,IAAI,SAAS;AAAA,MACb;AAAA,QACE,UAAU;AAAA,QACV,SAAS,KAAK,aAAa;AAAA,QAC3B;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,MACL,OAAO,IAAI,KAAK,QAAQ,CAAC;AAAA,MACzB,MAAM,IAAI,KAAK,QAAQ,SAAS,SAAS;AAAA,IAC3C;AAAA,EACF;AAAA,EAEA,MAAc,kBACZ,OACA,WACA,OACA,QACoD;AACpD,UAAM,MAAM,IAAI,IAAI,GAAG,KAAK,YAAY,CAAC,WAAW;AACpD,QAAI,aAAa,IAAI,SAAS,eAAe,KAAK,CAAC;AACnD,QAAI,aAAa,IAAI,kBAAkB,GAAG;AAC1C,QAAI,aAAa,IAAI,UAAU,eAAe,KAAK,CAAC;AACpD,QAAI,aAAa;AAAA,MACf;AAAA,MACA,KAAK,UAAU,EAAE,OAAO,UAAU,OAAO,OAAO,UAAU,MAAM,CAAC;AAAA,IACnE;AACA,QAAI,aAAa,IAAI,SAAS,OAAO,UAAU,CAAC;AAChD,QAAI,OAAO;AACT,UAAI,aAAa,IAAI,SAAS,KAAK;AAAA,IACrC;AACA,UAAM,MAAM,MAAM,KAAK,IAAgC,IAAI,SAAS,GAAG;AAAA,MACrE,UAAU;AAAA,MACV,SAAS,KAAK,aAAa;AAAA,MAC3B;AAAA,IACF,CAAC;AACD,WAAO;AAAA,MACL,OAAO,IAAI,KAAK,QAAQ,CAAC;AAAA,MACzB,MAAM,IAAI,KAAK,QAAQ,SAAS,SAAS;AAAA,IAC3C;AAAA,EACF;AAAA,EAEA,MAAc,eACZ,SACA,OACe;AACf,eAAW,OAAO,OAAO;AACvB,YAAM,QAAQ,OAAO,iBAAiB,GAAG,CAAC;AAAA,IAC5C;AAAA,EACF;AAAA,EAEA,MAAc,cACZ,SACA,OACA,OACe;AACf,eAAW,OAAO,OAAO;AACvB,YAAM,QAAQ,OAAO,yBAAyB,KAAK,KAAK,CAAC;AAAA,IAC3D;AAAA,EACF;AAAA,EAEA,MAAc,sBACZ,SACA,OACA,QACe;AACf,QAAI,gBAAgB,IAAI,KAAK,GAAG;AAC9B,YAAM,QAAQ,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC,YAAY,KAAK,CAAC,EAAE,CAAC;AACzD;AAAA,IACF;AACA,QAAI,CAAC,QAAQ;AACX;AAAA,IACF;AACA,UAAM,QAAQ,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,oBAAoB,EAAE,CAAC;AAAA,EAC9D;AAAA,EAEA,MAAM,KACJ,SACA,SACA,QACqB;AACrB,UAAM,SAAS,oBAAoB,QAAQ,MAAM,IAC7C,QAAQ,SACR;AACJ,UAAM,SAAS,QAAQ,SAAS;AAChC,UAAM,eAAe,KAAK,SAAS,gBAAgB;AACnD,UAAM,YAAY,aAAa,SAAS,YAAY;AAEpD,UAAM,SAAS;AAAA,MACb,CAAC,MAAM;AAAA,MACP;AAAA,MACA,KAAK,SAAS;AAAA,IAChB;AAEA,WAAO,gBAAsC;AAAA,MAC3C;AAAA,MACA;AAAA,MACA;AAAA,MACA,QAAQ,KAAK;AAAA,MACb,WAAW,OAAO,OAAO,MAAM,QAAQ;AACrC,YAAI,UAAU,aAAa;AACzB,iBAAO,KAAK,mBAAmB,MAAM,SAAS,GAAG;AAAA,QACnD;AACA,eAAO,KAAK,kBAAkB,OAAO,WAAW,MAAM,GAAG;AAAA,MAC3D;AAAA,MACA,YAAY,OAAO,OAAO,OAAO,SAAS;AACxC,YAAI,SAAS,MAAM;AACjB,gBAAM,KAAK,sBAAsB,SAAS,OAAO,MAAM;AAAA,QACzD;AACA,YAAI,UAAU,aAAa;AACzB,gBAAM,KAAK,eAAe,SAAS,KAAuB;AAC1D;AAAA,QACF;AACA,cAAM,KAAK;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,QAGF;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AACF;;;AC3yBA,IAAO,gBAAQ;","names":["startMs"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rawdash/connector-meta-ads",
3
- "version": "0.28.0",
3
+ "version": "0.29.0",
4
4
  "description": "Rawdash connector for Meta (Facebook + Instagram) Ads — campaigns and daily campaign/adset/ad insights",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -24,15 +24,15 @@
24
24
  },
25
25
  "dependencies": {
26
26
  "zod": "^4.4.3",
27
- "@rawdash/core": "0.28.0"
27
+ "@rawdash/core": "0.29.0"
28
28
  },
29
29
  "devDependencies": {
30
30
  "fast-check": "^4.8.0",
31
31
  "tsup": "^8.0.0",
32
32
  "typescript": "^5.7.2",
33
33
  "vitest": "^4.1.4",
34
- "@rawdash/connector-shared": "0.3.1",
35
- "@rawdash/connector-test-utils": "0.0.10"
34
+ "@rawdash/connector-test-utils": "0.0.10",
35
+ "@rawdash/connector-shared": "0.3.1"
36
36
  },
37
37
  "scripts": {
38
38
  "build": "tsup",