@koda-sl/baker-cli 0.512.0 → 0.514.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 +3 -3
- package/dist/account-D6LEVVWT.js +82 -0
- package/dist/account-D6LEVVWT.js.map +1 -0
- package/dist/account-SGK3IJ6N.js +82 -0
- package/dist/account-SGK3IJ6N.js.map +1 -0
- package/dist/account-assets-J5Z4URLQ.js +290 -0
- package/dist/account-level-4SBYYHRS.js +611 -0
- package/dist/account-level-4SBYYHRS.js.map +1 -0
- package/dist/account-reads-2MSUVG46.js +219 -0
- package/dist/account-users-MHC7AD6K.js +64 -0
- package/dist/account-users-MHC7AD6K.js.map +1 -0
- package/dist/account-writes-QQQC54WC.js +68 -0
- package/dist/account-writes-QQQC54WC.js.map +1 -0
- package/dist/accounts-2O6I7YBN.js +80 -0
- package/dist/accounts-2O6I7YBN.js.map +1 -0
- package/dist/accounts-QNE5QWBG.js +701 -0
- package/dist/accounts-QNE5QWBG.js.map +1 -0
- package/dist/actions-D7OWCP36.js +55 -0
- package/dist/activecampaign-LBS4M4C4.js +938 -0
- package/dist/activecampaign-LBS4M4C4.js.map +1 -0
- package/dist/ad-group-writes-CJTTUUIT.js +172 -0
- package/dist/ad-group-writes-CJTTUUIT.js.map +1 -0
- package/dist/ad-groups-HLGSVH74.js +70 -0
- package/dist/ad-groups-HLGSVH74.js.map +1 -0
- package/dist/ad-writes-3UE3U7VE.js +176 -0
- package/dist/ad-writes-3UE3U7VE.js.map +1 -0
- package/dist/add-HFQB2NTU.js +107 -0
- package/dist/add-HFQB2NTU.js.map +1 -0
- package/dist/add-IUYE5H6A.js +305 -0
- package/dist/add-IUYE5H6A.js.map +1 -0
- package/dist/ads-HLUJB5SQ.js +48 -0
- package/dist/ads-MAOI7S6V.js +115 -0
- package/dist/ads-PYNDMHUD.js +79 -0
- package/dist/ads-PYNDMHUD.js.map +1 -0
- package/dist/adsets-QRKICEFD.js +182 -0
- package/dist/advertisers-XXM6POO5.js +123 -0
- package/dist/advertisers-XXM6POO5.js.map +1 -0
- package/dist/analytics-AULKYNNT.js +447 -0
- package/dist/analytics-AULKYNNT.js.map +1 -0
- package/dist/analytics-DNDTC643.js +2437 -0
- package/dist/analytics-DNDTC643.js.map +1 -0
- package/dist/asset-writes-JLVUAVUZ.js +50 -0
- package/dist/assign-2GQVQKKO.js +80 -0
- package/dist/assign-2GQVQKKO.js.map +1 -0
- package/dist/audience-size-C3YHQPZB.js +76 -0
- package/dist/audience-size-C3YHQPZB.js.map +1 -0
- package/dist/audience-writes-CDKQB3SM.js +327 -0
- package/dist/audience-writes-CDKQB3SM.js.map +1 -0
- package/dist/audiences-CDDI5HR5.js +86 -0
- package/dist/audiences-M3A3QK52.js +280 -0
- package/dist/audiences-M3A3QK52.js.map +1 -0
- package/dist/audit-FLPJ7GTE.js +154 -0
- package/dist/audit-FLPJ7GTE.js.map +1 -0
- package/dist/autocomplete-ZYMHHV7E.js +114 -0
- package/dist/autocomplete-ZYMHHV7E.js.map +1 -0
- package/dist/avatars-WZ2JDMHH.js +38 -0
- package/dist/bid-pricing-H3M63PTW.js +103 -0
- package/dist/bid-pricing-H3M63PTW.js.map +1 -0
- package/dist/brand-IZTTBZII.js +22 -0
- package/dist/brief-OPQ7ICOY.js +543 -0
- package/dist/brief-OPQ7ICOY.js.map +1 -0
- package/dist/campaign-groups-IQSDKOVI.js +99 -0
- package/dist/campaign-groups-IQSDKOVI.js.map +1 -0
- package/dist/campaign-writes-APAAV4ZU.js +168 -0
- package/dist/campaign-writes-APAAV4ZU.js.map +1 -0
- package/dist/campaign-writes-BN4PZP4Z.js +176 -0
- package/dist/campaign-writes-BN4PZP4Z.js.map +1 -0
- package/dist/campaigns-4Y3JQC2U.js +111 -0
- package/dist/campaigns-GA34MEN2.js +78 -0
- package/dist/campaigns-GA34MEN2.js.map +1 -0
- package/dist/campaigns-HES5MDUG.js +130 -0
- package/dist/campaigns-K2FVD3XW.js +113 -0
- package/dist/campaigns-K2FVD3XW.js.map +1 -0
- package/dist/canvas-IQ753Q2O.js +46 -0
- package/dist/capabilities-ZITW4RF7.js +147 -0
- package/dist/capabilities-ZITW4RF7.js.map +1 -0
- package/dist/catalogs-TGLSJEK5.js +781 -0
- package/dist/catalogs-TGLSJEK5.js.map +1 -0
- package/dist/catalogs-ZR7DECMG.js +130 -0
- package/dist/catalogs-ZR7DECMG.js.map +1 -0
- package/dist/changes-VWKTM27U.js +135 -0
- package/dist/chatgpt-OPOD2WHB.js +47 -0
- package/dist/chats-AL2SYBJO.js +279 -0
- package/dist/chats-AL2SYBJO.js.map +1 -0
- package/dist/check-U2YLRD6H.js +107 -0
- package/dist/check-U2YLRD6H.js.map +1 -0
- package/dist/chunk-2GYCWRF3.js +170 -0
- package/dist/chunk-2L7F7KR4.js +61 -0
- package/dist/chunk-44ZRCSR7.js +228 -0
- package/dist/chunk-46YLDIQG.js +4254 -0
- package/dist/chunk-4EDWXZ3I.js +363 -0
- package/dist/chunk-4FFBGR5L.js +66 -0
- package/dist/chunk-4UQRXKYJ.js +219 -0
- package/dist/chunk-4UZUUSVL.js +592 -0
- package/dist/chunk-4XHCEYHI.js +831 -0
- package/dist/chunk-4Z6PVR3M.js +479 -0
- package/dist/chunk-5Y5FZV2W.js +122 -0
- package/dist/chunk-6HJXLGDV.js +392 -0
- package/dist/chunk-6S5CWGDA.js +429 -0
- package/dist/chunk-6VYROVWO.js +494 -0
- package/dist/chunk-7BUIOP36.js +287 -0
- package/dist/chunk-AQRXXC6E.js +319 -0
- package/dist/chunk-ARBXT73X.js +387 -0
- package/dist/chunk-BEZHQIEI.js +97 -0
- package/dist/chunk-BFURWHVR.js +547 -0
- package/dist/chunk-BX6B25KK.js +358 -0
- package/dist/chunk-BX6B25KK.js.map +1 -0
- package/dist/chunk-DSCEXBDR.js +676 -0
- package/dist/chunk-FLWDFPXE.js +23 -0
- package/dist/chunk-FRNBRN2C.js +1646 -0
- package/dist/chunk-G4FG5EQO.js +110 -0
- package/dist/chunk-G4FG5EQO.js.map +1 -0
- package/dist/chunk-GE3VT3CA.js +51 -0
- package/dist/chunk-GW2WRZZA.js +24026 -0
- package/dist/chunk-GW2WRZZA.js.map +1 -0
- package/dist/chunk-HGBJQGO5.js +142 -0
- package/dist/chunk-I7AYMJZA.js +783 -0
- package/dist/chunk-IRM273C7.js +207 -0
- package/dist/chunk-IWKD56AY.js +156 -0
- package/dist/chunk-JLAQWMVF.js +3868 -0
- package/dist/chunk-JOE42ZW6.js +2984 -0
- package/dist/chunk-JOE42ZW6.js.map +1 -0
- package/dist/chunk-K6NULQN7.js +57 -0
- package/dist/chunk-L2BRAC2J.js +19 -0
- package/dist/chunk-LGMI4LSY.js +455 -0
- package/dist/chunk-NB2FU6EZ.js +179 -0
- package/dist/chunk-NKQWDEB7.js +115 -0
- package/dist/chunk-NKQWDEB7.js.map +1 -0
- package/dist/chunk-NWF2LRKO.js +188 -0
- package/dist/chunk-OG47L5GG.js +275 -0
- package/dist/chunk-OR6KVLI5.js +169 -0
- package/dist/chunk-PC3FJEZH.js +7211 -0
- package/dist/chunk-PC3FJEZH.js.map +1 -0
- package/dist/chunk-Q3GSVLE5.js +4274 -0
- package/dist/chunk-Q3GSVLE5.js.map +1 -0
- package/dist/chunk-QDQZ62FL.js +207 -0
- package/dist/chunk-QIOFOD4K.js +344 -0
- package/dist/chunk-R2SYF5FC.js +249 -0
- package/dist/chunk-RTH2I55B.js +942 -0
- package/dist/chunk-S6VFDNSQ.js +63 -0
- package/dist/chunk-SG2TDQEE.js +150 -0
- package/dist/chunk-SOPVIIUW.js +68 -0
- package/dist/chunk-SOPVIIUW.js.map +1 -0
- package/dist/chunk-T3P6O2VU.js +89 -0
- package/dist/chunk-TXDE2QJ3.js +604 -0
- package/dist/chunk-UDIEKCIR.js +203 -0
- package/dist/chunk-USCADMM3.js +163 -0
- package/dist/chunk-UUGISMNE.js +910 -0
- package/dist/chunk-VDPOCAQ2.js +197 -0
- package/dist/chunk-VEJ3QX6M.js +103 -0
- package/dist/chunk-VFJB7OG6.js +91 -0
- package/dist/chunk-VRIPVLE7.js +8954 -0
- package/dist/chunk-WFY7KSHB.js +274 -0
- package/dist/chunk-WLESMH4J.js +45 -0
- package/dist/chunk-XHQYFNNK.js +846 -0
- package/dist/chunk-XOQNM7I4.js +396 -0
- package/dist/chunk-XOQNM7I4.js.map +1 -0
- package/dist/chunk-YILZR3XI.js +224 -0
- package/dist/chunk-YJFLXOH4.js +191 -0
- package/dist/chunk-YWHZYR6S.js +297 -0
- package/dist/claim-GOLUTAJ3.js +200 -0
- package/dist/claim-GOLUTAJ3.js.map +1 -0
- package/dist/cli.js +30 -30
- package/dist/companies-4AJJGTMC.js +198 -0
- package/dist/companies-4AJJGTMC.js.map +1 -0
- package/dist/company-MOSPLRZJ.js +308 -0
- package/dist/company-MOSPLRZJ.js.map +1 -0
- package/dist/complete-777BDV45.js +84 -0
- package/dist/complete-777BDV45.js.map +1 -0
- package/dist/connect-3YY7MF3I.js +45 -0
- package/dist/conversation-KROMHDHK.js +96 -0
- package/dist/conversation-KROMHDHK.js.map +1 -0
- package/dist/conversion-report-JZHI2IHY.js +127 -0
- package/dist/conversion-report-JZHI2IHY.js.map +1 -0
- package/dist/conversion-upload-command-7RSZ5ZQU.js +439 -0
- package/dist/conversion-writes-SNCLQDXQ.js +122 -0
- package/dist/conversion-writes-SNCLQDXQ.js.map +1 -0
- package/dist/conversions-43EYP2W2.js +140 -0
- package/dist/conversions-43EYP2W2.js.map +1 -0
- package/dist/conversions-EUOWILCQ.js +78 -0
- package/dist/conversions-EUOWILCQ.js.map +1 -0
- package/dist/copy-writes-CIHIFLY6.js +170 -0
- package/dist/copy-writes-CIHIFLY6.js.map +1 -0
- package/dist/create-3WOZO2XT.js +239 -0
- package/dist/create-3WOZO2XT.js.map +1 -0
- package/dist/create-7IFYLX7P.js +210 -0
- package/dist/create-7IFYLX7P.js.map +1 -0
- package/dist/create-FOHIQC3E.js +62 -0
- package/dist/create-FOHIQC3E.js.map +1 -0
- package/dist/create-UWQSQTK6.js +135 -0
- package/dist/create-UWQSQTK6.js.map +1 -0
- package/dist/creatives-2DR3QHJW.js +58 -0
- package/dist/creatives-BB6S2OF6.js +89 -0
- package/dist/creatives-KUO67WYA.js +223 -0
- package/dist/creatives-KUO67WYA.js.map +1 -0
- package/dist/credentials-IP4L37XP.js +290 -0
- package/dist/credentials-IP4L37XP.js.map +1 -0
- package/dist/critique-LGDNMLBL.js +232 -0
- package/dist/critique-LGDNMLBL.js.map +1 -0
- package/dist/critique-LJMFAURK.js +1451 -0
- package/dist/critique-LJMFAURK.js.map +1 -0
- package/dist/crm-BNYW7PF7.js +323 -0
- package/dist/crm-BNYW7PF7.js.map +1 -0
- package/dist/crm-J53V2QTN.js +227 -0
- package/dist/crm-J53V2QTN.js.map +1 -0
- package/dist/currency-X7Y6TSLF.js +88 -0
- package/dist/custom-conversions-HAGG72LF.js +128 -0
- package/dist/customers-663K2OCP.js +59 -0
- package/dist/delete-5W3YVA4L.js +67 -0
- package/dist/delete-5W3YVA4L.js.map +1 -0
- package/dist/delete-6U7E6YUW.js +57 -0
- package/dist/delete-6U7E6YUW.js.map +1 -0
- package/dist/delete-G4NY6ZG6.js +69 -0
- package/dist/delete-G4NY6ZG6.js.map +1 -0
- package/dist/demographics-4Y5LTCU4.js +141 -0
- package/dist/demographics-4Y5LTCU4.js.map +1 -0
- package/dist/discard-RKCZNKMN.js +69 -0
- package/dist/discard-RKCZNKMN.js.map +1 -0
- package/dist/discover-6DORQWXY.js +172 -0
- package/dist/domain-PLA2O4FW.js +101 -0
- package/dist/domain-PLA2O4FW.js.map +1 -0
- package/dist/download-4EFLVSA4.js +172 -0
- package/dist/download-4EFLVSA4.js.map +1 -0
- package/dist/draft-4V7W5DV2.js +310 -0
- package/dist/draft-4V7W5DV2.js.map +1 -0
- package/dist/draft-HY3U2GHU.js +283 -0
- package/dist/draft-HY3U2GHU.js.map +1 -0
- package/dist/draft-JMNAGW7C.js +220 -0
- package/dist/draft-JMNAGW7C.js.map +1 -0
- package/dist/draft-R6EKORNS.js +308 -0
- package/dist/draft-TRPQCJ4B.js +335 -0
- package/dist/draft-TRPQCJ4B.js.map +1 -0
- package/dist/experiments-4V242DG2.js +236 -0
- package/dist/experiments-4V242DG2.js.map +1 -0
- package/dist/facets-WXJ2MCU6.js +117 -0
- package/dist/facets-WXJ2MCU6.js.map +1 -0
- package/dist/feedback-IRSRPXV6.js +111 -0
- package/dist/feedback-IRSRPXV6.js.map +1 -0
- package/dist/feedback-record-DT3R4NA3.js +265 -0
- package/dist/feedback-record-DT3R4NA3.js.map +1 -0
- package/dist/fetch-YLQP5VQL.js +179 -0
- package/dist/fetch-YLQP5VQL.js.map +1 -0
- package/dist/fit-BEFCY2B4.js +65 -0
- package/dist/flows-FDMF3F3P.js +250 -0
- package/dist/flows-FDMF3F3P.js.map +1 -0
- package/dist/fonts-C6XN5667.js +39 -0
- package/dist/forecast-BTPIXEQ5.js +169 -0
- package/dist/forecast-ZTXZD3HD.js +121 -0
- package/dist/forecast-ZTXZD3HD.js.map +1 -0
- package/dist/funnel-7KOEX7YM.js +397 -0
- package/dist/funnel-7KOEX7YM.js.map +1 -0
- package/dist/get-KBSKPTAV.js +59 -0
- package/dist/get-KBSKPTAV.js.map +1 -0
- package/dist/get-OSVQVMP6.js +82 -0
- package/dist/get-OSVQVMP6.js.map +1 -0
- package/dist/google-M6BJ22UL.js +3267 -0
- package/dist/google-ads-XCJVAXWF.js +198 -0
- package/dist/google-ads-XCJVAXWF.js.map +1 -0
- package/dist/history-3NTM5SFN.js +69 -0
- package/dist/history-3NTM5SFN.js.map +1 -0
- package/dist/history-4TN2A6BL.js +136 -0
- package/dist/history-4TN2A6BL.js.map +1 -0
- package/dist/history-5IWW2X33.js +102 -0
- package/dist/history-5IWW2X33.js.map +1 -0
- package/dist/hubspot-HMVSRJQZ.js +782 -0
- package/dist/hubspot-HMVSRJQZ.js.map +1 -0
- package/dist/ideas-M66FU45H.js +45 -0
- package/dist/images-MM5OPUKV.js +92 -0
- package/dist/ingest-GU3WEBN3.js +110 -0
- package/dist/ingest-GU3WEBN3.js.map +1 -0
- package/dist/ingest-KMLYCFF4.js +26 -0
- package/dist/insights-CQCWOCIT.js +132 -0
- package/dist/insights-CQCWOCIT.js.map +1 -0
- package/dist/insights-XZL2EM53.js +439 -0
- package/dist/insights-XZL2EM53.js.map +1 -0
- package/dist/inspect-4JO2J3D6.js +192 -0
- package/dist/inspect-4JO2J3D6.js.map +1 -0
- package/dist/instant-experiences-RM4MLJTL.js +260 -0
- package/dist/instant-experiences-RM4MLJTL.js.map +1 -0
- package/dist/intent-YCQCUCV3.js +95 -0
- package/dist/keyword-gap-PUSF7EKY.js +130 -0
- package/dist/keyword-metrics-JXBM22ZD.js +200 -0
- package/dist/keyword-metrics-JXBM22ZD.js.map +1 -0
- package/dist/keywords-for-site-OAYA7KP7.js +131 -0
- package/dist/keywords-for-site-OAYA7KP7.js.map +1 -0
- package/dist/landing-OZU5RRFV.js +28 -0
- package/dist/languages-SLFV73GE.js +40 -0
- package/dist/leads-Q7APIJRQ.js +115 -0
- package/dist/leads-Q7APIJRQ.js.map +1 -0
- package/dist/leads-ULYHTG22.js +540 -0
- package/dist/leads-ULYHTG22.js.map +1 -0
- package/dist/learn-NBBJRMVZ.js +207 -0
- package/dist/learn-NBBJRMVZ.js.map +1 -0
- package/dist/line-item-writes-X3L5NQEH.js +211 -0
- package/dist/line-item-writes-X3L5NQEH.js.map +1 -0
- package/dist/line-items-NJMEPEF5.js +107 -0
- package/dist/link-WZSNUQQ2.js +67 -0
- package/dist/link-WZSNUQQ2.js.map +1 -0
- package/dist/linkedin-55PAID62.js +1316 -0
- package/dist/linkedin-55PAID62.js.map +1 -0
- package/dist/linkedin-person-MVRI7LLC.js +139 -0
- package/dist/linkedin-person-MVRI7LLC.js.map +1 -0
- package/dist/list-6S54IAUX.js +67 -0
- package/dist/list-6S54IAUX.js.map +1 -0
- package/dist/list-QRJ4E3SC.js +154 -0
- package/dist/list-QRJ4E3SC.js.map +1 -0
- package/dist/list-RZUDFLJN.js +306 -0
- package/dist/list-RZUDFLJN.js.map +1 -0
- package/dist/locations-T2OMP6QN.js +40 -0
- package/dist/locations-W3VOYBWL.js +69 -0
- package/dist/locations-W3VOYBWL.js.map +1 -0
- package/dist/log-53SAPZZ3.js +95 -0
- package/dist/log-53SAPZZ3.js.map +1 -0
- package/dist/lookup-S5VG7AWH.js +23 -0
- package/dist/make-ZF3YIMKW.js +1370 -0
- package/dist/make-ZF3YIMKW.js.map +1 -0
- package/dist/map-J37QFJSV.js +436 -0
- package/dist/map-J37QFJSV.js.map +1 -0
- package/dist/mcp-YRIFPYNU.js +717 -0
- package/dist/mcp-YRIFPYNU.js.map +1 -0
- package/dist/media-P4T4ZBNS.js +18 -0
- package/dist/media-sources-EQI3U4TC.js +35 -0
- package/dist/members-GYVMSCKL.js +47 -0
- package/dist/members-GYVMSCKL.js.map +1 -0
- package/dist/meta-IPLWNYC7.js +122 -0
- package/dist/metrics-4YVJHYRI.js +152 -0
- package/dist/normalize-MFN3WCH5.js +144 -0
- package/dist/normalize-MFN3WCH5.js.map +1 -0
- package/dist/options-DLHDHO7H.js +125 -0
- package/dist/options-DLHDHO7H.js.map +1 -0
- package/dist/organization-C2VOA2SD.js +949 -0
- package/dist/organization-C2VOA2SD.js.map +1 -0
- package/dist/page-photos-OQ5QATQH.js +118 -0
- package/dist/page-photos-OQ5QATQH.js.map +1 -0
- package/dist/performance-NIF3CESZ.js +27 -0
- package/dist/performance-T6TBVHVU.js +5541 -0
- package/dist/performance-T6TBVHVU.js.map +1 -0
- package/dist/pixels-LQAJOZIM.js +62 -0
- package/dist/pixels-LQAJOZIM.js.map +1 -0
- package/dist/plan-7UF76Q6U.js +208 -0
- package/dist/plan-7UF76Q6U.js.map +1 -0
- package/dist/preview-LRJOQ7LM.js +218 -0
- package/dist/preview-LRJOQ7LM.js.map +1 -0
- package/dist/promoted-tweet-writes-Y6BBOWDL.js +183 -0
- package/dist/promoted-tweet-writes-Y6BBOWDL.js.map +1 -0
- package/dist/promoted-tweets-RJT53DRM.js +119 -0
- package/dist/publish-IOTMJCCY.js +344 -0
- package/dist/publish-IOTMJCCY.js.map +1 -0
- package/dist/query-XZMDGRO5.js +62 -0
- package/dist/questionnaire-DWEH5II4.js +94 -0
- package/dist/questionnaire-DWEH5II4.js.map +1 -0
- package/dist/reddit-JIHMAKAQ.js +29 -0
- package/dist/refresh-OQZ263CS.js +97 -0
- package/dist/refresh-OQZ263CS.js.map +1 -0
- package/dist/release-4CHFURO3.js +63 -0
- package/dist/release-4CHFURO3.js.map +1 -0
- package/dist/relevant-pages-FS7GBVXK.js +99 -0
- package/dist/remove-IJQPHPDI.js +53 -0
- package/dist/remove-IJQPHPDI.js.map +1 -0
- package/dist/reopen-YTLPVKUR.js +66 -0
- package/dist/reopen-YTLPVKUR.js.map +1 -0
- package/dist/rerun-C7NB7B35.js +95 -0
- package/dist/rerun-C7NB7B35.js.map +1 -0
- package/dist/research-PNBACCCP.js +72 -0
- package/dist/research-TLHNPK3O.js +265 -0
- package/dist/research-TLHNPK3O.js.map +1 -0
- package/dist/reset-JVLW7HA2.js +57 -0
- package/dist/reset-JVLW7HA2.js.map +1 -0
- package/dist/reset-Y6FXLAQE.js +57 -0
- package/dist/reset-Y6FXLAQE.js.map +1 -0
- package/dist/resolve-YLRESES5.js +124 -0
- package/dist/resolve-YLRESES5.js.map +1 -0
- package/dist/retry-3V3IGDRR.js +65 -0
- package/dist/retry-3V3IGDRR.js.map +1 -0
- package/dist/retry-AEKQLQMP.js +63 -0
- package/dist/retry-AEKQLQMP.js.map +1 -0
- package/dist/revenue-E4GQIOUY.js +91 -0
- package/dist/revenue-E4GQIOUY.js.map +1 -0
- package/dist/rule-writes-UAVWT4XS.js +525 -0
- package/dist/rule-writes-UAVWT4XS.js.map +1 -0
- package/dist/rules-34BQAWU3.js +99 -0
- package/dist/rules-34BQAWU3.js.map +1 -0
- package/dist/rules-ZVS2MWED.js +217 -0
- package/dist/rules-add-CM5XUYNS.js +465 -0
- package/dist/rules-add-CM5XUYNS.js.map +1 -0
- package/dist/run-MD64RDHZ.js +69 -0
- package/dist/run-MD64RDHZ.js.map +1 -0
- package/dist/run-QCNRR3CA.js +70 -0
- package/dist/scaffold-ad-NADPY3CB.js +1516 -0
- package/dist/scaffold-ad-NADPY3CB.js.map +1 -0
- package/dist/scaffold-static-ad-5YEYYVYH.js +1015 -0
- package/dist/scaffold-static-ad-5YEYYVYH.js.map +1 -0
- package/dist/scaffold-video-W7DF2CBJ.js +888 -0
- package/dist/scaffold-video-W7DF2CBJ.js.map +1 -0
- package/dist/schedule-CTRRF6TS.js +64 -0
- package/dist/schedule-CTRRF6TS.js.map +1 -0
- package/dist/scheduled-agents-OXYAVSIE.js +55 -0
- package/dist/schema-BEUYOGSW.js +135 -0
- package/dist/schema-BEUYOGSW.js.map +1 -0
- package/dist/search-GFJKWPKN.js +177 -0
- package/dist/search-GFJKWPKN.js.map +1 -0
- package/dist/select-S72CCL57.js +106 -0
- package/dist/select-S72CCL57.js.map +1 -0
- package/dist/sends-LAY5C74B.js +349 -0
- package/dist/sends-LAY5C74B.js.map +1 -0
- package/dist/set-prompt-YYFDRXRI.js +198 -0
- package/dist/set-prompt-YYFDRXRI.js.map +1 -0
- package/dist/setup-links-D4JJLESN.js +144 -0
- package/dist/setup-links-D4JJLESN.js.map +1 -0
- package/dist/shopify-T4UWFIQC.js +3958 -0
- package/dist/shopify-T4UWFIQC.js.map +1 -0
- package/dist/site-BBZAUZA3.js +180 -0
- package/dist/site-BBZAUZA3.js.map +1 -0
- package/dist/sources-BUPV7H7F.js +457 -0
- package/dist/sources-BUPV7H7F.js.map +1 -0
- package/dist/sources-FLUGHSVP.js +30 -0
- package/dist/stale-HP54JS5X.js +76 -0
- package/dist/stale-HP54JS5X.js.map +1 -0
- package/dist/status-CLSWQFCQ.js +65 -0
- package/dist/status-CLSWQFCQ.js.map +1 -0
- package/dist/studies-EPO2AM4M.js +250 -0
- package/dist/study-writes-7FP443QQ.js +470 -0
- package/dist/study-writes-7FP443QQ.js.map +1 -0
- package/dist/sync-4YTVVEVC.js +57 -0
- package/dist/sync-4YTVVEVC.js.map +1 -0
- package/dist/sync-definition-OGQ5XTQE.js +69 -0
- package/dist/tags-CZTRQNXV.js +26 -0
- package/dist/tags-FDQXWTCQ.js +23 -0
- package/dist/tags-FDQXWTCQ.js.map +1 -0
- package/dist/tags-R67DKRZ6.js +23 -0
- package/dist/tags-R67DKRZ6.js.map +1 -0
- package/dist/tags-SA2LU6AW.js +227 -0
- package/dist/tags-SA2LU6AW.js.map +1 -0
- package/dist/tags-ZOXX6VRE.js +23 -0
- package/dist/tags-ZOXX6VRE.js.map +1 -0
- package/dist/targeting-NKZFEZD7.js +762 -0
- package/dist/targeting-NKZFEZD7.js.map +1 -0
- package/dist/targeting-criteria-AQYXVSGN.js +113 -0
- package/dist/targets-UUVCEG3F.js +106 -0
- package/dist/targets-UUVCEG3F.js.map +1 -0
- package/dist/testimonials-G3OCNPBY.js +32 -0
- package/dist/themes-FEVYZZCW.js +95 -0
- package/dist/top-companies-BQGWKYUK.js +111 -0
- package/dist/top-companies-BQGWKYUK.js.map +1 -0
- package/dist/transcript-U46AEOEI.js +165 -0
- package/dist/transcript-U46AEOEI.js.map +1 -0
- package/dist/translate-AS7JVKYM.js +609 -0
- package/dist/translate-AS7JVKYM.js.map +1 -0
- package/dist/triggers-ECRBPYYF.js +144 -0
- package/dist/triggers-ECRBPYYF.js.map +1 -0
- package/dist/unlink-VDNCSRJV.js +70 -0
- package/dist/unlink-VDNCSRJV.js.map +1 -0
- package/dist/unstage-ATDXUYP2.js +88 -0
- package/dist/unstage-ATDXUYP2.js.map +1 -0
- package/dist/unstage-ZA3WQBEA.js +96 -0
- package/dist/unstage-ZA3WQBEA.js.map +1 -0
- package/dist/update-FJ6TBV3E.js +101 -0
- package/dist/update-FJ6TBV3E.js.map +1 -0
- package/dist/update-HDBJCSTI.js +66 -0
- package/dist/update-HDBJCSTI.js.map +1 -0
- package/dist/update-U43DJRGV.js +155 -0
- package/dist/update-U43DJRGV.js.map +1 -0
- package/dist/update-Z7TIUBMJ.js +192 -0
- package/dist/update-Z7TIUBMJ.js.map +1 -0
- package/dist/upload-LXCFSRIU.js +129 -0
- package/dist/upload-LXCFSRIU.js.map +1 -0
- package/dist/url-options-Z4BQBEMI.js +131 -0
- package/dist/url-options-Z4BQBEMI.js.map +1 -0
- package/dist/url-params-J7RKP5RB.js +101 -0
- package/dist/url-params-J7RKP5RB.js.map +1 -0
- package/dist/validate-VR3HEOMJ.js +122 -0
- package/dist/validate-VR3HEOMJ.js.map +1 -0
- package/dist/videos-36AXDUGZ.js +41 -0
- package/dist/viewer-check-6HL4OUQY.js +83 -0
- package/dist/web-7BRJRQX7.js +143 -0
- package/dist/web-7BRJRQX7.js.map +1 -0
- package/dist/winning-ads-GMM5W2O4.js +86 -0
- package/dist/write-commands-IM5DPG3R.js +1502 -0
- package/dist/write-commands-IM5DPG3R.js.map +1 -0
- package/dist/write-commands-OGVDWWBV.js +1172 -0
- package/dist/write-commands-OGVDWWBV.js.map +1 -0
- package/dist/x-HGFAL3NY.js +47 -0
- package/package.json +1 -1
- package/dist/account-C635ATVM.js +0 -80
- package/dist/account-C635ATVM.js.map +0 -1
- package/dist/account-WLQBAQMD.js +0 -80
- package/dist/account-WLQBAQMD.js.map +0 -1
- package/dist/account-assets-O7IA37RE.js +0 -290
- package/dist/account-level-EU7BWCO7.js +0 -609
- package/dist/account-level-EU7BWCO7.js.map +0 -1
- package/dist/account-reads-SNOWDHHF.js +0 -219
- package/dist/account-users-XYYENK6X.js +0 -62
- package/dist/account-users-XYYENK6X.js.map +0 -1
- package/dist/account-writes-TU3WJRSI.js +0 -66
- package/dist/account-writes-TU3WJRSI.js.map +0 -1
- package/dist/accounts-A2FBLZ5H.js +0 -78
- package/dist/accounts-A2FBLZ5H.js.map +0 -1
- package/dist/accounts-V2IZZ5KU.js +0 -699
- package/dist/accounts-V2IZZ5KU.js.map +0 -1
- package/dist/actions-Y4SULW2E.js +0 -55
- package/dist/activecampaign-63XG6CLL.js +0 -936
- package/dist/activecampaign-63XG6CLL.js.map +0 -1
- package/dist/ad-group-writes-HLBWN4E4.js +0 -170
- package/dist/ad-group-writes-HLBWN4E4.js.map +0 -1
- package/dist/ad-groups-33NZD6J3.js +0 -68
- package/dist/ad-groups-33NZD6J3.js.map +0 -1
- package/dist/ad-writes-7ZDNDZ2E.js +0 -174
- package/dist/ad-writes-7ZDNDZ2E.js.map +0 -1
- package/dist/add-FOLFVGQO.js +0 -303
- package/dist/add-FOLFVGQO.js.map +0 -1
- package/dist/add-RYB4JSYC.js +0 -105
- package/dist/add-RYB4JSYC.js.map +0 -1
- package/dist/ads-2TVWICGM.js +0 -48
- package/dist/ads-KBE5JXCH.js +0 -115
- package/dist/ads-X3AM6M7L.js +0 -77
- package/dist/ads-X3AM6M7L.js.map +0 -1
- package/dist/adsets-YWBJFPAZ.js +0 -182
- package/dist/advertisers-IGUMSK5Q.js +0 -121
- package/dist/advertisers-IGUMSK5Q.js.map +0 -1
- package/dist/analytics-3DKM7J43.js +0 -445
- package/dist/analytics-3DKM7J43.js.map +0 -1
- package/dist/analytics-R3QDSX2K.js +0 -2435
- package/dist/analytics-R3QDSX2K.js.map +0 -1
- package/dist/asset-writes-72DEVXGO.js +0 -48
- package/dist/assign-EKCGLNZB.js +0 -78
- package/dist/assign-EKCGLNZB.js.map +0 -1
- package/dist/audience-size-IKXK32HU.js +0 -74
- package/dist/audience-size-IKXK32HU.js.map +0 -1
- package/dist/audience-writes-KCHLNFSI.js +0 -325
- package/dist/audience-writes-KCHLNFSI.js.map +0 -1
- package/dist/audiences-QAH42HIM.js +0 -86
- package/dist/audiences-WCDWN64L.js +0 -268
- package/dist/audiences-WCDWN64L.js.map +0 -1
- package/dist/audit-CY76S4V7.js +0 -152
- package/dist/audit-CY76S4V7.js.map +0 -1
- package/dist/autocomplete-G2S5HNUS.js +0 -112
- package/dist/autocomplete-G2S5HNUS.js.map +0 -1
- package/dist/avatars-P4UPNZM4.js +0 -38
- package/dist/bid-pricing-AJT4QU2C.js +0 -101
- package/dist/bid-pricing-AJT4QU2C.js.map +0 -1
- package/dist/brand-HALQWZWB.js +0 -22
- package/dist/brief-NW7TGG2Q.js +0 -541
- package/dist/brief-NW7TGG2Q.js.map +0 -1
- package/dist/campaign-groups-I43KMAJP.js +0 -97
- package/dist/campaign-groups-I43KMAJP.js.map +0 -1
- package/dist/campaign-writes-DLLPFAGG.js +0 -166
- package/dist/campaign-writes-DLLPFAGG.js.map +0 -1
- package/dist/campaign-writes-XO74BALZ.js +0 -174
- package/dist/campaign-writes-XO74BALZ.js.map +0 -1
- package/dist/campaigns-LS65QM6W.js +0 -76
- package/dist/campaigns-LS65QM6W.js.map +0 -1
- package/dist/campaigns-UIYSYCF3.js +0 -111
- package/dist/campaigns-UIYSYCF3.js.map +0 -1
- package/dist/campaigns-XZOQ2VTK.js +0 -130
- package/dist/campaigns-YMM5TQVO.js +0 -111
- package/dist/canvas-IJS7H3VE.js +0 -46
- package/dist/capabilities-XMQPM3UL.js +0 -145
- package/dist/capabilities-XMQPM3UL.js.map +0 -1
- package/dist/catalogs-JLCSFZYC.js +0 -779
- package/dist/catalogs-JLCSFZYC.js.map +0 -1
- package/dist/catalogs-T23RKYFC.js +0 -128
- package/dist/catalogs-T23RKYFC.js.map +0 -1
- package/dist/changes-36C754R4.js +0 -135
- package/dist/chatgpt-3WFOPURY.js +0 -47
- package/dist/chats-ZI6EYDAF.js +0 -277
- package/dist/chats-ZI6EYDAF.js.map +0 -1
- package/dist/check-CHJ2OLAU.js +0 -105
- package/dist/check-CHJ2OLAU.js.map +0 -1
- package/dist/chunk-2TOIY2WN.js +0 -4254
- package/dist/chunk-4GAEB6NK.js +0 -228
- package/dist/chunk-5RVQK3SP.js +0 -494
- package/dist/chunk-5SRUAGTG.js +0 -547
- package/dist/chunk-5XTGG4PV.js +0 -274
- package/dist/chunk-6Z7Y4KQB.js +0 -275
- package/dist/chunk-7J3NZBPV.js +0 -2982
- package/dist/chunk-7J3NZBPV.js.map +0 -1
- package/dist/chunk-AIRD6T4W.js +0 -169
- package/dist/chunk-AQOFQNKQ.js +0 -57
- package/dist/chunk-ARUXGKZO.js +0 -91
- package/dist/chunk-AYPBARKP.js +0 -156
- package/dist/chunk-BRLZX3VP.js +0 -207
- package/dist/chunk-BTJSGJNQ.js +0 -604
- package/dist/chunk-C7QQQVBS.js +0 -51
- package/dist/chunk-CCEDZTP7.js +0 -391
- package/dist/chunk-CCEDZTP7.js.map +0 -1
- package/dist/chunk-D3KLDIPQ.js +0 -45
- package/dist/chunk-DUR7PEJI.js +0 -170
- package/dist/chunk-EDLZGTGN.js +0 -216
- package/dist/chunk-EDLZGTGN.js.map +0 -1
- package/dist/chunk-FFFJBNHA.js +0 -63
- package/dist/chunk-FPWQA7TK.js +0 -344
- package/dist/chunk-G5Y7OIPL.js +0 -392
- package/dist/chunk-GACSA5A6.js +0 -1646
- package/dist/chunk-GMHCHHAA.js +0 -19
- package/dist/chunk-H4NNQGG6.js +0 -66
- package/dist/chunk-HSQRITKG.js +0 -203
- package/dist/chunk-I47BEKYK.js +0 -831
- package/dist/chunk-JND6XSGZ.js +0 -7192
- package/dist/chunk-JND6XSGZ.js.map +0 -1
- package/dist/chunk-KBRJSPIM.js +0 -676
- package/dist/chunk-KQTNUDKD.js +0 -97
- package/dist/chunk-M7HD42M6.js +0 -24017
- package/dist/chunk-M7HD42M6.js.map +0 -1
- package/dist/chunk-MBSFEVOG.js +0 -163
- package/dist/chunk-MGYC2IRC.js +0 -783
- package/dist/chunk-MUXKYSTI.js +0 -297
- package/dist/chunk-MZCTX37B.js +0 -3868
- package/dist/chunk-N7M2TWR5.js +0 -592
- package/dist/chunk-NDFS3APP.js +0 -319
- package/dist/chunk-OBJCNGZL.js +0 -207
- package/dist/chunk-OJGYYH6K.js +0 -179
- package/dist/chunk-P6BKKUSX.js +0 -142
- package/dist/chunk-PLAOK4AK.js +0 -103
- package/dist/chunk-PLBITHV6.js +0 -846
- package/dist/chunk-PNKIOTCK.js +0 -349
- package/dist/chunk-PNKIOTCK.js.map +0 -1
- package/dist/chunk-QBRPM3DQ.js +0 -61
- package/dist/chunk-QMDMTZAW.js +0 -224
- package/dist/chunk-QW6HL7D6.js +0 -429
- package/dist/chunk-R53XMPDO.js +0 -363
- package/dist/chunk-RFIUOQLN.js +0 -89
- package/dist/chunk-RVEY7BTT.js +0 -8954
- package/dist/chunk-SCL7PM6P.js +0 -479
- package/dist/chunk-SM7G7RUA.js +0 -910
- package/dist/chunk-SVBJ56T7.js +0 -455
- package/dist/chunk-TVAIOC77.js +0 -287
- package/dist/chunk-TVTSA4EN.js +0 -188
- package/dist/chunk-VIPKDXGU.js +0 -4229
- package/dist/chunk-VIPKDXGU.js.map +0 -1
- package/dist/chunk-W574NV67.js +0 -191
- package/dist/chunk-XGPU2VDU.js +0 -219
- package/dist/chunk-XQXZJ5IR.js +0 -249
- package/dist/chunk-YMF2RG4Z.js +0 -942
- package/dist/chunk-Z2DRGXBB.js +0 -197
- package/dist/chunk-Z7YMSHRZ.js +0 -23
- package/dist/chunk-ZEC46NWW.js +0 -122
- package/dist/chunk-ZFC562TP.js +0 -387
- package/dist/chunk-ZNRGH4NS.js +0 -150
- package/dist/claim-OUYHVGB4.js +0 -198
- package/dist/claim-OUYHVGB4.js.map +0 -1
- package/dist/companies-XDNSG7ME.js +0 -196
- package/dist/companies-XDNSG7ME.js.map +0 -1
- package/dist/company-4FAYYPEJ.js +0 -306
- package/dist/company-4FAYYPEJ.js.map +0 -1
- package/dist/complete-M2PAUMDP.js +0 -82
- package/dist/complete-M2PAUMDP.js.map +0 -1
- package/dist/connect-PP6Z3F7Y.js +0 -43
- package/dist/conversation-B5NPTLFQ.js +0 -94
- package/dist/conversation-B5NPTLFQ.js.map +0 -1
- package/dist/conversion-report-DKXFHLLX.js +0 -125
- package/dist/conversion-report-DKXFHLLX.js.map +0 -1
- package/dist/conversion-upload-command-KTYAY7IG.js +0 -439
- package/dist/conversion-writes-7ODJDAUP.js +0 -120
- package/dist/conversion-writes-7ODJDAUP.js.map +0 -1
- package/dist/conversions-BKVGNNC6.js +0 -76
- package/dist/conversions-BKVGNNC6.js.map +0 -1
- package/dist/conversions-QRJL6JIO.js +0 -138
- package/dist/conversions-QRJL6JIO.js.map +0 -1
- package/dist/copy-writes-P4WK6OPM.js +0 -168
- package/dist/copy-writes-P4WK6OPM.js.map +0 -1
- package/dist/create-4DWCJ2UA.js +0 -208
- package/dist/create-4DWCJ2UA.js.map +0 -1
- package/dist/create-OYV33CQ6.js +0 -60
- package/dist/create-OYV33CQ6.js.map +0 -1
- package/dist/create-U6JVIYFY.js +0 -237
- package/dist/create-U6JVIYFY.js.map +0 -1
- package/dist/create-V4FNRO63.js +0 -133
- package/dist/create-V4FNRO63.js.map +0 -1
- package/dist/creatives-64FKWEGL.js +0 -221
- package/dist/creatives-64FKWEGL.js.map +0 -1
- package/dist/creatives-JPKA2RHU.js +0 -89
- package/dist/creatives-UJDPD4CI.js +0 -58
- package/dist/credentials-EIPEZ2C2.js +0 -288
- package/dist/credentials-EIPEZ2C2.js.map +0 -1
- package/dist/critique-BADMZAZP.js +0 -230
- package/dist/critique-BADMZAZP.js.map +0 -1
- package/dist/critique-M3DEVEDA.js +0 -1449
- package/dist/critique-M3DEVEDA.js.map +0 -1
- package/dist/crm-GIYSLLY7.js +0 -321
- package/dist/crm-GIYSLLY7.js.map +0 -1
- package/dist/crm-HXKSJRY3.js +0 -225
- package/dist/crm-HXKSJRY3.js.map +0 -1
- package/dist/currency-R4ORE2U3.js +0 -88
- package/dist/custom-conversions-5HJBB5XL.js +0 -128
- package/dist/customers-7J3QGSOJ.js +0 -57
- package/dist/delete-EDWJQMMV.js +0 -65
- package/dist/delete-EDWJQMMV.js.map +0 -1
- package/dist/delete-O24CVQMJ.js +0 -67
- package/dist/delete-O24CVQMJ.js.map +0 -1
- package/dist/delete-QN2OYUCS.js +0 -55
- package/dist/delete-QN2OYUCS.js.map +0 -1
- package/dist/demographics-MPYYF2OI.js +0 -139
- package/dist/demographics-MPYYF2OI.js.map +0 -1
- package/dist/discard-FJCFB6SA.js +0 -67
- package/dist/discard-FJCFB6SA.js.map +0 -1
- package/dist/discover-GSJRZUOC.js +0 -172
- package/dist/domain-UILPSEA5.js +0 -99
- package/dist/domain-UILPSEA5.js.map +0 -1
- package/dist/download-ZMAI7QSG.js +0 -170
- package/dist/download-ZMAI7QSG.js.map +0 -1
- package/dist/draft-5OQT3APG.js +0 -308
- package/dist/draft-5OQT3APG.js.map +0 -1
- package/dist/draft-LPVK6YHI.js +0 -281
- package/dist/draft-LPVK6YHI.js.map +0 -1
- package/dist/draft-MSESA5UE.js +0 -333
- package/dist/draft-MSESA5UE.js.map +0 -1
- package/dist/draft-WYDMLNYA.js +0 -308
- package/dist/draft-XOOSEVY5.js +0 -218
- package/dist/draft-XOOSEVY5.js.map +0 -1
- package/dist/experiments-PVOQUL47.js +0 -234
- package/dist/experiments-PVOQUL47.js.map +0 -1
- package/dist/facets-5RW4Z3D3.js +0 -115
- package/dist/facets-5RW4Z3D3.js.map +0 -1
- package/dist/feedback-GC56R2EJ.js +0 -109
- package/dist/feedback-GC56R2EJ.js.map +0 -1
- package/dist/feedback-record-JX7ZU6J7.js +0 -263
- package/dist/feedback-record-JX7ZU6J7.js.map +0 -1
- package/dist/fetch-WUTXAPBL.js +0 -177
- package/dist/fetch-WUTXAPBL.js.map +0 -1
- package/dist/fit-N7WDGWYZ.js +0 -63
- package/dist/flows-ULZACISF.js +0 -248
- package/dist/flows-ULZACISF.js.map +0 -1
- package/dist/fonts-TTUAP7HS.js +0 -37
- package/dist/forecast-4AG5F7YC.js +0 -169
- package/dist/forecast-RQJJRN5D.js +0 -119
- package/dist/forecast-RQJJRN5D.js.map +0 -1
- package/dist/funnel-QPWJIXWK.js +0 -395
- package/dist/funnel-QPWJIXWK.js.map +0 -1
- package/dist/get-35D7YHWJ.js +0 -57
- package/dist/get-35D7YHWJ.js.map +0 -1
- package/dist/get-GIETDPF4.js +0 -80
- package/dist/get-GIETDPF4.js.map +0 -1
- package/dist/google-NAA4BASO.js +0 -3267
- package/dist/google-ads-2IZFZO4C.js +0 -196
- package/dist/google-ads-2IZFZO4C.js.map +0 -1
- package/dist/history-5MRLNTCI.js +0 -67
- package/dist/history-5MRLNTCI.js.map +0 -1
- package/dist/history-RPLWWPLQ.js +0 -134
- package/dist/history-RPLWWPLQ.js.map +0 -1
- package/dist/history-TQYLUYVU.js +0 -100
- package/dist/history-TQYLUYVU.js.map +0 -1
- package/dist/hubspot-EPK43X4C.js +0 -780
- package/dist/hubspot-EPK43X4C.js.map +0 -1
- package/dist/ideas-SUXKKZC7.js +0 -43
- package/dist/images-VKVZ3DWG.js +0 -92
- package/dist/ingest-BBBFT6FZ.js +0 -24
- package/dist/ingest-EM2UY42Z.js +0 -108
- package/dist/ingest-EM2UY42Z.js.map +0 -1
- package/dist/insights-M3CYB22P.js +0 -437
- package/dist/insights-M3CYB22P.js.map +0 -1
- package/dist/insights-XCT3ONC7.js +0 -130
- package/dist/insights-XCT3ONC7.js.map +0 -1
- package/dist/inspect-ZPOVOENK.js +0 -190
- package/dist/inspect-ZPOVOENK.js.map +0 -1
- package/dist/instant-experiences-HT3HB4QZ.js +0 -258
- package/dist/instant-experiences-HT3HB4QZ.js.map +0 -1
- package/dist/intent-NSHW7EJT.js +0 -95
- package/dist/keyword-gap-ITTWKBWZ.js +0 -130
- package/dist/keyword-metrics-7DPR264W.js +0 -198
- package/dist/keyword-metrics-7DPR264W.js.map +0 -1
- package/dist/keywords-for-site-IXL22UKQ.js +0 -129
- package/dist/keywords-for-site-IXL22UKQ.js.map +0 -1
- package/dist/landing-KZ4OSHHR.js +0 -28
- package/dist/languages-MEFQQBJ2.js +0 -40
- package/dist/leads-DNQTRYIR.js +0 -113
- package/dist/leads-DNQTRYIR.js.map +0 -1
- package/dist/leads-SZOGCQFK.js +0 -538
- package/dist/leads-SZOGCQFK.js.map +0 -1
- package/dist/learn-Q7WZG7FA.js +0 -205
- package/dist/learn-Q7WZG7FA.js.map +0 -1
- package/dist/line-item-writes-3K7FT5GO.js +0 -209
- package/dist/line-item-writes-3K7FT5GO.js.map +0 -1
- package/dist/line-items-G5YJG25N.js +0 -107
- package/dist/link-2QVSIZJT.js +0 -65
- package/dist/link-2QVSIZJT.js.map +0 -1
- package/dist/linkedin-VAFPZNWZ.js +0 -1314
- package/dist/linkedin-VAFPZNWZ.js.map +0 -1
- package/dist/linkedin-person-EHTYVNZY.js +0 -137
- package/dist/linkedin-person-EHTYVNZY.js.map +0 -1
- package/dist/list-5TS2VPQZ.js +0 -152
- package/dist/list-5TS2VPQZ.js.map +0 -1
- package/dist/list-PSSQXQRJ.js +0 -65
- package/dist/list-PSSQXQRJ.js.map +0 -1
- package/dist/list-UMVIFQ3X.js +0 -304
- package/dist/list-UMVIFQ3X.js.map +0 -1
- package/dist/locations-RLWAS6AC.js +0 -40
- package/dist/locations-UKBOT6SB.js +0 -67
- package/dist/locations-UKBOT6SB.js.map +0 -1
- package/dist/log-ZZK3QMZB.js +0 -93
- package/dist/log-ZZK3QMZB.js.map +0 -1
- package/dist/lookup-ZJIP2M4X.js +0 -22
- package/dist/make-DB2BDVB3.js +0 -1368
- package/dist/make-DB2BDVB3.js.map +0 -1
- package/dist/map-UJ23GKRH.js +0 -434
- package/dist/map-UJ23GKRH.js.map +0 -1
- package/dist/mcp-SS2ALFRJ.js +0 -715
- package/dist/mcp-SS2ALFRJ.js.map +0 -1
- package/dist/media-ZWN7E777.js +0 -18
- package/dist/media-sources-GKSEDSE3.js +0 -35
- package/dist/members-KD2XRYPZ.js +0 -45
- package/dist/members-KD2XRYPZ.js.map +0 -1
- package/dist/meta-EHKXH257.js +0 -122
- package/dist/metrics-K6FDCMK7.js +0 -152
- package/dist/normalize-ZNTXNX6D.js +0 -142
- package/dist/normalize-ZNTXNX6D.js.map +0 -1
- package/dist/options-TBIFJF3E.js +0 -123
- package/dist/options-TBIFJF3E.js.map +0 -1
- package/dist/organization-26J2ZIKM.js +0 -947
- package/dist/organization-26J2ZIKM.js.map +0 -1
- package/dist/page-photos-HWKQOSCU.js +0 -116
- package/dist/page-photos-HWKQOSCU.js.map +0 -1
- package/dist/performance-6TPJGM6A.js +0 -25
- package/dist/performance-O5BXZGS6.js +0 -5539
- package/dist/performance-O5BXZGS6.js.map +0 -1
- package/dist/pixels-Z7ZGXAGB.js +0 -60
- package/dist/pixels-Z7ZGXAGB.js.map +0 -1
- package/dist/plan-TVQSEKYL.js +0 -207
- package/dist/plan-TVQSEKYL.js.map +0 -1
- package/dist/preview-CWRGDFCP.js +0 -216
- package/dist/preview-CWRGDFCP.js.map +0 -1
- package/dist/promoted-tweet-writes-SZN74JHY.js +0 -181
- package/dist/promoted-tweet-writes-SZN74JHY.js.map +0 -1
- package/dist/promoted-tweets-7P4TSQU7.js +0 -119
- package/dist/publish-3DUERLGT.js +0 -342
- package/dist/publish-3DUERLGT.js.map +0 -1
- package/dist/query-QM7MBQD7.js +0 -62
- package/dist/questionnaire-LEZJLMAV.js +0 -93
- package/dist/questionnaire-LEZJLMAV.js.map +0 -1
- package/dist/reddit-SN5CESF4.js +0 -27
- package/dist/refresh-2ICQBTK4.js +0 -95
- package/dist/refresh-2ICQBTK4.js.map +0 -1
- package/dist/release-JSZ4JXYJ.js +0 -61
- package/dist/release-JSZ4JXYJ.js.map +0 -1
- package/dist/relevant-pages-VOICMQ7U.js +0 -99
- package/dist/remove-TFUAVRFM.js +0 -51
- package/dist/remove-TFUAVRFM.js.map +0 -1
- package/dist/reopen-5T2PE7MU.js +0 -64
- package/dist/reopen-5T2PE7MU.js.map +0 -1
- package/dist/rerun-EZJ4MLYG.js +0 -93
- package/dist/rerun-EZJ4MLYG.js.map +0 -1
- package/dist/research-BKYSU2C6.js +0 -263
- package/dist/research-BKYSU2C6.js.map +0 -1
- package/dist/research-QU63CJB5.js +0 -72
- package/dist/reset-DKTKZF7G.js +0 -55
- package/dist/reset-DKTKZF7G.js.map +0 -1
- package/dist/reset-SXABIVP6.js +0 -55
- package/dist/reset-SXABIVP6.js.map +0 -1
- package/dist/resolve-L47U2T77.js +0 -122
- package/dist/resolve-L47U2T77.js.map +0 -1
- package/dist/retry-JEWPK2B5.js +0 -63
- package/dist/retry-JEWPK2B5.js.map +0 -1
- package/dist/retry-SXFVLT3W.js +0 -61
- package/dist/retry-SXFVLT3W.js.map +0 -1
- package/dist/revenue-2MAYTXRM.js +0 -89
- package/dist/revenue-2MAYTXRM.js.map +0 -1
- package/dist/rule-writes-S3RN47OG.js +0 -523
- package/dist/rule-writes-S3RN47OG.js.map +0 -1
- package/dist/rules-CHWTFASP.js +0 -97
- package/dist/rules-CHWTFASP.js.map +0 -1
- package/dist/rules-KTJ2ZZ62.js +0 -217
- package/dist/rules-add-3M5TALWN.js +0 -463
- package/dist/rules-add-3M5TALWN.js.map +0 -1
- package/dist/run-2DLA6YO4.js +0 -68
- package/dist/run-JCGH2NHD.js +0 -67
- package/dist/run-JCGH2NHD.js.map +0 -1
- package/dist/scaffold-ad-VEW7JDY7.js +0 -1514
- package/dist/scaffold-ad-VEW7JDY7.js.map +0 -1
- package/dist/scaffold-static-ad-DYRL7DOS.js +0 -1013
- package/dist/scaffold-static-ad-DYRL7DOS.js.map +0 -1
- package/dist/scaffold-video-YRAVVKUH.js +0 -886
- package/dist/scaffold-video-YRAVVKUH.js.map +0 -1
- package/dist/schedule-WYIC6VNW.js +0 -62
- package/dist/schedule-WYIC6VNW.js.map +0 -1
- package/dist/scheduled-agents-FRBF4EOK.js +0 -55
- package/dist/schema-TC2O3FYY.js +0 -133
- package/dist/schema-TC2O3FYY.js.map +0 -1
- package/dist/search-VDW242BN.js +0 -175
- package/dist/search-VDW242BN.js.map +0 -1
- package/dist/select-O5HSSC36.js +0 -104
- package/dist/select-O5HSSC36.js.map +0 -1
- package/dist/sends-6FI7WELX.js +0 -348
- package/dist/sends-6FI7WELX.js.map +0 -1
- package/dist/set-prompt-TI7N64PJ.js +0 -196
- package/dist/set-prompt-TI7N64PJ.js.map +0 -1
- package/dist/setup-links-BM2FTXUZ.js +0 -142
- package/dist/setup-links-BM2FTXUZ.js.map +0 -1
- package/dist/shopify-FIHG5PYP.js +0 -3956
- package/dist/shopify-FIHG5PYP.js.map +0 -1
- package/dist/site-S3IGBMVC.js +0 -178
- package/dist/site-S3IGBMVC.js.map +0 -1
- package/dist/sources-77WLFY4Y.js +0 -30
- package/dist/sources-A4B3R5JC.js +0 -455
- package/dist/sources-A4B3R5JC.js.map +0 -1
- package/dist/stale-YIRCXDOK.js +0 -74
- package/dist/stale-YIRCXDOK.js.map +0 -1
- package/dist/status-6YNR647D.js +0 -63
- package/dist/status-6YNR647D.js.map +0 -1
- package/dist/studies-ZOEUY6YP.js +0 -250
- package/dist/study-writes-DTGMCNFP.js +0 -468
- package/dist/study-writes-DTGMCNFP.js.map +0 -1
- package/dist/sync-FFVWHGLE.js +0 -55
- package/dist/sync-FFVWHGLE.js.map +0 -1
- package/dist/sync-definition-K4XOAG2B.js +0 -67
- package/dist/tags-5KIOGEKN.js +0 -21
- package/dist/tags-5KIOGEKN.js.map +0 -1
- package/dist/tags-BKAN5EAJ.js +0 -21
- package/dist/tags-BKAN5EAJ.js.map +0 -1
- package/dist/tags-ETOOYQ57.js +0 -26
- package/dist/tags-RPNK7TMO.js +0 -225
- package/dist/tags-RPNK7TMO.js.map +0 -1
- package/dist/tags-UM4QO46J.js +0 -21
- package/dist/tags-UM4QO46J.js.map +0 -1
- package/dist/targeting-VBQY3HUN.js +0 -760
- package/dist/targeting-VBQY3HUN.js.map +0 -1
- package/dist/targeting-criteria-L4DYIJZI.js +0 -113
- package/dist/targets-S6N7327Q.js +0 -104
- package/dist/targets-S6N7327Q.js.map +0 -1
- package/dist/testimonials-65N7IYN7.js +0 -32
- package/dist/themes-B4LCQUXB.js +0 -95
- package/dist/top-companies-SJKND75M.js +0 -109
- package/dist/top-companies-SJKND75M.js.map +0 -1
- package/dist/transcript-5I7I6DWL.js +0 -163
- package/dist/transcript-5I7I6DWL.js.map +0 -1
- package/dist/translate-SH6IG5Y7.js +0 -607
- package/dist/translate-SH6IG5Y7.js.map +0 -1
- package/dist/triggers-YRY3HTYU.js +0 -142
- package/dist/triggers-YRY3HTYU.js.map +0 -1
- package/dist/unlink-4B4YTQE4.js +0 -68
- package/dist/unlink-4B4YTQE4.js.map +0 -1
- package/dist/unstage-H5NTDJ6A.js +0 -86
- package/dist/unstage-H5NTDJ6A.js.map +0 -1
- package/dist/unstage-MDSSPUKV.js +0 -95
- package/dist/unstage-MDSSPUKV.js.map +0 -1
- package/dist/update-DI52JJGB.js +0 -99
- package/dist/update-DI52JJGB.js.map +0 -1
- package/dist/update-DMPDLMMV.js +0 -64
- package/dist/update-DMPDLMMV.js.map +0 -1
- package/dist/update-TUS4PJIG.js +0 -190
- package/dist/update-TUS4PJIG.js.map +0 -1
- package/dist/update-Z66LMXNS.js +0 -153
- package/dist/update-Z66LMXNS.js.map +0 -1
- package/dist/upload-7KQYZ7T7.js +0 -127
- package/dist/upload-7KQYZ7T7.js.map +0 -1
- package/dist/url-options-IHL4OU4C.js +0 -130
- package/dist/url-options-IHL4OU4C.js.map +0 -1
- package/dist/url-params-SDA2MWB5.js +0 -99
- package/dist/url-params-SDA2MWB5.js.map +0 -1
- package/dist/validate-ZABZNCSB.js +0 -120
- package/dist/validate-ZABZNCSB.js.map +0 -1
- package/dist/videos-BKVCCKIH.js +0 -41
- package/dist/viewer-check-B5O7ZY3Q.js +0 -81
- package/dist/web-KN3BTVG4.js +0 -141
- package/dist/web-KN3BTVG4.js.map +0 -1
- package/dist/winning-ads-JIDPISKM.js +0 -86
- package/dist/write-commands-K6M3RTVK.js +0 -1170
- package/dist/write-commands-K6M3RTVK.js.map +0 -1
- package/dist/write-commands-RVHLLLDR.js +0 -1498
- package/dist/write-commands-RVHLLLDR.js.map +0 -1
- package/dist/x-NLGWZCGX.js +0 -47
- /package/dist/{account-assets-O7IA37RE.js.map → account-assets-J5Z4URLQ.js.map} +0 -0
- /package/dist/{account-reads-SNOWDHHF.js.map → account-reads-2MSUVG46.js.map} +0 -0
- /package/dist/{actions-Y4SULW2E.js.map → actions-D7OWCP36.js.map} +0 -0
- /package/dist/{ads-2TVWICGM.js.map → ads-HLUJB5SQ.js.map} +0 -0
- /package/dist/{ads-KBE5JXCH.js.map → ads-MAOI7S6V.js.map} +0 -0
- /package/dist/{adsets-YWBJFPAZ.js.map → adsets-QRKICEFD.js.map} +0 -0
- /package/dist/{asset-writes-72DEVXGO.js.map → asset-writes-JLVUAVUZ.js.map} +0 -0
- /package/dist/{audiences-QAH42HIM.js.map → audiences-CDDI5HR5.js.map} +0 -0
- /package/dist/{avatars-P4UPNZM4.js.map → avatars-WZ2JDMHH.js.map} +0 -0
- /package/dist/{brand-HALQWZWB.js.map → brand-IZTTBZII.js.map} +0 -0
- /package/dist/{campaigns-YMM5TQVO.js.map → campaigns-4Y3JQC2U.js.map} +0 -0
- /package/dist/{campaigns-XZOQ2VTK.js.map → campaigns-HES5MDUG.js.map} +0 -0
- /package/dist/{canvas-IJS7H3VE.js.map → canvas-IQ753Q2O.js.map} +0 -0
- /package/dist/{changes-36C754R4.js.map → changes-VWKTM27U.js.map} +0 -0
- /package/dist/{chatgpt-3WFOPURY.js.map → chatgpt-OPOD2WHB.js.map} +0 -0
- /package/dist/{chunk-DUR7PEJI.js.map → chunk-2GYCWRF3.js.map} +0 -0
- /package/dist/{chunk-QBRPM3DQ.js.map → chunk-2L7F7KR4.js.map} +0 -0
- /package/dist/{chunk-4GAEB6NK.js.map → chunk-44ZRCSR7.js.map} +0 -0
- /package/dist/{chunk-2TOIY2WN.js.map → chunk-46YLDIQG.js.map} +0 -0
- /package/dist/{chunk-R53XMPDO.js.map → chunk-4EDWXZ3I.js.map} +0 -0
- /package/dist/{chunk-H4NNQGG6.js.map → chunk-4FFBGR5L.js.map} +0 -0
- /package/dist/{chunk-XGPU2VDU.js.map → chunk-4UQRXKYJ.js.map} +0 -0
- /package/dist/{chunk-N7M2TWR5.js.map → chunk-4UZUUSVL.js.map} +0 -0
- /package/dist/{chunk-I47BEKYK.js.map → chunk-4XHCEYHI.js.map} +0 -0
- /package/dist/{chunk-SCL7PM6P.js.map → chunk-4Z6PVR3M.js.map} +0 -0
- /package/dist/{chunk-ZEC46NWW.js.map → chunk-5Y5FZV2W.js.map} +0 -0
- /package/dist/{chunk-G5Y7OIPL.js.map → chunk-6HJXLGDV.js.map} +0 -0
- /package/dist/{chunk-QW6HL7D6.js.map → chunk-6S5CWGDA.js.map} +0 -0
- /package/dist/{chunk-5RVQK3SP.js.map → chunk-6VYROVWO.js.map} +0 -0
- /package/dist/{chunk-TVAIOC77.js.map → chunk-7BUIOP36.js.map} +0 -0
- /package/dist/{chunk-NDFS3APP.js.map → chunk-AQRXXC6E.js.map} +0 -0
- /package/dist/{chunk-ZFC562TP.js.map → chunk-ARBXT73X.js.map} +0 -0
- /package/dist/{chunk-KQTNUDKD.js.map → chunk-BEZHQIEI.js.map} +0 -0
- /package/dist/{chunk-5SRUAGTG.js.map → chunk-BFURWHVR.js.map} +0 -0
- /package/dist/{chunk-KBRJSPIM.js.map → chunk-DSCEXBDR.js.map} +0 -0
- /package/dist/{chunk-Z7YMSHRZ.js.map → chunk-FLWDFPXE.js.map} +0 -0
- /package/dist/{chunk-GACSA5A6.js.map → chunk-FRNBRN2C.js.map} +0 -0
- /package/dist/{chunk-C7QQQVBS.js.map → chunk-GE3VT3CA.js.map} +0 -0
- /package/dist/{chunk-P6BKKUSX.js.map → chunk-HGBJQGO5.js.map} +0 -0
- /package/dist/{chunk-MGYC2IRC.js.map → chunk-I7AYMJZA.js.map} +0 -0
- /package/dist/{chunk-BRLZX3VP.js.map → chunk-IRM273C7.js.map} +0 -0
- /package/dist/{chunk-AYPBARKP.js.map → chunk-IWKD56AY.js.map} +0 -0
- /package/dist/{chunk-MZCTX37B.js.map → chunk-JLAQWMVF.js.map} +0 -0
- /package/dist/{chunk-AQOFQNKQ.js.map → chunk-K6NULQN7.js.map} +0 -0
- /package/dist/{chunk-GMHCHHAA.js.map → chunk-L2BRAC2J.js.map} +0 -0
- /package/dist/{chunk-SVBJ56T7.js.map → chunk-LGMI4LSY.js.map} +0 -0
- /package/dist/{chunk-OJGYYH6K.js.map → chunk-NB2FU6EZ.js.map} +0 -0
- /package/dist/{chunk-TVTSA4EN.js.map → chunk-NWF2LRKO.js.map} +0 -0
- /package/dist/{chunk-6Z7Y4KQB.js.map → chunk-OG47L5GG.js.map} +0 -0
- /package/dist/{chunk-AIRD6T4W.js.map → chunk-OR6KVLI5.js.map} +0 -0
- /package/dist/{chunk-OBJCNGZL.js.map → chunk-QDQZ62FL.js.map} +0 -0
- /package/dist/{chunk-FPWQA7TK.js.map → chunk-QIOFOD4K.js.map} +0 -0
- /package/dist/{chunk-XQXZJ5IR.js.map → chunk-R2SYF5FC.js.map} +0 -0
- /package/dist/{chunk-YMF2RG4Z.js.map → chunk-RTH2I55B.js.map} +0 -0
- /package/dist/{chunk-FFFJBNHA.js.map → chunk-S6VFDNSQ.js.map} +0 -0
- /package/dist/{chunk-ZNRGH4NS.js.map → chunk-SG2TDQEE.js.map} +0 -0
- /package/dist/{chunk-RFIUOQLN.js.map → chunk-T3P6O2VU.js.map} +0 -0
- /package/dist/{chunk-BTJSGJNQ.js.map → chunk-TXDE2QJ3.js.map} +0 -0
- /package/dist/{chunk-HSQRITKG.js.map → chunk-UDIEKCIR.js.map} +0 -0
- /package/dist/{chunk-MBSFEVOG.js.map → chunk-USCADMM3.js.map} +0 -0
- /package/dist/{chunk-SM7G7RUA.js.map → chunk-UUGISMNE.js.map} +0 -0
- /package/dist/{chunk-Z2DRGXBB.js.map → chunk-VDPOCAQ2.js.map} +0 -0
- /package/dist/{chunk-PLAOK4AK.js.map → chunk-VEJ3QX6M.js.map} +0 -0
- /package/dist/{chunk-ARUXGKZO.js.map → chunk-VFJB7OG6.js.map} +0 -0
- /package/dist/{chunk-RVEY7BTT.js.map → chunk-VRIPVLE7.js.map} +0 -0
- /package/dist/{chunk-5XTGG4PV.js.map → chunk-WFY7KSHB.js.map} +0 -0
- /package/dist/{chunk-D3KLDIPQ.js.map → chunk-WLESMH4J.js.map} +0 -0
- /package/dist/{chunk-PLBITHV6.js.map → chunk-XHQYFNNK.js.map} +0 -0
- /package/dist/{chunk-QMDMTZAW.js.map → chunk-YILZR3XI.js.map} +0 -0
- /package/dist/{chunk-W574NV67.js.map → chunk-YJFLXOH4.js.map} +0 -0
- /package/dist/{chunk-MUXKYSTI.js.map → chunk-YWHZYR6S.js.map} +0 -0
- /package/dist/{connect-PP6Z3F7Y.js.map → connect-3YY7MF3I.js.map} +0 -0
- /package/dist/{conversion-upload-command-KTYAY7IG.js.map → conversion-upload-command-7RSZ5ZQU.js.map} +0 -0
- /package/dist/{creatives-UJDPD4CI.js.map → creatives-2DR3QHJW.js.map} +0 -0
- /package/dist/{creatives-JPKA2RHU.js.map → creatives-BB6S2OF6.js.map} +0 -0
- /package/dist/{currency-R4ORE2U3.js.map → currency-X7Y6TSLF.js.map} +0 -0
- /package/dist/{custom-conversions-5HJBB5XL.js.map → custom-conversions-HAGG72LF.js.map} +0 -0
- /package/dist/{customers-7J3QGSOJ.js.map → customers-663K2OCP.js.map} +0 -0
- /package/dist/{discover-GSJRZUOC.js.map → discover-6DORQWXY.js.map} +0 -0
- /package/dist/{draft-WYDMLNYA.js.map → draft-R6EKORNS.js.map} +0 -0
- /package/dist/{fit-N7WDGWYZ.js.map → fit-BEFCY2B4.js.map} +0 -0
- /package/dist/{fonts-TTUAP7HS.js.map → fonts-C6XN5667.js.map} +0 -0
- /package/dist/{forecast-4AG5F7YC.js.map → forecast-BTPIXEQ5.js.map} +0 -0
- /package/dist/{google-NAA4BASO.js.map → google-M6BJ22UL.js.map} +0 -0
- /package/dist/{ideas-SUXKKZC7.js.map → ideas-M66FU45H.js.map} +0 -0
- /package/dist/{images-VKVZ3DWG.js.map → images-MM5OPUKV.js.map} +0 -0
- /package/dist/{ingest-BBBFT6FZ.js.map → ingest-KMLYCFF4.js.map} +0 -0
- /package/dist/{intent-NSHW7EJT.js.map → intent-YCQCUCV3.js.map} +0 -0
- /package/dist/{keyword-gap-ITTWKBWZ.js.map → keyword-gap-PUSF7EKY.js.map} +0 -0
- /package/dist/{landing-KZ4OSHHR.js.map → landing-OZU5RRFV.js.map} +0 -0
- /package/dist/{languages-MEFQQBJ2.js.map → languages-SLFV73GE.js.map} +0 -0
- /package/dist/{line-items-G5YJG25N.js.map → line-items-NJMEPEF5.js.map} +0 -0
- /package/dist/{locations-RLWAS6AC.js.map → locations-T2OMP6QN.js.map} +0 -0
- /package/dist/{lookup-ZJIP2M4X.js.map → lookup-S5VG7AWH.js.map} +0 -0
- /package/dist/{media-ZWN7E777.js.map → media-P4T4ZBNS.js.map} +0 -0
- /package/dist/{media-sources-GKSEDSE3.js.map → media-sources-EQI3U4TC.js.map} +0 -0
- /package/dist/{meta-EHKXH257.js.map → meta-IPLWNYC7.js.map} +0 -0
- /package/dist/{metrics-K6FDCMK7.js.map → metrics-4YVJHYRI.js.map} +0 -0
- /package/dist/{performance-6TPJGM6A.js.map → performance-NIF3CESZ.js.map} +0 -0
- /package/dist/{promoted-tweets-7P4TSQU7.js.map → promoted-tweets-RJT53DRM.js.map} +0 -0
- /package/dist/{query-QM7MBQD7.js.map → query-XZMDGRO5.js.map} +0 -0
- /package/dist/{reddit-SN5CESF4.js.map → reddit-JIHMAKAQ.js.map} +0 -0
- /package/dist/{relevant-pages-VOICMQ7U.js.map → relevant-pages-FS7GBVXK.js.map} +0 -0
- /package/dist/{research-QU63CJB5.js.map → research-PNBACCCP.js.map} +0 -0
- /package/dist/{rules-KTJ2ZZ62.js.map → rules-ZVS2MWED.js.map} +0 -0
- /package/dist/{run-2DLA6YO4.js.map → run-QCNRR3CA.js.map} +0 -0
- /package/dist/{scheduled-agents-FRBF4EOK.js.map → scheduled-agents-OXYAVSIE.js.map} +0 -0
- /package/dist/{sources-77WLFY4Y.js.map → sources-FLUGHSVP.js.map} +0 -0
- /package/dist/{studies-ZOEUY6YP.js.map → studies-EPO2AM4M.js.map} +0 -0
- /package/dist/{sync-definition-K4XOAG2B.js.map → sync-definition-OGQ5XTQE.js.map} +0 -0
- /package/dist/{tags-ETOOYQ57.js.map → tags-CZTRQNXV.js.map} +0 -0
- /package/dist/{targeting-criteria-L4DYIJZI.js.map → targeting-criteria-AQYXVSGN.js.map} +0 -0
- /package/dist/{testimonials-65N7IYN7.js.map → testimonials-G3OCNPBY.js.map} +0 -0
- /package/dist/{themes-B4LCQUXB.js.map → themes-FEVYZZCW.js.map} +0 -0
- /package/dist/{videos-BKVCCKIH.js.map → videos-36AXDUGZ.js.map} +0 -0
- /package/dist/{viewer-check-B5O7ZY3Q.js.map → viewer-check-6HL4OUQY.js.map} +0 -0
- /package/dist/{winning-ads-JIDPISKM.js.map → winning-ads-GMM5W2O4.js.map} +0 -0
- /package/dist/{x-NLGWZCGX.js.map → x-HGFAL3NY.js.map} +0 -0
|
@@ -0,0 +1,4274 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ADVANTAGE_AUDIENCE_MAX_HARD_AGE_MIN,
|
|
3
|
+
ADVANTAGE_AUDIENCE_OPEN_AGE_MAX,
|
|
4
|
+
AD_FORMATS,
|
|
5
|
+
ASSET_FEED_CTA_TYPES,
|
|
6
|
+
ATTRIBUTION_EVENT_TYPES,
|
|
7
|
+
ATTRIBUTION_MAX_WINDOW_DAYS,
|
|
8
|
+
BID_STRATEGIES,
|
|
9
|
+
BID_STRATEGIES_FORBIDDING_AMOUNT,
|
|
10
|
+
BID_STRATEGIES_REQUIRING_AMOUNT,
|
|
11
|
+
BILLING_EVENTS,
|
|
12
|
+
BUYING_TYPES,
|
|
13
|
+
CREATIVE_UPDATE_STATUSES,
|
|
14
|
+
CTA_TYPES,
|
|
15
|
+
CURRENCY_MINIMUMS,
|
|
16
|
+
CUSTOMER_FILE_SOURCES,
|
|
17
|
+
CUSTOM_AUDIENCE_SUBTYPES,
|
|
18
|
+
CUSTOM_EVENT_TYPES,
|
|
19
|
+
DEFAULT_CURRENCY_MINIMUM,
|
|
20
|
+
DESTINATION_TYPES,
|
|
21
|
+
ENROLL_STATUSES,
|
|
22
|
+
EU_DSA_COUNTRIES,
|
|
23
|
+
FREQUENCY_CAP_EVENTS,
|
|
24
|
+
FREQUENCY_CAP_TYPES,
|
|
25
|
+
IMAGE_HASH_REGEX,
|
|
26
|
+
LEAD_FORM_CTA_TYPES,
|
|
27
|
+
MEDIA_KINDS,
|
|
28
|
+
META_LIMITS,
|
|
29
|
+
NUMERIC_ID_REGEX,
|
|
30
|
+
OBJECTIVES,
|
|
31
|
+
OPTIMIZATION_GOALS,
|
|
32
|
+
OPTIMIZATION_GOALS_REQUIRING_APP,
|
|
33
|
+
OPTIMIZATION_GOALS_REQUIRING_CONVERSION_SOURCE,
|
|
34
|
+
OPTIMIZATION_GOALS_REQUIRING_PAGE,
|
|
35
|
+
OPTIMIZATION_GOALS_REQUIRING_PIXEL,
|
|
36
|
+
OPTIMIZATION_GOALS_WITH_FREQUENCY_CAP,
|
|
37
|
+
PACING_TYPES,
|
|
38
|
+
ROAS_FLOOR_SCALE,
|
|
39
|
+
SCHEDULE_TIMEZONE_TYPES,
|
|
40
|
+
SPECIAL_AD_CATEGORIES,
|
|
41
|
+
SPECIAL_CATEGORIES_REQUIRING_COUNTRY,
|
|
42
|
+
STAGEABLE_CREATE_STATUSES,
|
|
43
|
+
TEMP_REF_REGEX,
|
|
44
|
+
UPDATE_STATUSES,
|
|
45
|
+
adStudyCreateSchema,
|
|
46
|
+
adStudyUpdateSchema
|
|
47
|
+
} from "./chunk-GDFJOCDO.js";
|
|
48
|
+
import {
|
|
49
|
+
maskPersonValue,
|
|
50
|
+
maskedPersonValueIssue
|
|
51
|
+
} from "./chunk-SOPVIIUW.js";
|
|
52
|
+
|
|
53
|
+
// ../api/src/ads-meta/customConversions.ts
|
|
54
|
+
var RULE_OPERATORS = {
|
|
55
|
+
eq: "is",
|
|
56
|
+
neq: "is not",
|
|
57
|
+
contains: "contains",
|
|
58
|
+
i_contains: "contains",
|
|
59
|
+
not_contains: "does not contain",
|
|
60
|
+
i_not_contains: "does not contain",
|
|
61
|
+
starts_with: "starts with",
|
|
62
|
+
i_starts_with: "starts with",
|
|
63
|
+
ends_with: "ends with",
|
|
64
|
+
i_ends_with: "ends with",
|
|
65
|
+
gt: "is more than",
|
|
66
|
+
gte: "is at least",
|
|
67
|
+
lt: "is less than",
|
|
68
|
+
lte: "is at most",
|
|
69
|
+
regex_match: "matches",
|
|
70
|
+
exists: "exists"
|
|
71
|
+
};
|
|
72
|
+
function isRecord(value) {
|
|
73
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
74
|
+
}
|
|
75
|
+
var DEFAULT_CUSTOM_CONVERSION_EVENT = "PageView";
|
|
76
|
+
function buildCustomConversionRule(input) {
|
|
77
|
+
return {
|
|
78
|
+
and: [
|
|
79
|
+
{ event: { eq: input.event ?? DEFAULT_CUSTOM_CONVERSION_EVENT } },
|
|
80
|
+
{ URL: { i_contains: input.urlContains } }
|
|
81
|
+
]
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function customConversionRuleRefusal(rule) {
|
|
85
|
+
const refusal = ruleNodeRefusal(rule, "rule");
|
|
86
|
+
if (refusal) {
|
|
87
|
+
return refusal;
|
|
88
|
+
}
|
|
89
|
+
if (!ruleFields(rule).some((field) => field !== "event")) {
|
|
90
|
+
return 'rule only names the event, and Meta refuses a custom conversion with no condition beyond it \u2014 narrow it with a URL, referrer or event parameter the event carries, e.g. {"and":[{"event":{"eq":"Lead"}},{"URL":{"i_contains":"/gracias"}}]}';
|
|
91
|
+
}
|
|
92
|
+
return void 0;
|
|
93
|
+
}
|
|
94
|
+
function ruleFields(node) {
|
|
95
|
+
if (!isRecord(node)) {
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
return Object.entries(node).flatMap(
|
|
99
|
+
([key, value]) => (key === "and" || key === "or") && Array.isArray(value) ? value.flatMap(ruleFields) : [key]
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
function branchRefusal(key, value, path) {
|
|
103
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
104
|
+
return `${path}.${key} must be a non-empty array of conditions \u2014 an empty one counts every event the pixel sends`;
|
|
105
|
+
}
|
|
106
|
+
for (const [index, child] of value.entries()) {
|
|
107
|
+
const refusal = ruleNodeRefusal(child, `${path}.${key}[${index}]`);
|
|
108
|
+
if (refusal) {
|
|
109
|
+
return refusal;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return void 0;
|
|
113
|
+
}
|
|
114
|
+
function conditionRefusal(key, value, path) {
|
|
115
|
+
return key === "and" || key === "or" ? branchRefusal(key, value, path) : leafRefusal(key, value, path);
|
|
116
|
+
}
|
|
117
|
+
function ruleNodeRefusal(node, path) {
|
|
118
|
+
if (!isRecord(node)) {
|
|
119
|
+
return `${path} must be an object like {"URL":{"i_contains":"/thank-you"}}`;
|
|
120
|
+
}
|
|
121
|
+
const entries = Object.entries(node);
|
|
122
|
+
if (entries.length === 0) {
|
|
123
|
+
return `${path} is empty \u2014 a rule with no condition counts every event the pixel sends`;
|
|
124
|
+
}
|
|
125
|
+
if (entries.length > 1) {
|
|
126
|
+
return `${path} names ${entries.length} conditions side by side (${entries.map(([key]) => key).join(", ")}) \u2014 Meta reads one branch or one field test per object, so combine them with {"and":[\u2026]}`;
|
|
127
|
+
}
|
|
128
|
+
for (const [key, value] of entries) {
|
|
129
|
+
const refusal = conditionRefusal(key, value, path);
|
|
130
|
+
if (refusal) {
|
|
131
|
+
return refusal;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return void 0;
|
|
135
|
+
}
|
|
136
|
+
function leafRefusal(field, condition, path) {
|
|
137
|
+
if (!isRecord(condition)) {
|
|
138
|
+
return `${path}.${field} must be a test like {"i_contains":"/thank-you"}, not a bare value`;
|
|
139
|
+
}
|
|
140
|
+
const tests = Object.entries(condition);
|
|
141
|
+
if (tests.length === 0) {
|
|
142
|
+
return `${path}.${field} names no test \u2014 use one of ${Object.keys(RULE_OPERATORS).join(", ")}`;
|
|
143
|
+
}
|
|
144
|
+
if (tests.length > 1) {
|
|
145
|
+
return `${path}.${field} names ${tests.length} operators at once (${tests.map(([operator]) => operator).join(", ")}) \u2014 test a field once per condition and combine them with {"and":[\u2026]}`;
|
|
146
|
+
}
|
|
147
|
+
for (const [operator] of tests) {
|
|
148
|
+
if (!RULE_OPERATORS[operator]) {
|
|
149
|
+
return `${path}.${field} uses "${operator}", which is not a Meta rule operator \u2014 use one of ${Object.keys(RULE_OPERATORS).join(", ")} (URL matching is i_contains, not contains or includes)`;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return void 0;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ../api/src/ads-meta/leadAds.ts
|
|
156
|
+
var META_LEAD_ADS_NOT_ENABLED_MESSAGE = "Using Meta instant forms and their leads is not switched on for this company. Everything else on Meta keeps working. Tell the user this needs switching on by the Baker team, and carry on with the rest of the job.";
|
|
157
|
+
var META_LEAD_ADS_RECONNECT_MESSAGE = "This Meta connection was made before lead ads were switched on for this company, so it cannot work with instant forms or read their leads. Ask the user to reconnect Meta in the dashboard \u2014 that grants the extra permissions \u2014 then ask for this again. Everything else on Meta keeps working meanwhile.";
|
|
158
|
+
function isMetaLeadAdsRefusal(message) {
|
|
159
|
+
return message === META_LEAD_ADS_NOT_ENABLED_MESSAGE || message === META_LEAD_ADS_RECONNECT_MESSAGE;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ../api/src/ads-meta/placements.ts
|
|
163
|
+
var FACEBOOK_POSITIONS_REQUIRING_FEED = ["marketplace", "search", "profile_feed", "notification"];
|
|
164
|
+
var PLACEMENT_TARGETING_DOC = "https://developers.facebook.com/docs/marketing-api/audiences/reference/placement-targeting/";
|
|
165
|
+
var PUBLISHER_PLATFORMS = ["facebook", "instagram", "threads", "messenger", "audience_network"];
|
|
166
|
+
var FACEBOOK_POSITIONS = [
|
|
167
|
+
"feed",
|
|
168
|
+
"right_hand_column",
|
|
169
|
+
"marketplace",
|
|
170
|
+
"story",
|
|
171
|
+
"search",
|
|
172
|
+
"instream_video",
|
|
173
|
+
"facebook_reels",
|
|
174
|
+
"facebook_reels_overlay",
|
|
175
|
+
"profile_feed",
|
|
176
|
+
"notification"
|
|
177
|
+
];
|
|
178
|
+
var INSTAGRAM_POSITIONS = [
|
|
179
|
+
"stream",
|
|
180
|
+
"story",
|
|
181
|
+
"explore_home",
|
|
182
|
+
"reels",
|
|
183
|
+
"profile_feed",
|
|
184
|
+
"ig_search",
|
|
185
|
+
"profile_reels"
|
|
186
|
+
];
|
|
187
|
+
var AUDIENCE_NETWORK_POSITIONS = ["classic", "rewarded_video"];
|
|
188
|
+
var MESSENGER_POSITIONS = ["sponsored_messages"];
|
|
189
|
+
var THREADS_POSITIONS = ["threads_stream"];
|
|
190
|
+
var DEVICE_PLATFORMS = ["mobile", "desktop", "connected_tv"];
|
|
191
|
+
var PLACEMENT_VOCABULARIES = {
|
|
192
|
+
publisher_platforms: PUBLISHER_PLATFORMS,
|
|
193
|
+
facebook_positions: FACEBOOK_POSITIONS,
|
|
194
|
+
instagram_positions: INSTAGRAM_POSITIONS,
|
|
195
|
+
audience_network_positions: AUDIENCE_NETWORK_POSITIONS,
|
|
196
|
+
messenger_positions: MESSENGER_POSITIONS,
|
|
197
|
+
threads_positions: THREADS_POSITIONS,
|
|
198
|
+
device_platforms: DEVICE_PLATFORMS
|
|
199
|
+
};
|
|
200
|
+
var SAME_SURFACE_ON_THIS_PLATFORM = {
|
|
201
|
+
facebook_positions: { reels: "facebook_reels", stream: "feed", stories: "story", ig_search: "search" },
|
|
202
|
+
instagram_positions: {
|
|
203
|
+
feed: "stream",
|
|
204
|
+
facebook_reels: "reels",
|
|
205
|
+
stories: "story",
|
|
206
|
+
search: "ig_search"
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
var RETIRED_PLACEMENTS = {
|
|
210
|
+
facebook_positions: {
|
|
211
|
+
video_feeds: 'Meta retired Facebook video feeds \u2014 use "instream_video" for in-stream video, or "facebook_reels", and drop "video_feeds"'
|
|
212
|
+
},
|
|
213
|
+
// v26.0, re-swept 2026-10-15: `explore` is refused (subcode 2490589, "IG
|
|
214
|
+
// Explore placement is deprecated for this API version"); `explore_home`
|
|
215
|
+
// validates on its own.
|
|
216
|
+
instagram_positions: {
|
|
217
|
+
explore: 'Meta retired Instagram Explore \u2014 drop "explore"; "explore_home" (Explore home) still runs on its own'
|
|
218
|
+
},
|
|
219
|
+
// v26.0 takes Messenger `story` and silently drops it from the ad set (every
|
|
220
|
+
// version from 2026-10-27), so what was staged would never be what runs.
|
|
221
|
+
messenger_positions: {
|
|
222
|
+
story: 'Meta retired Messenger Stories \u2014 drop "story" from messenger_positions; Facebook and Instagram "story" still run'
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
function placementFieldsAccepting(value, except) {
|
|
226
|
+
return Object.keys(PLACEMENT_VOCABULARIES).filter(
|
|
227
|
+
(field) => field !== except && PLACEMENT_VOCABULARIES[field].includes(value)
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
function placementValueIssue(field, value) {
|
|
231
|
+
const retired = RETIRED_PLACEMENTS[field]?.[value];
|
|
232
|
+
if (retired) {
|
|
233
|
+
return { field, value, kind: "retired", fix: retired };
|
|
234
|
+
}
|
|
235
|
+
const renamed = SAME_SURFACE_ON_THIS_PLATFORM[field]?.[value];
|
|
236
|
+
if (renamed) {
|
|
237
|
+
return {
|
|
238
|
+
field,
|
|
239
|
+
value,
|
|
240
|
+
kind: "wrong-field",
|
|
241
|
+
fix: `Meta calls that surface "${renamed}" in ${field} \u2014 use "${renamed}"`
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
const elsewhere = placementFieldsAccepting(value, field);
|
|
245
|
+
if (elsewhere.length > 0) {
|
|
246
|
+
return {
|
|
247
|
+
field,
|
|
248
|
+
value,
|
|
249
|
+
kind: "wrong-field",
|
|
250
|
+
fix: `"${value}" belongs to ${elsewhere.join(" / ")}, not ${field} \u2014 move it there or drop it`
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
field,
|
|
255
|
+
value,
|
|
256
|
+
kind: "unknown",
|
|
257
|
+
fix: `"${value}" is not a placement Baker knows; ${field} accepts ${PLACEMENT_VOCABULARIES[field].join(", ")}`
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
function invalidPlacementValues(targeting) {
|
|
261
|
+
const issues = [];
|
|
262
|
+
for (const field of Object.keys(PLACEMENT_VOCABULARIES)) {
|
|
263
|
+
const allowed = PLACEMENT_VOCABULARIES[field];
|
|
264
|
+
const selected = targeting?.[field] ?? [];
|
|
265
|
+
for (const value of selected) {
|
|
266
|
+
if (!allowed.includes(value)) {
|
|
267
|
+
issues.push(placementValueIssue(field, value));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return issues;
|
|
272
|
+
}
|
|
273
|
+
function placementCoRequirementIssues(targeting) {
|
|
274
|
+
const facebook = targeting?.facebook_positions ?? [];
|
|
275
|
+
const instagram = targeting?.instagram_positions ?? [];
|
|
276
|
+
const threads = targeting?.threads_positions ?? [];
|
|
277
|
+
const devices = targeting?.device_platforms;
|
|
278
|
+
const issues = [];
|
|
279
|
+
if (facebook.includes("story")) {
|
|
280
|
+
if (!facebook.includes("feed") && !instagram.includes("story")) {
|
|
281
|
+
issues.push(
|
|
282
|
+
'Meta refuses Facebook "story" on its own \u2014 select "feed" in facebook_positions or "story" in instagram_positions alongside it'
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
if (devices && !devices.includes("mobile")) {
|
|
286
|
+
issues.push(
|
|
287
|
+
'Facebook "story" only runs on mobile \u2014 add "mobile" to device_platforms or drop the story placement'
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (threads.includes("threads_stream") && !instagram.includes("stream")) {
|
|
292
|
+
issues.push('Threads "threads_stream" cannot be selected alone \u2014 add "stream" to instagram_positions');
|
|
293
|
+
}
|
|
294
|
+
return issues;
|
|
295
|
+
}
|
|
296
|
+
var CONSTRAINED_PLACEMENTS = [
|
|
297
|
+
{
|
|
298
|
+
position: "facebook_reels_overlay",
|
|
299
|
+
reportedAs: "facebook_reels_overlay",
|
|
300
|
+
platform: "facebook",
|
|
301
|
+
kind: "constraint",
|
|
302
|
+
note: "Ads on Facebook Reels take two shapes and a stock 9:16 video fits neither: overlay ads are 'a single, static image ad' (JPG/PNG, 1:1, 0% ratio tolerance, headline capped at 10 characters), and post-loop ads are 1:1 video of 4-15 seconds. Expect a long-headline 9:16 creative to look wrong here.",
|
|
303
|
+
contested: false,
|
|
304
|
+
source: "https://www.facebook.com/business/ads-guide/update/image/facebook-facebook-reels-overlay"
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
position: "explore_home",
|
|
308
|
+
reportedAs: "instagram_explore_grid_home",
|
|
309
|
+
platform: "instagram",
|
|
310
|
+
kind: "constraint",
|
|
311
|
+
note: "Marketing API placement asset customization states 'The explore_home placement only supports the SINGLE_IMAGE format', but Meta's own Ads Guide lists Instagram Explore home under video specs. Check the account's play rate for this position before treating it as image-only.",
|
|
312
|
+
contested: true,
|
|
313
|
+
source: "https://developers.facebook.com/docs/marketing-api/dynamic-creative/placement-asset-customization/"
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
position: "right_hand_column",
|
|
317
|
+
reportedAs: "right_hand_column",
|
|
318
|
+
platform: "facebook",
|
|
319
|
+
kind: "correction",
|
|
320
|
+
note: "Facebook right column publishes full video specs (MP4/MOV/GIF, 1:1 at 1440x1440 or mobile-only 4:5 at 1440x1800, 1 second to 241 minutes) \u2014 it is NOT image-only. Its real constraint is that it cannot be the only placement for a video, collection, or canvas ad.",
|
|
321
|
+
contested: false,
|
|
322
|
+
source: "https://www.facebook.com/business/ads-guide/update/video/facebook-right-hand-column"
|
|
323
|
+
}
|
|
324
|
+
];
|
|
325
|
+
var FACEBOOK_PREVIEW_FORMATS = {
|
|
326
|
+
feed: "MOBILE_FEED_STANDARD",
|
|
327
|
+
right_hand_column: "RIGHT_COLUMN_STANDARD",
|
|
328
|
+
marketplace: "MARKETPLACE_MOBILE",
|
|
329
|
+
facebook_stories: "FACEBOOK_STORY_MOBILE",
|
|
330
|
+
facebook_reels: "FACEBOOK_REELS_MOBILE",
|
|
331
|
+
facebook_reels_overlay: "FACEBOOK_REELS_BANNER",
|
|
332
|
+
instream_video: "INSTREAM_VIDEO_MOBILE",
|
|
333
|
+
// In the v25.0 ad_format enum and rendered live (2026-10-14).
|
|
334
|
+
facebook_profile_feed: "FACEBOOK_PROFILE_FEED_MOBILE",
|
|
335
|
+
// Retired for targeting (RETIRED_PLACEMENTS) and still reported by a breakdown
|
|
336
|
+
// over any window that predates the retirement — an ad set created before it
|
|
337
|
+
// keeps delivering there. Previewing what a historic row measured has to keep
|
|
338
|
+
// working, so this stays.
|
|
339
|
+
video_feeds: "INSTREAM_VIDEO_MOBILE"
|
|
340
|
+
};
|
|
341
|
+
var INSTAGRAM_PREVIEW_FORMATS = {
|
|
342
|
+
feed: "INSTAGRAM_STANDARD",
|
|
343
|
+
instagram_stories: "INSTAGRAM_STORY",
|
|
344
|
+
instagram_reels: "INSTAGRAM_REELS",
|
|
345
|
+
instagram_explore: "INSTAGRAM_EXPLORE_CONTEXTUAL",
|
|
346
|
+
instagram_explore_grid_home: "INSTAGRAM_EXPLORE_GRID_HOME",
|
|
347
|
+
instagram_search: "INSTAGRAM_SEARCH_CHAIN",
|
|
348
|
+
instagram_profile_feed: "INSTAGRAM_PROFILE_FEED"
|
|
349
|
+
};
|
|
350
|
+
var PLATFORM_SCOPED_POSITIONS = /* @__PURE__ */ new Set(["feed"]);
|
|
351
|
+
function previewFormatForPosition(position, platform) {
|
|
352
|
+
if (platform === "facebook") return FACEBOOK_PREVIEW_FORMATS[position];
|
|
353
|
+
if (platform === "instagram") return INSTAGRAM_PREVIEW_FORMATS[position];
|
|
354
|
+
if (PLATFORM_SCOPED_POSITIONS.has(position)) return void 0;
|
|
355
|
+
return FACEBOOK_PREVIEW_FORMATS[position] ?? INSTAGRAM_PREVIEW_FORMATS[position];
|
|
356
|
+
}
|
|
357
|
+
function placementFormatNote(reportedPosition) {
|
|
358
|
+
return CONSTRAINED_PLACEMENTS.find((p) => p.reportedAs === reportedPosition);
|
|
359
|
+
}
|
|
360
|
+
function positionsMissingRequiredFeed(targeting) {
|
|
361
|
+
const positions = targeting?.facebook_positions;
|
|
362
|
+
if (!positions?.length || positions.includes("feed")) {
|
|
363
|
+
return [];
|
|
364
|
+
}
|
|
365
|
+
return FACEBOOK_POSITIONS_REQUIRING_FEED.filter((required) => positions.includes(required));
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// ../api/src/ads-meta/brandSafety.ts
|
|
369
|
+
import { z } from "zod";
|
|
370
|
+
var publisherSchema = z.string().trim().min(3).max(2048).refine((value) => !/\s/.test(value), "a site or app address has no spaces");
|
|
371
|
+
var PUBLISHER_BLOCK_LIST_MAX_URLS = 1e4;
|
|
372
|
+
var publisherBlockListCreateSchema = z.strictObject({
|
|
373
|
+
name: z.string().trim().min(1).max(100),
|
|
374
|
+
/** The sites and apps to keep ads off. */
|
|
375
|
+
urls: z.array(publisherSchema).min(1).max(PUBLISHER_BLOCK_LIST_MAX_URLS)
|
|
376
|
+
});
|
|
377
|
+
var publisherBlockListUpdateSchema = z.strictObject({
|
|
378
|
+
/** More sites and apps to keep ads off. Meta takes none off a list. */
|
|
379
|
+
addUrls: z.array(publisherSchema).min(1).max(PUBLISHER_BLOCK_LIST_MAX_URLS).optional(),
|
|
380
|
+
status: z.literal("DELETED").optional()
|
|
381
|
+
}).refine((p) => p.addUrls === void 0 !== (p.status === void 0), "add urls OR delete the list");
|
|
382
|
+
var CONTENT_FILTER_LEVELS = [
|
|
383
|
+
"FACEBOOK_RELAXED",
|
|
384
|
+
"FACEBOOK_STANDARD",
|
|
385
|
+
"FACEBOOK_STRICT",
|
|
386
|
+
"AN_RELAXED",
|
|
387
|
+
"AN_STANDARD",
|
|
388
|
+
"AN_STRICT",
|
|
389
|
+
"FEED_RELAXED",
|
|
390
|
+
"FEED_STANDARD",
|
|
391
|
+
"FEED_STRICT"
|
|
392
|
+
];
|
|
393
|
+
var EXCLUDED_PUBLISHER_CATEGORIES = [
|
|
394
|
+
"dating",
|
|
395
|
+
"gambling",
|
|
396
|
+
"debated_social_issues",
|
|
397
|
+
"mature_audiences",
|
|
398
|
+
"tragedy_and_conflict"
|
|
399
|
+
];
|
|
400
|
+
var blockListRefSchema = z.union([z.string().regex(/^\d+$/), z.string().regex(TEMP_REF_REGEX)]);
|
|
401
|
+
var brandSafetyTargetingFields = {
|
|
402
|
+
excluded_publisher_list_ids: z.array(blockListRefSchema).min(1).optional(),
|
|
403
|
+
brand_safety_content_filter_levels: z.array(z.enum(CONTENT_FILTER_LEVELS)).min(1).refine(
|
|
404
|
+
(levels) => new Set(levels.map((level) => level.split("_")[0])).size === levels.length,
|
|
405
|
+
"one content filter level per family (FACEBOOK, AN, FEED)"
|
|
406
|
+
).optional(),
|
|
407
|
+
excluded_publisher_categories: z.array(z.enum(EXCLUDED_PUBLISHER_CATEGORIES)).min(1).optional()
|
|
408
|
+
};
|
|
409
|
+
var BRAND_SAFETY_KEYS = new Set(Object.keys(brandSafetyTargetingFields));
|
|
410
|
+
|
|
411
|
+
// ../api/src/ads-meta/catalogs.ts
|
|
412
|
+
import { z as z2 } from "zod";
|
|
413
|
+
var tempRefSchema = z2.string().regex(TEMP_REF_REGEX, "expected a meta_temp_* reference");
|
|
414
|
+
var catalogRefSchema = z2.union([z2.string().regex(NUMERIC_ID_REGEX, "expected a numeric id"), tempRefSchema]);
|
|
415
|
+
var httpsUrlSchema = z2.string().url().refine((value) => value.startsWith("https://"), "must be an https URL");
|
|
416
|
+
var CATALOG_LIMITS = {
|
|
417
|
+
nameMax: 100,
|
|
418
|
+
productNameMax: 200,
|
|
419
|
+
descriptionMax: 9999,
|
|
420
|
+
retailerIdMax: 100,
|
|
421
|
+
brandMax: 100,
|
|
422
|
+
additionalImagesMax: 20,
|
|
423
|
+
filterValuesMax: 500
|
|
424
|
+
};
|
|
425
|
+
var eventSourcesSchema = z2.array(z2.string().regex(NUMERIC_ID_REGEX)).min(1).max(10);
|
|
426
|
+
var productCatalogCreateSchema = z2.strictObject({
|
|
427
|
+
name: z2.string().trim().min(1).max(CATALOG_LIMITS.nameMax),
|
|
428
|
+
/** The Business it is made on — filled at staging from the ad account's own Business; any other is refused. */
|
|
429
|
+
business_id: z2.string().regex(NUMERIC_ID_REGEX).optional(),
|
|
430
|
+
/**
|
|
431
|
+
* Pixels to connect once it exists. Advantage+ catalog ads need one: live on
|
|
432
|
+
* v26.0 a catalog ad set on a catalog without one was refused — "An event
|
|
433
|
+
* source (Facebook Pixel or App) is required to run Dynamic Ads" (1885059).
|
|
434
|
+
*/
|
|
435
|
+
event_sources: eventSourcesSchema.optional()
|
|
436
|
+
});
|
|
437
|
+
var productCatalogUpdateSchema = z2.strictObject({
|
|
438
|
+
name: z2.string().trim().min(1).max(CATALOG_LIMITS.nameMax).optional(),
|
|
439
|
+
event_sources: eventSourcesSchema.optional(),
|
|
440
|
+
unlink_event_sources: eventSourcesSchema.optional(),
|
|
441
|
+
status: z2.literal("DELETED").optional()
|
|
442
|
+
}).superRefine((p, ctx) => {
|
|
443
|
+
const changes = [p.name, p.event_sources, p.unlink_event_sources].filter((value) => value !== void 0).length;
|
|
444
|
+
if (p.status === "DELETED" && changes > 0) {
|
|
445
|
+
ctx.addIssue({ code: "custom", message: "a catalog delete changes nothing else" });
|
|
446
|
+
}
|
|
447
|
+
if (p.status === void 0 && changes === 0) {
|
|
448
|
+
ctx.addIssue({ code: "custom", message: "rename the catalog, connect or disconnect a pixel, or delete it" });
|
|
449
|
+
}
|
|
450
|
+
const both = (p.event_sources ?? []).filter((id) => p.unlink_event_sources?.includes(id));
|
|
451
|
+
if (both.length > 0) {
|
|
452
|
+
ctx.addIssue({ code: "custom", message: `pixel ${both.join(", ")} is both connected and disconnected` });
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
var PRODUCT_AVAILABILITIES = [
|
|
456
|
+
"in stock",
|
|
457
|
+
"out of stock",
|
|
458
|
+
"preorder",
|
|
459
|
+
"available for order",
|
|
460
|
+
"discontinued"
|
|
461
|
+
];
|
|
462
|
+
var PRODUCT_CONDITIONS = ["new", "refurbished", "used"];
|
|
463
|
+
var priceSchema = z2.string().regex(/^\d{1,9}(\.\d{1,2})?$/, 'a price in major units, e.g. "19.99"');
|
|
464
|
+
var currencySchema = z2.string().regex(/^[A-Z]{3}$/, "a three-letter currency code, e.g. EUR");
|
|
465
|
+
var productFields = {
|
|
466
|
+
name: z2.string().trim().min(1).max(CATALOG_LIMITS.productNameMax),
|
|
467
|
+
description: z2.string().trim().min(1).max(CATALOG_LIMITS.descriptionMax),
|
|
468
|
+
price: priceSchema,
|
|
469
|
+
sale_price: priceSchema,
|
|
470
|
+
currency: currencySchema,
|
|
471
|
+
availability: z2.enum(PRODUCT_AVAILABILITIES),
|
|
472
|
+
condition: z2.enum(PRODUCT_CONDITIONS),
|
|
473
|
+
/** The product's own page — where its ad sends people. */
|
|
474
|
+
url: httpsUrlSchema,
|
|
475
|
+
/** A picture Meta fetches: a public https address (a library image's address works). */
|
|
476
|
+
image_url: httpsUrlSchema,
|
|
477
|
+
additional_image_urls: z2.array(httpsUrlSchema).min(1).max(CATALOG_LIMITS.additionalImagesMax),
|
|
478
|
+
brand: z2.string().trim().min(1).max(CATALOG_LIMITS.brandMax)
|
|
479
|
+
};
|
|
480
|
+
var productCreateSchema = z2.strictObject({
|
|
481
|
+
catalog_id: catalogRefSchema,
|
|
482
|
+
/** The advertiser's own id for the product (Meta's `retailer_id`, the SKU) — what a product set filters on. */
|
|
483
|
+
retailer_id: z2.string().trim().min(1).max(CATALOG_LIMITS.retailerIdMax),
|
|
484
|
+
name: productFields.name,
|
|
485
|
+
description: productFields.description,
|
|
486
|
+
price: productFields.price,
|
|
487
|
+
sale_price: productFields.sale_price.optional(),
|
|
488
|
+
currency: productFields.currency,
|
|
489
|
+
availability: productFields.availability.default("in stock"),
|
|
490
|
+
condition: productFields.condition.default("new"),
|
|
491
|
+
url: productFields.url,
|
|
492
|
+
image_url: productFields.image_url,
|
|
493
|
+
additional_image_urls: productFields.additional_image_urls.optional(),
|
|
494
|
+
brand: productFields.brand.optional()
|
|
495
|
+
});
|
|
496
|
+
var productUpdateSchema = z2.strictObject({
|
|
497
|
+
name: productFields.name.optional(),
|
|
498
|
+
description: productFields.description.optional(),
|
|
499
|
+
price: productFields.price.optional(),
|
|
500
|
+
sale_price: productFields.sale_price.optional(),
|
|
501
|
+
currency: productFields.currency.optional(),
|
|
502
|
+
availability: productFields.availability.optional(),
|
|
503
|
+
condition: productFields.condition.optional(),
|
|
504
|
+
url: productFields.url.optional(),
|
|
505
|
+
image_url: productFields.image_url.optional(),
|
|
506
|
+
additional_image_urls: productFields.additional_image_urls.optional(),
|
|
507
|
+
brand: productFields.brand.optional(),
|
|
508
|
+
status: z2.literal("DELETED").optional()
|
|
509
|
+
}).superRefine((p, ctx) => {
|
|
510
|
+
const changes = Object.keys(p).filter((key) => key !== "status");
|
|
511
|
+
if (p.status === "DELETED" && changes.length > 0) {
|
|
512
|
+
ctx.addIssue({ code: "custom", message: "a product delete changes nothing else" });
|
|
513
|
+
}
|
|
514
|
+
if (p.status === void 0 && changes.length === 0) {
|
|
515
|
+
ctx.addIssue({ code: "custom", message: "change at least one field, or delete the product" });
|
|
516
|
+
}
|
|
517
|
+
if ((p.price !== void 0 || p.sale_price !== void 0) && p.currency === void 0) {
|
|
518
|
+
ctx.addIssue({ code: "custom", path: ["currency"], message: "a price names its currency" });
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
var LIST_OPERATORS = ["is_any", "is_not_any"];
|
|
522
|
+
var NUMBER_OPERATORS = ["lt", "lte", "gt", "gte"];
|
|
523
|
+
var TEXT_OPERATORS = [
|
|
524
|
+
"eq",
|
|
525
|
+
"neq",
|
|
526
|
+
"contains",
|
|
527
|
+
"not_contains",
|
|
528
|
+
"i_contains",
|
|
529
|
+
"i_not_contains",
|
|
530
|
+
"starts_with"
|
|
531
|
+
];
|
|
532
|
+
var PRODUCT_SET_OPERATORS = [...LIST_OPERATORS, ...NUMBER_OPERATORS, ...TEXT_OPERATORS];
|
|
533
|
+
var conditionSchema = z2.union([
|
|
534
|
+
...LIST_OPERATORS.map(
|
|
535
|
+
(operator) => z2.strictObject({ [operator]: z2.array(z2.string().min(1)).min(1).max(CATALOG_LIMITS.filterValuesMax) })
|
|
536
|
+
),
|
|
537
|
+
...NUMBER_OPERATORS.map((operator) => z2.strictObject({ [operator]: z2.number() })),
|
|
538
|
+
...TEXT_OPERATORS.map((operator) => z2.strictObject({ [operator]: z2.string().min(1) }))
|
|
539
|
+
]);
|
|
540
|
+
var FIELD_NAME_REGEX = /^[a-z][a-z0-9_.]*$/;
|
|
541
|
+
function productSetFilterIssue(rule) {
|
|
542
|
+
if (!rule || typeof rule !== "object" || Array.isArray(rule)) return "a rule is an object";
|
|
543
|
+
const entries = Object.entries(rule);
|
|
544
|
+
if (entries.length !== 1) return "one field per rule \u2014 combine several with and / or";
|
|
545
|
+
const [key, value] = entries[0];
|
|
546
|
+
if (key === "and" || key === "or") {
|
|
547
|
+
if (!Array.isArray(value) || value.length === 0) return `${key} takes a list of rules`;
|
|
548
|
+
for (const inner of value) {
|
|
549
|
+
const issue = productSetFilterIssue(inner);
|
|
550
|
+
if (issue) return issue;
|
|
551
|
+
}
|
|
552
|
+
return void 0;
|
|
553
|
+
}
|
|
554
|
+
if (!FIELD_NAME_REGEX.test(key)) {
|
|
555
|
+
return "a rule names a product field, e.g. retailer_id, brand, product_type, price, custom_label_0";
|
|
556
|
+
}
|
|
557
|
+
return conditionSchema.safeParse(value).success ? void 0 : `${key}: one operator of ${PRODUCT_SET_OPERATORS.join(", ")} with its value \u2014 a list for is_any / is_not_any, a number for lt / lte / gt / gte, text for the rest`;
|
|
558
|
+
}
|
|
559
|
+
var productSetFilterSchema = z2.record(z2.string(), z2.unknown()).superRefine((rule, ctx) => {
|
|
560
|
+
const issue = productSetFilterIssue(rule);
|
|
561
|
+
if (issue) ctx.addIssue({ code: "custom", message: issue });
|
|
562
|
+
}).transform((rule) => rule);
|
|
563
|
+
var productSetCreateSchema = z2.strictObject({
|
|
564
|
+
catalog_id: catalogRefSchema,
|
|
565
|
+
name: z2.string().trim().min(1).max(CATALOG_LIMITS.nameMax),
|
|
566
|
+
/** Which products are in it. Without one the set holds the whole catalog. */
|
|
567
|
+
filter: productSetFilterSchema.optional()
|
|
568
|
+
});
|
|
569
|
+
var productSetUpdateSchema = z2.strictObject({
|
|
570
|
+
name: z2.string().trim().min(1).max(CATALOG_LIMITS.nameMax).optional(),
|
|
571
|
+
filter: productSetFilterSchema.optional(),
|
|
572
|
+
status: z2.literal("DELETED").optional()
|
|
573
|
+
}).superRefine((p, ctx) => {
|
|
574
|
+
const changes = p.name !== void 0 || p.filter !== void 0;
|
|
575
|
+
if (p.status === "DELETED" && changes) {
|
|
576
|
+
ctx.addIssue({ code: "custom", message: "a product set delete changes nothing else" });
|
|
577
|
+
}
|
|
578
|
+
if (p.status === void 0 && !changes) {
|
|
579
|
+
ctx.addIssue({ code: "custom", message: "rename the set, change its filter, or delete it" });
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
function retailerIdFilter(retailerIds) {
|
|
583
|
+
return { retailer_id: { is_any: [...retailerIds] } };
|
|
584
|
+
}
|
|
585
|
+
var FEED_INTERVALS = ["HOURLY", "DAILY", "WEEKLY"];
|
|
586
|
+
var FEED_DAYS = ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY"];
|
|
587
|
+
var feedScheduleSchema = z2.strictObject({
|
|
588
|
+
url: httpsUrlSchema,
|
|
589
|
+
interval: z2.enum(FEED_INTERVALS),
|
|
590
|
+
/** Hour of day, 0–23, in Meta's feed time zone. */
|
|
591
|
+
hour: z2.number().int().min(0).max(23).optional(),
|
|
592
|
+
day_of_week: z2.enum(FEED_DAYS).optional()
|
|
593
|
+
}).superRefine((s, ctx) => {
|
|
594
|
+
if (s.interval === "WEEKLY" && s.day_of_week === void 0) {
|
|
595
|
+
ctx.addIssue({ code: "custom", path: ["day_of_week"], message: "a weekly schedule names its day" });
|
|
596
|
+
}
|
|
597
|
+
if (s.interval !== "WEEKLY" && s.day_of_week !== void 0) {
|
|
598
|
+
ctx.addIssue({ code: "custom", path: ["day_of_week"], message: "only a weekly schedule names a day" });
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
var productFeedCreateSchema = z2.strictObject({
|
|
602
|
+
catalog_id: catalogRefSchema,
|
|
603
|
+
name: z2.string().trim().min(1).max(CATALOG_LIMITS.nameMax),
|
|
604
|
+
/** Without one the feed waits for files uploaded to it. */
|
|
605
|
+
schedule: feedScheduleSchema.optional()
|
|
606
|
+
});
|
|
607
|
+
var productFeedUpdateSchema = z2.strictObject({
|
|
608
|
+
name: z2.string().trim().min(1).max(CATALOG_LIMITS.nameMax).optional(),
|
|
609
|
+
schedule: feedScheduleSchema.optional(),
|
|
610
|
+
status: z2.literal("DELETED").optional()
|
|
611
|
+
}).superRefine((p, ctx) => {
|
|
612
|
+
const changes = p.name !== void 0 || p.schedule !== void 0;
|
|
613
|
+
if (p.status === "DELETED" && changes) {
|
|
614
|
+
ctx.addIssue({ code: "custom", message: "a feed delete changes nothing else" });
|
|
615
|
+
}
|
|
616
|
+
if (p.status === void 0 && !changes) {
|
|
617
|
+
ctx.addIssue({ code: "custom", message: "rename the feed, change its schedule, or delete it" });
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
// ../api/src/ads-meta/customerList.ts
|
|
622
|
+
import { z as z3 } from "zod";
|
|
623
|
+
var CUSTOMER_LIST_KEYS = [
|
|
624
|
+
"EMAIL",
|
|
625
|
+
"PHONE",
|
|
626
|
+
"FN",
|
|
627
|
+
"LN",
|
|
628
|
+
"FI",
|
|
629
|
+
"CT",
|
|
630
|
+
"ST",
|
|
631
|
+
"ZIP",
|
|
632
|
+
"COUNTRY",
|
|
633
|
+
"DOBY",
|
|
634
|
+
"DOBM",
|
|
635
|
+
"DOBD",
|
|
636
|
+
"GEN",
|
|
637
|
+
"MADID",
|
|
638
|
+
"EXTERN_ID",
|
|
639
|
+
"LOOKALIKE_VALUE"
|
|
640
|
+
];
|
|
641
|
+
var UNHASHED_CUSTOMER_LIST_KEYS = /* @__PURE__ */ new Set([
|
|
642
|
+
"MADID",
|
|
643
|
+
"EXTERN_ID",
|
|
644
|
+
"LOOKALIKE_VALUE"
|
|
645
|
+
]);
|
|
646
|
+
var IDENTIFYING_CUSTOMER_LIST_KEYS = ["EMAIL", "PHONE", "MADID", "EXTERN_ID"];
|
|
647
|
+
var CUSTOMER_LIST_BATCH_ROWS = 1e4;
|
|
648
|
+
var CUSTOMER_LIST_MAX_BATCHES = 20;
|
|
649
|
+
var SHA256_HEX = /^[0-9a-f]{64}$/;
|
|
650
|
+
var MADID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
651
|
+
function customerListCellIssue(key, value) {
|
|
652
|
+
if (value === "") {
|
|
653
|
+
return key === "LOOKALIKE_VALUE" ? "a value-based list carries a value for every person \u2014 give the row a value or leave it out" : void 0;
|
|
654
|
+
}
|
|
655
|
+
if (key === "LOOKALIKE_VALUE") {
|
|
656
|
+
return /^\d+(\.\d+)?$/.test(value) ? void 0 : "a value-based list carries a number of 0 or more per person";
|
|
657
|
+
}
|
|
658
|
+
if (key === "MADID") {
|
|
659
|
+
return MADID.test(value) ? void 0 : "a mobile advertiser id is a lowercase IDFA/AAID with hyphens";
|
|
660
|
+
}
|
|
661
|
+
if (key === "EXTERN_ID") {
|
|
662
|
+
return value.length <= 256 ? void 0 : "an external id is at most 256 characters";
|
|
663
|
+
}
|
|
664
|
+
return SHA256_HEX.test(value) ? void 0 : `${key} must be the lowercase SHA-256 digest the CLI makes from the normalized value, never the value itself`;
|
|
665
|
+
}
|
|
666
|
+
var PREVIEW_KIND = {
|
|
667
|
+
EMAIL: "email",
|
|
668
|
+
PHONE: "phone",
|
|
669
|
+
FN: "name",
|
|
670
|
+
LN: "name",
|
|
671
|
+
FI: "hidden",
|
|
672
|
+
CT: "name",
|
|
673
|
+
ST: "shown",
|
|
674
|
+
ZIP: "name",
|
|
675
|
+
COUNTRY: "country",
|
|
676
|
+
DOBY: "hidden",
|
|
677
|
+
DOBM: "hidden",
|
|
678
|
+
DOBD: "hidden",
|
|
679
|
+
GEN: "shown",
|
|
680
|
+
MADID: "id",
|
|
681
|
+
EXTERN_ID: "shown",
|
|
682
|
+
LOOKALIKE_VALUE: "shown"
|
|
683
|
+
};
|
|
684
|
+
function customerListPreviewCell(key, normalized) {
|
|
685
|
+
return maskPersonValue(PREVIEW_KIND[key], normalized);
|
|
686
|
+
}
|
|
687
|
+
function customerListPreviewIssue(key, preview) {
|
|
688
|
+
return maskedPersonValueIssue(PREVIEW_KIND[key], preview) && `the preview of ${key} must be masked the way the CLI masks it, never the value itself`;
|
|
689
|
+
}
|
|
690
|
+
function previewsIssue(batch) {
|
|
691
|
+
if (!batch.previews) return void 0;
|
|
692
|
+
if (batch.previews.length !== batch.rows.length) return { path: ["previews"], message: "one preview per row" };
|
|
693
|
+
for (const [index, preview] of batch.previews.entries()) {
|
|
694
|
+
const message = preview.length === batch.schema.length ? batch.schema.map((key, column) => customerListPreviewIssue(key, preview[column] ?? "")).find(Boolean) : `each preview has one value per column (${batch.schema.length})`;
|
|
695
|
+
if (message) return { path: ["previews", index], message };
|
|
696
|
+
}
|
|
697
|
+
return void 0;
|
|
698
|
+
}
|
|
699
|
+
var schemaColumnsSchema = z3.array(z3.enum(CUSTOMER_LIST_KEYS)).min(1).refine((keys) => new Set(keys).size === keys.length, "each column appears once").refine(
|
|
700
|
+
(keys) => keys.some((key) => IDENTIFYING_CUSTOMER_LIST_KEYS.includes(key)),
|
|
701
|
+
`a customer list matches people on at least one of ${IDENTIFYING_CUSTOMER_LIST_KEYS.join(", ")}`
|
|
702
|
+
);
|
|
703
|
+
var customerListBatchSchema = z3.strictObject({
|
|
704
|
+
schema: schemaColumnsSchema,
|
|
705
|
+
rows: z3.array(z3.array(z3.string())).min(1).max(CUSTOMER_LIST_BATCH_ROWS),
|
|
706
|
+
/** Each row's person masked for the review (`customerListPreviewCell`), in the rows' order. Older CLIs send none. */
|
|
707
|
+
previews: z3.array(z3.array(z3.string())).optional()
|
|
708
|
+
}).superRefine((batch, ctx) => {
|
|
709
|
+
const previews = previewsIssue(batch);
|
|
710
|
+
if (previews) {
|
|
711
|
+
ctx.addIssue({ code: "custom", path: previews.path, message: previews.message });
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
for (const [index, row] of batch.rows.entries()) {
|
|
715
|
+
if (row.length !== batch.schema.length) {
|
|
716
|
+
ctx.addIssue({
|
|
717
|
+
code: "custom",
|
|
718
|
+
path: ["rows", index],
|
|
719
|
+
message: `each row has one value per column (${batch.schema.length})`
|
|
720
|
+
});
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
for (const [column, key] of batch.schema.entries()) {
|
|
724
|
+
const issue = customerListCellIssue(key, row[column] ?? "");
|
|
725
|
+
if (issue) {
|
|
726
|
+
ctx.addIssue({ code: "custom", path: ["rows", index, column], message: issue });
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
});
|
|
732
|
+
var customerListBatchResponseSchema = z3.object({ fileId: z3.string(), rows: z3.number().int() });
|
|
733
|
+
var CUSTOMER_LIST_ACTIONS = ["add", "remove", "replace"];
|
|
734
|
+
var customAudienceMembersSchema = z3.strictObject({
|
|
735
|
+
audience_id: z3.union([z3.string().regex(NUMERIC_ID_REGEX), z3.string().regex(TEMP_REF_REGEX)]),
|
|
736
|
+
action: z3.enum(CUSTOMER_LIST_ACTIONS),
|
|
737
|
+
schema: schemaColumnsSchema,
|
|
738
|
+
batches: z3.array(z3.strictObject({ fileId: z3.string().min(1), rows: z3.number().int().min(1).max(CUSTOMER_LIST_BATCH_ROWS) })).min(1).max(CUSTOMER_LIST_MAX_BATCHES)
|
|
739
|
+
}).superRefine((p, ctx) => {
|
|
740
|
+
if (new Set(p.batches.map((batch) => batch.fileId)).size !== p.batches.length) {
|
|
741
|
+
ctx.addIssue({ code: "custom", path: ["batches"], message: "each batch is sent once" });
|
|
742
|
+
}
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
// ../api/src/ads-meta/ops.ts
|
|
746
|
+
import { z as z12 } from "zod";
|
|
747
|
+
|
|
748
|
+
// ../api/src/ads-meta/adRules.ts
|
|
749
|
+
import { z as z4 } from "zod";
|
|
750
|
+
var AD_RULE_EVALUATION_TYPES = ["SCHEDULE", "TRIGGER"];
|
|
751
|
+
var AD_RULE_OPERATORS = [
|
|
752
|
+
"GREATER_THAN",
|
|
753
|
+
"LESS_THAN",
|
|
754
|
+
"EQUAL",
|
|
755
|
+
"NOT_EQUAL",
|
|
756
|
+
"IN_RANGE",
|
|
757
|
+
"NOT_IN_RANGE",
|
|
758
|
+
"IN",
|
|
759
|
+
"NOT_IN",
|
|
760
|
+
"CONTAIN",
|
|
761
|
+
"NOT_CONTAIN",
|
|
762
|
+
"ANY",
|
|
763
|
+
"ALL",
|
|
764
|
+
"NONE"
|
|
765
|
+
];
|
|
766
|
+
var AD_RULE_TRIGGER_TYPES = [
|
|
767
|
+
"METADATA_CREATION",
|
|
768
|
+
"METADATA_UPDATE",
|
|
769
|
+
"STATS_MILESTONE",
|
|
770
|
+
"STATS_CHANGE",
|
|
771
|
+
"DELIVERY_INSIGHTS_CHANGE"
|
|
772
|
+
];
|
|
773
|
+
var AD_RULE_EXECUTION_TYPES = [
|
|
774
|
+
"PAUSE",
|
|
775
|
+
"UNPAUSE",
|
|
776
|
+
"NOTIFICATION",
|
|
777
|
+
"CHANGE_BUDGET",
|
|
778
|
+
"CHANGE_CAMPAIGN_BUDGET",
|
|
779
|
+
"CHANGE_BID",
|
|
780
|
+
"ROTATE",
|
|
781
|
+
"REBALANCE_BUDGET"
|
|
782
|
+
];
|
|
783
|
+
var TRIGGER_EXECUTION_TYPES = /* @__PURE__ */ new Set(["PAUSE", "UNPAUSE", "NOTIFICATION"]);
|
|
784
|
+
var AD_RULE_SCHEDULE_TYPES = ["DAILY", "HOURLY", "SEMI_HOURLY", "CUSTOM"];
|
|
785
|
+
var AD_RULE_CREATE_STATUSES = ["ENABLED", "DISABLED"];
|
|
786
|
+
var AD_RULE_UPDATE_STATUSES = ["ENABLED", "DISABLED", "DELETED"];
|
|
787
|
+
var AD_RULE_ENTITY_TYPES = ["AD", "ADSET", "CAMPAIGN"];
|
|
788
|
+
var TIME_PRESETS_WITH_TODAY = [
|
|
789
|
+
"LIFETIME",
|
|
790
|
+
"TODAY",
|
|
791
|
+
"LAST_2_DAYS",
|
|
792
|
+
"LAST_3_DAYS",
|
|
793
|
+
"LAST_7_DAYS",
|
|
794
|
+
"LAST_14_DAYS",
|
|
795
|
+
"LAST_28_DAYS",
|
|
796
|
+
"LAST_30_DAYS",
|
|
797
|
+
"THIS_MONTH",
|
|
798
|
+
"THIS_WEEK_MON_TODAY",
|
|
799
|
+
"THIS_WEEK_SUN_TODAY"
|
|
800
|
+
];
|
|
801
|
+
var AD_RULE_TIME_PRESETS = [
|
|
802
|
+
...TIME_PRESETS_WITH_TODAY,
|
|
803
|
+
"YESTERDAY",
|
|
804
|
+
"LAST_2D",
|
|
805
|
+
"LAST_3D",
|
|
806
|
+
"LAST_7D",
|
|
807
|
+
"LAST_14D",
|
|
808
|
+
"LAST_28D",
|
|
809
|
+
"LAST_30D",
|
|
810
|
+
"LAST_ND_14_8",
|
|
811
|
+
"LAST_ND_30_8",
|
|
812
|
+
"LAST_ND_60_8",
|
|
813
|
+
"LAST_ND_120_8",
|
|
814
|
+
"LAST_ND_180_8",
|
|
815
|
+
"LAST_ND_LIFETIME_8",
|
|
816
|
+
"LAST_ND_60_29",
|
|
817
|
+
"LAST_ND_120_29",
|
|
818
|
+
"LAST_ND_180_29",
|
|
819
|
+
"LAST_ND_LIFETIME_29"
|
|
820
|
+
];
|
|
821
|
+
var SPECIAL_FIELDS = ["entity_type", "time_preset", "attribution_window", "aggregation_id"];
|
|
822
|
+
var METADATA_FIELDS = [
|
|
823
|
+
"id",
|
|
824
|
+
"name",
|
|
825
|
+
"adlabel_ids",
|
|
826
|
+
"objective",
|
|
827
|
+
"start_time",
|
|
828
|
+
"stop_time",
|
|
829
|
+
"buying_type",
|
|
830
|
+
"billing_event",
|
|
831
|
+
"optimization_goal",
|
|
832
|
+
"is_autobid",
|
|
833
|
+
"daily_budget",
|
|
834
|
+
"lifetime_budget",
|
|
835
|
+
"spend_cap",
|
|
836
|
+
"bid_amount",
|
|
837
|
+
"created_time",
|
|
838
|
+
"updated_time"
|
|
839
|
+
];
|
|
840
|
+
var SCHEDULE_ONLY_METADATA_FIELDS = [
|
|
841
|
+
"effective_status",
|
|
842
|
+
"placement.page_types",
|
|
843
|
+
"budget_reset_period",
|
|
844
|
+
"hours_since_creation",
|
|
845
|
+
"estimated_budget_spending_percentage",
|
|
846
|
+
"audience_reached_percentage",
|
|
847
|
+
"active_time",
|
|
848
|
+
"current_time"
|
|
849
|
+
];
|
|
850
|
+
var APP_EVENTS = [
|
|
851
|
+
"achievement_unlocked",
|
|
852
|
+
"activate_app",
|
|
853
|
+
"add_payment_info",
|
|
854
|
+
"add_to_cart",
|
|
855
|
+
"add_to_wishlist",
|
|
856
|
+
"complete_registration",
|
|
857
|
+
"content_view",
|
|
858
|
+
"initiated_checkout",
|
|
859
|
+
"level_achieved",
|
|
860
|
+
"purchase",
|
|
861
|
+
"rate",
|
|
862
|
+
"search",
|
|
863
|
+
"spent_credits",
|
|
864
|
+
"tutorial_completion"
|
|
865
|
+
];
|
|
866
|
+
var OFFLINE_EVENTS = [
|
|
867
|
+
"add_payment_info",
|
|
868
|
+
"add_to_cart",
|
|
869
|
+
"add_to_wishlist",
|
|
870
|
+
"complete_registration",
|
|
871
|
+
"initiate_checkout",
|
|
872
|
+
"lead",
|
|
873
|
+
"other",
|
|
874
|
+
"purchase",
|
|
875
|
+
"search",
|
|
876
|
+
"view_content"
|
|
877
|
+
];
|
|
878
|
+
var PIXEL_EVENTS = [
|
|
879
|
+
"add_payment_info",
|
|
880
|
+
"add_to_cart",
|
|
881
|
+
"add_to_wishlist",
|
|
882
|
+
"complete_registration",
|
|
883
|
+
"initiate_checkout",
|
|
884
|
+
"lead",
|
|
885
|
+
"purchase",
|
|
886
|
+
"search",
|
|
887
|
+
"view_content",
|
|
888
|
+
"other"
|
|
889
|
+
];
|
|
890
|
+
var PIXEL_COST_EVENTS = [
|
|
891
|
+
"add_payment_info",
|
|
892
|
+
"add_to_cart",
|
|
893
|
+
"add_to_wishlist",
|
|
894
|
+
"complete_registration",
|
|
895
|
+
"initiate_checkout",
|
|
896
|
+
"lead",
|
|
897
|
+
"purchase",
|
|
898
|
+
"search",
|
|
899
|
+
"view_content"
|
|
900
|
+
];
|
|
901
|
+
var SCHEDULE_ONLY_INSIGHTS_FIELDS = [
|
|
902
|
+
"mobile_app_purchase_roas",
|
|
903
|
+
"website_purchase_roas",
|
|
904
|
+
"offline_conversion",
|
|
905
|
+
...OFFLINE_EVENTS.map((event) => `offline_conversion.${event}`),
|
|
906
|
+
"cost_per_offline_conversion",
|
|
907
|
+
"cost_per_offline_other",
|
|
908
|
+
"cost_per_post_engagement",
|
|
909
|
+
"lifetime_impressions",
|
|
910
|
+
"lifetime_spent",
|
|
911
|
+
"today_spent",
|
|
912
|
+
"yesterday_spent",
|
|
913
|
+
// The same page lists both as "Yes" higher up and "No" in its last rows; the
|
|
914
|
+
// stricter reading is kept, since a refused real-time rule fails at publish.
|
|
915
|
+
"unique_clicks",
|
|
916
|
+
"reach"
|
|
917
|
+
];
|
|
918
|
+
var INSIGHTS_FIELDS = /* @__PURE__ */ new Set([
|
|
919
|
+
...SCHEDULE_ONLY_INSIGHTS_FIELDS,
|
|
920
|
+
"impressions",
|
|
921
|
+
"unique_impressions",
|
|
922
|
+
"clicks",
|
|
923
|
+
"unique_clicks",
|
|
924
|
+
"spent",
|
|
925
|
+
"results",
|
|
926
|
+
"cost_per",
|
|
927
|
+
"cpc",
|
|
928
|
+
"cpm",
|
|
929
|
+
"ctr",
|
|
930
|
+
"cpa",
|
|
931
|
+
"cpp",
|
|
932
|
+
"reach",
|
|
933
|
+
"frequency",
|
|
934
|
+
"leadgen",
|
|
935
|
+
"link_ctr",
|
|
936
|
+
"cost_per_unique_click",
|
|
937
|
+
"result_rate",
|
|
938
|
+
"mobile_app_install",
|
|
939
|
+
"cost_per_mobile_app_install",
|
|
940
|
+
"app_custom_event",
|
|
941
|
+
...APP_EVENTS.map((event) => `app_custom_event.fb_mobile_${event}`),
|
|
942
|
+
"app_custom_event.other",
|
|
943
|
+
...APP_EVENTS.map((event) => `cost_per_mobile_${event}`),
|
|
944
|
+
"offsite_conversion",
|
|
945
|
+
...PIXEL_EVENTS.map((event) => `offsite_conversion.fb_pixel_${event}`),
|
|
946
|
+
...PIXEL_COST_EVENTS.map((event) => `cost_per_${event}_fb`),
|
|
947
|
+
"link_click",
|
|
948
|
+
"cost_per_link_click",
|
|
949
|
+
"like",
|
|
950
|
+
"offsite_engagement",
|
|
951
|
+
"post",
|
|
952
|
+
"post_comment",
|
|
953
|
+
"post_engagement",
|
|
954
|
+
"post_like",
|
|
955
|
+
"post_reaction",
|
|
956
|
+
"view_content",
|
|
957
|
+
"video_play",
|
|
958
|
+
"video_view",
|
|
959
|
+
"vote"
|
|
960
|
+
]);
|
|
961
|
+
var ADVANCED_FIELDS = /* @__PURE__ */ new Set(["daily_ratio_spent", "lifetime_ratio_spent"]);
|
|
962
|
+
var OBJECT_PREFIXES = ["ad", "adset", "campaign"];
|
|
963
|
+
var ATTRIBUTION_PREFIXES = [
|
|
964
|
+
"account_default",
|
|
965
|
+
"default",
|
|
966
|
+
"inline",
|
|
967
|
+
"1d_view",
|
|
968
|
+
"7d_view",
|
|
969
|
+
"28d_view",
|
|
970
|
+
"1d_click",
|
|
971
|
+
"7d_click",
|
|
972
|
+
"28d_click",
|
|
973
|
+
"1d_view_1d_click",
|
|
974
|
+
"7d_view_1d_click",
|
|
975
|
+
"28d_view_1d_click",
|
|
976
|
+
"1d_view_7d_click",
|
|
977
|
+
"7d_view_7d_click",
|
|
978
|
+
"28d_view_7d_click",
|
|
979
|
+
"7d_view_28d_click",
|
|
980
|
+
"28d_view_28d_click"
|
|
981
|
+
];
|
|
982
|
+
var LIVE_ATTRIBUTION_PREFIX = "account_default";
|
|
983
|
+
var AD_RULE_ATTRIBUTION_WINDOWS = [LIVE_ATTRIBUTION_PREFIX.toUpperCase()];
|
|
984
|
+
var RETIRED_ATTRIBUTION = `Meta retired every attribution window in rules but the account's own (#11 "This attribution window has been deprecated") \u2014 leave the window out, or use ACCOUNT_DEFAULT (account_default:)`;
|
|
985
|
+
function retiredAttributionPrefix(field) {
|
|
986
|
+
const colon = field.indexOf(":");
|
|
987
|
+
const prefix = colon > 0 ? field.slice(0, colon) : void 0;
|
|
988
|
+
return prefix !== void 0 && prefix !== LIVE_ATTRIBUTION_PREFIX && ATTRIBUTION_PREFIXES.includes(prefix);
|
|
989
|
+
}
|
|
990
|
+
var TIME_PREFIXES = AD_RULE_TIME_PRESETS.map((preset) => `${preset.toLowerCase()}_`).sort(
|
|
991
|
+
(a, b) => b.length - a.length
|
|
992
|
+
);
|
|
993
|
+
var KNOWN_PLAIN_FIELDS = /* @__PURE__ */ new Set([
|
|
994
|
+
...SPECIAL_FIELDS,
|
|
995
|
+
...METADATA_FIELDS,
|
|
996
|
+
...SCHEDULE_ONLY_METADATA_FIELDS,
|
|
997
|
+
...INSIGHTS_FIELDS,
|
|
998
|
+
...ADVANCED_FIELDS
|
|
999
|
+
]);
|
|
1000
|
+
function plainKind(field) {
|
|
1001
|
+
if (SPECIAL_FIELDS.includes(field)) return "special";
|
|
1002
|
+
if (INSIGHTS_FIELDS.has(field)) return "insights";
|
|
1003
|
+
if (ADVANCED_FIELDS.has(field)) return "advanced";
|
|
1004
|
+
if (KNOWN_PLAIN_FIELDS.has(field)) return "metadata";
|
|
1005
|
+
return void 0;
|
|
1006
|
+
}
|
|
1007
|
+
function insightsBase(field) {
|
|
1008
|
+
let rest = field;
|
|
1009
|
+
const colon = rest.indexOf(":");
|
|
1010
|
+
if (colon > 0) {
|
|
1011
|
+
if (!ATTRIBUTION_PREFIXES.includes(rest.slice(0, colon))) return void 0;
|
|
1012
|
+
rest = rest.slice(colon + 1);
|
|
1013
|
+
}
|
|
1014
|
+
if (INSIGHTS_FIELDS.has(rest)) return rest;
|
|
1015
|
+
const prefix = TIME_PREFIXES.find((candidate) => rest.startsWith(candidate));
|
|
1016
|
+
const base = prefix ? rest.slice(prefix.length) : void 0;
|
|
1017
|
+
return base && INSIGHTS_FIELDS.has(base) ? base : void 0;
|
|
1018
|
+
}
|
|
1019
|
+
function singleField(field) {
|
|
1020
|
+
const kind = plainKind(field);
|
|
1021
|
+
if (kind) return { kind, base: field };
|
|
1022
|
+
const aggregate = /^aggregate\((.+)\)$/.exec(field);
|
|
1023
|
+
if (aggregate?.[1]) {
|
|
1024
|
+
const base2 = insightsBase(aggregate[1]);
|
|
1025
|
+
return base2 ? { kind: "aggregate", base: base2 } : void 0;
|
|
1026
|
+
}
|
|
1027
|
+
const dot = field.indexOf(".");
|
|
1028
|
+
const object = dot > 0 ? OBJECT_PREFIXES.find((prefix) => prefix === field.slice(0, dot)) : void 0;
|
|
1029
|
+
const rest = object ? field.slice(dot + 1) : field;
|
|
1030
|
+
const restKind = object ? plainKind(rest) : void 0;
|
|
1031
|
+
if (object && restKind && restKind !== "special") return { kind: restKind, base: rest, object };
|
|
1032
|
+
const base = insightsBase(rest);
|
|
1033
|
+
return base ? { kind: "insights", base, ...object ? { object } : {} } : void 0;
|
|
1034
|
+
}
|
|
1035
|
+
var FORMULA_OPERATORS = /* @__PURE__ */ new Set(["+", "-", "*", "/"]);
|
|
1036
|
+
var MAX_FORMULA_FIELDS = 6;
|
|
1037
|
+
function formulaTerms(field) {
|
|
1038
|
+
const tokens = field.trim().split(/\s+/);
|
|
1039
|
+
const fields = [];
|
|
1040
|
+
for (const [index, token] of tokens.entries()) {
|
|
1041
|
+
const isOperator = FORMULA_OPERATORS.has(token);
|
|
1042
|
+
if (index % 2 === 1 ? !isOperator : isOperator) {
|
|
1043
|
+
return { refusal: `formula "${field}" must alternate terms and + - * / operators, each separated by a space` };
|
|
1044
|
+
}
|
|
1045
|
+
if (!isOperator && !/^-?\d+(\.\d+)?$/.test(token)) fields.push(token);
|
|
1046
|
+
}
|
|
1047
|
+
if (tokens.length % 2 === 0) return { refusal: `formula "${field}" ends with an operator` };
|
|
1048
|
+
if (fields.length > MAX_FORMULA_FIELDS) {
|
|
1049
|
+
return { refusal: `formula "${field}" uses ${fields.length} fields; Meta allows at most ${MAX_FORMULA_FIELDS}` };
|
|
1050
|
+
}
|
|
1051
|
+
return { fields };
|
|
1052
|
+
}
|
|
1053
|
+
var FIELD_HELP = "see the field list in `baker schema ads.meta.rules.create` (Insights metrics like cost_per, spent, results, cpm, ctr, frequency, website_purchase_roas; metadata like name, daily_budget, hours_since_creation), optionally prefixed by ad./adset./campaign., the account's attribution window (account_default:) or a time range (yesterday_)";
|
|
1054
|
+
function parseAdRuleField(field) {
|
|
1055
|
+
const retired = field.split(/[\s()]+/).map((term) => term.replace(/^(ad|adset|campaign)\./, "")).find(retiredAttributionPrefix);
|
|
1056
|
+
if (retired) return { refusal: `"${field}": ${RETIRED_ATTRIBUTION}` };
|
|
1057
|
+
if (!/\s/.test(field.trim())) {
|
|
1058
|
+
return singleField(field) ?? { refusal: `Meta does not recognise the filter field "${field}" \u2014 ${FIELD_HELP}` };
|
|
1059
|
+
}
|
|
1060
|
+
const terms = formulaTerms(field);
|
|
1061
|
+
if ("refusal" in terms) return terms;
|
|
1062
|
+
const unknown = terms.fields.find((term) => {
|
|
1063
|
+
const parsed = singleField(term);
|
|
1064
|
+
return !parsed || parsed.kind === "special";
|
|
1065
|
+
});
|
|
1066
|
+
if (unknown) {
|
|
1067
|
+
return { refusal: `formula "${field}": Meta does not recognise the field "${unknown}" \u2014 ${FIELD_HELP}` };
|
|
1068
|
+
}
|
|
1069
|
+
return formulaAmountRefusal(field, terms.fields) ?? { kind: "formula", base: field };
|
|
1070
|
+
}
|
|
1071
|
+
function formulaAmountRefusal(field, fields) {
|
|
1072
|
+
const amounts = fields.filter((term) => isAdRuleMoneyField(term));
|
|
1073
|
+
if (amounts.length === 0) return void 0;
|
|
1074
|
+
const ratioOfAmounts = fields.length === 2 && amounts.length === 2 && field.trim().split(/\s+/)[1] === "/";
|
|
1075
|
+
return ratioOfAmounts ? void 0 : {
|
|
1076
|
+
refusal: `formula "${field}" works out to an amount, which Meta compares in the account's minor units and Baker cannot convert inside a formula \u2014 use a plain cost field (cost_per, cpa, cpc) or a ratio of two amounts`
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
var COST_BASES = /* @__PURE__ */ new Set(["cpa", "cpc", "cpm", "cpp", "cost_per"]);
|
|
1080
|
+
function isCostField(field) {
|
|
1081
|
+
const parsed = parseAdRuleField(field);
|
|
1082
|
+
return "kind" in parsed && parsed.kind !== "formula" && (COST_BASES.has(parsed.base) || parsed.base.startsWith("cost_per"));
|
|
1083
|
+
}
|
|
1084
|
+
function isInsightsField(field) {
|
|
1085
|
+
const parsed = parseAdRuleField(field);
|
|
1086
|
+
return "kind" in parsed && parsed.kind !== "special" && parsed.kind !== "metadata";
|
|
1087
|
+
}
|
|
1088
|
+
var scalarSchema = z4.union([z4.number(), z4.string(), z4.boolean()]);
|
|
1089
|
+
var adRuleFilterSchema = z4.strictObject({
|
|
1090
|
+
field: z4.string().min(1),
|
|
1091
|
+
operator: z4.enum(AD_RULE_OPERATORS),
|
|
1092
|
+
value: z4.union([scalarSchema, z4.array(scalarSchema)])
|
|
1093
|
+
});
|
|
1094
|
+
var triggerSchema = z4.strictObject({
|
|
1095
|
+
type: z4.enum(AD_RULE_TRIGGER_TYPES),
|
|
1096
|
+
field: z4.string().min(1).optional(),
|
|
1097
|
+
operator: z4.enum(AD_RULE_OPERATORS).optional(),
|
|
1098
|
+
value: z4.union([scalarSchema, z4.array(scalarSchema)]).optional()
|
|
1099
|
+
});
|
|
1100
|
+
var adRuleEvaluationSpecSchema = z4.strictObject({
|
|
1101
|
+
evaluation_type: z4.enum(AD_RULE_EVALUATION_TYPES),
|
|
1102
|
+
filters: z4.array(adRuleFilterSchema).min(1),
|
|
1103
|
+
trigger: triggerSchema.optional()
|
|
1104
|
+
});
|
|
1105
|
+
var executionOptionSchema = z4.strictObject({
|
|
1106
|
+
field: z4.string().min(1),
|
|
1107
|
+
operator: z4.enum(["EQUAL", "IN"]).default("EQUAL"),
|
|
1108
|
+
value: z4.unknown()
|
|
1109
|
+
});
|
|
1110
|
+
var adRuleExecutionSpecSchema = z4.strictObject({
|
|
1111
|
+
execution_type: z4.enum(AD_RULE_EXECUTION_TYPES),
|
|
1112
|
+
execution_options: z4.array(executionOptionSchema).optional()
|
|
1113
|
+
});
|
|
1114
|
+
var scheduleItemSchema = z4.strictObject({
|
|
1115
|
+
start_minute: z4.number().int().optional(),
|
|
1116
|
+
end_minute: z4.number().int().optional(),
|
|
1117
|
+
days: z4.array(z4.number().int()).optional()
|
|
1118
|
+
});
|
|
1119
|
+
var adRuleScheduleSpecSchema = z4.strictObject({
|
|
1120
|
+
schedule_type: z4.enum(AD_RULE_SCHEDULE_TYPES),
|
|
1121
|
+
schedule: z4.array(scheduleItemSchema).optional()
|
|
1122
|
+
});
|
|
1123
|
+
var COMPARE = ["GREATER_THAN", "LESS_THAN", "EQUAL", "IN_RANGE", "NOT_IN_RANGE"];
|
|
1124
|
+
var BOUNDED = ["GREATER_THAN", "LESS_THAN", "IN_RANGE", "NOT_IN_RANGE"];
|
|
1125
|
+
var LISTED = ["IN", "NOT_IN"];
|
|
1126
|
+
var TAGGED = ["ANY", "ALL", "NONE"];
|
|
1127
|
+
var METADATA_OPERATORS = {
|
|
1128
|
+
id: ["EQUAL", "IN", "NOT_IN"],
|
|
1129
|
+
name: ["EQUAL", "CONTAIN", "NOT_CONTAIN"],
|
|
1130
|
+
adlabel_ids: TAGGED,
|
|
1131
|
+
objective: LISTED,
|
|
1132
|
+
buying_type: LISTED,
|
|
1133
|
+
billing_event: LISTED,
|
|
1134
|
+
optimization_goal: LISTED,
|
|
1135
|
+
is_autobid: LISTED,
|
|
1136
|
+
effective_status: LISTED,
|
|
1137
|
+
budget_reset_period: LISTED,
|
|
1138
|
+
"placement.page_types": TAGGED,
|
|
1139
|
+
stop_time: ["GREATER_THAN", "LESS_THAN"]
|
|
1140
|
+
};
|
|
1141
|
+
var SPECIAL_OPERATORS = {
|
|
1142
|
+
entity_type: ["EQUAL"],
|
|
1143
|
+
time_preset: ["EQUAL"],
|
|
1144
|
+
attribution_window: ["EQUAL"],
|
|
1145
|
+
aggregation_id: ["IN"]
|
|
1146
|
+
};
|
|
1147
|
+
function operatorsFor(parsed) {
|
|
1148
|
+
if (parsed.kind === "special") return SPECIAL_OPERATORS[parsed.base] ?? ["EQUAL"];
|
|
1149
|
+
if (parsed.kind === "metadata") return METADATA_OPERATORS[parsed.base] ?? BOUNDED;
|
|
1150
|
+
return COMPARE;
|
|
1151
|
+
}
|
|
1152
|
+
function isAscendingPair(value) {
|
|
1153
|
+
return Array.isArray(value) && value.length === 2 && value.every((bound) => typeof bound === "number") && value[0] < value[1];
|
|
1154
|
+
}
|
|
1155
|
+
var isNonEmptyList = (value) => Array.isArray(value) && value.length > 0;
|
|
1156
|
+
var isText = (value) => typeof value === "string" && value.length > 0;
|
|
1157
|
+
var VALUE_SHAPES = {
|
|
1158
|
+
IN_RANGE: { fits: isAscendingPair, words: "a [low, high] pair of numbers, low first" },
|
|
1159
|
+
NOT_IN_RANGE: { fits: isAscendingPair, words: "a [low, high] pair of numbers, low first" },
|
|
1160
|
+
IN: { fits: isNonEmptyList, words: "a non-empty list" },
|
|
1161
|
+
NOT_IN: { fits: isNonEmptyList, words: "a non-empty list" },
|
|
1162
|
+
ANY: { fits: isNonEmptyList, words: "a non-empty list" },
|
|
1163
|
+
ALL: { fits: isNonEmptyList, words: "a non-empty list" },
|
|
1164
|
+
NONE: { fits: isNonEmptyList, words: "a non-empty list" },
|
|
1165
|
+
CONTAIN: { fits: isText, words: "text" },
|
|
1166
|
+
NOT_CONTAIN: { fits: isText, words: "text" }
|
|
1167
|
+
};
|
|
1168
|
+
function valueShapeIssue(operator, value, textual) {
|
|
1169
|
+
const shape = VALUE_SHAPES[operator] ?? {
|
|
1170
|
+
fits: (candidate) => typeof candidate === "number" || textual && typeof candidate === "string",
|
|
1171
|
+
words: textual ? "text" : "a number"
|
|
1172
|
+
};
|
|
1173
|
+
return shape.fits(value) ? void 0 : `${operator} takes ${shape.words}`;
|
|
1174
|
+
}
|
|
1175
|
+
var TEXTUAL_FIELDS = /* @__PURE__ */ new Set(["name", "id", "entity_type", "time_preset", "attribution_window"]);
|
|
1176
|
+
var ENUM_VALUES = {
|
|
1177
|
+
entity_type: AD_RULE_ENTITY_TYPES,
|
|
1178
|
+
time_preset: AD_RULE_TIME_PRESETS,
|
|
1179
|
+
attribution_window: AD_RULE_ATTRIBUTION_WINDOWS
|
|
1180
|
+
};
|
|
1181
|
+
function enumIssue(parsed, value) {
|
|
1182
|
+
const allowed = parsed.kind === "special" ? ENUM_VALUES[parsed.base] : void 0;
|
|
1183
|
+
if (!allowed || typeof value === "string" && allowed.includes(value)) return void 0;
|
|
1184
|
+
return `${parsed.base} must be one of ${allowed.join(", ")}`;
|
|
1185
|
+
}
|
|
1186
|
+
function oneFilterIssue(filter) {
|
|
1187
|
+
const parsed = parseAdRuleField(filter.field);
|
|
1188
|
+
if ("refusal" in parsed) return parsed.refusal;
|
|
1189
|
+
const operators = operatorsFor(parsed);
|
|
1190
|
+
if (!operators.includes(filter.operator)) {
|
|
1191
|
+
return `${filter.field} takes ${operators.join(", ")} \u2014 not ${filter.operator}`;
|
|
1192
|
+
}
|
|
1193
|
+
return valueShapeIssue(filter.operator, filter.value, TEXTUAL_FIELDS.has(parsed.base)) ?? enumIssue(parsed, filter.value);
|
|
1194
|
+
}
|
|
1195
|
+
function filtersNamed(spec, field) {
|
|
1196
|
+
return spec.filters.filter((filter) => filter.field === field);
|
|
1197
|
+
}
|
|
1198
|
+
function scopeIssues(spec) {
|
|
1199
|
+
if (filtersNamed(spec, "entity_type").length > 0 || filtersNamed(spec, "id").length > 0) return [];
|
|
1200
|
+
return [
|
|
1201
|
+
{
|
|
1202
|
+
path: ["evaluation_spec", "filters"],
|
|
1203
|
+
message: "say what the rule applies to \u2014 add an entity_type filter (EQUAL AD, ADSET or CAMPAIGN) or an id filter (IN [ids])"
|
|
1204
|
+
}
|
|
1205
|
+
];
|
|
1206
|
+
}
|
|
1207
|
+
function onceOnly(spec, field) {
|
|
1208
|
+
return filtersNamed(spec, field).length > 1 ? [{ path: ["evaluation_spec", "filters"], message: `Meta takes one ${field} filter per rule` }] : [];
|
|
1209
|
+
}
|
|
1210
|
+
function measuresPerformance(spec) {
|
|
1211
|
+
return spec.filters.some((filter) => isInsightsField(filter.field)) || spec.trigger?.type.startsWith("STATS") === true;
|
|
1212
|
+
}
|
|
1213
|
+
function timePresetIssues(spec) {
|
|
1214
|
+
if (!measuresPerformance(spec) || filtersNamed(spec, "time_preset").length > 0) return [];
|
|
1215
|
+
return [
|
|
1216
|
+
{
|
|
1217
|
+
path: ["evaluation_spec", "filters"],
|
|
1218
|
+
message: "a performance condition needs a time range \u2014 add a time_preset filter (EQUAL, e.g. LAST_7D); Meta refuses the rule without one"
|
|
1219
|
+
}
|
|
1220
|
+
];
|
|
1221
|
+
}
|
|
1222
|
+
function aggregateIssues(spec) {
|
|
1223
|
+
const aggregates = spec.filters.some((filter) => filter.field.startsWith("aggregate("));
|
|
1224
|
+
if (!aggregates || filtersNamed(spec, "aggregation_id").length > 0) return [];
|
|
1225
|
+
return [
|
|
1226
|
+
{
|
|
1227
|
+
path: ["evaluation_spec", "filters"],
|
|
1228
|
+
message: "an aggregate(...) filter needs an aggregation_id filter (IN [ids]) naming what to add up"
|
|
1229
|
+
}
|
|
1230
|
+
];
|
|
1231
|
+
}
|
|
1232
|
+
var TEMP_REF = /^meta_temp_[A-Za-z0-9_-]{4,}$/;
|
|
1233
|
+
var NUMERIC_ID = /^\d+$/;
|
|
1234
|
+
var ENTITY_OF_PREFIX = {
|
|
1235
|
+
"ad.id": "ad",
|
|
1236
|
+
"adset.id": "adSet",
|
|
1237
|
+
"campaign.id": "campaign"
|
|
1238
|
+
};
|
|
1239
|
+
function idFilterEntity(field) {
|
|
1240
|
+
if (field === "id") return "level";
|
|
1241
|
+
if (/(^|\.)adlabel_ids$/.test(field)) return "adLabel";
|
|
1242
|
+
return ENTITY_OF_PREFIX[field];
|
|
1243
|
+
}
|
|
1244
|
+
function idValues(value) {
|
|
1245
|
+
return (Array.isArray(value) ? value : [value]).map(String);
|
|
1246
|
+
}
|
|
1247
|
+
function idFilterIssue(filter, level) {
|
|
1248
|
+
const named = idFilterEntity(filter.field);
|
|
1249
|
+
const ids = filter.field === "aggregation_id" || named ? idValues(filter.value) : [];
|
|
1250
|
+
const bad = ids.find((id) => !NUMERIC_ID.test(id) && !TEMP_REF.test(id));
|
|
1251
|
+
if (bad) return `${filter.field} takes bare numeric ids or meta_temp_* refs \u2014 not "${bad}"`;
|
|
1252
|
+
const temp = ids.some((id) => TEMP_REF.test(id));
|
|
1253
|
+
if (temp && filter.field === "aggregation_id")
|
|
1254
|
+
return "aggregation_id takes ids already on Meta, not meta_temp_* refs";
|
|
1255
|
+
if (temp && named === "level" && !level) {
|
|
1256
|
+
return "an id filter naming meta_temp_* refs needs an entity_type filter too, so the rule waits for the right objects";
|
|
1257
|
+
}
|
|
1258
|
+
return void 0;
|
|
1259
|
+
}
|
|
1260
|
+
function filterIssues(spec) {
|
|
1261
|
+
const issues = [];
|
|
1262
|
+
const level = adRuleLevel(spec);
|
|
1263
|
+
for (const [index, filter] of spec.filters.entries()) {
|
|
1264
|
+
const message = oneFilterIssue(filter) ?? idFilterIssue(filter, level);
|
|
1265
|
+
if (message) issues.push({ path: ["evaluation_spec", "filters", index], message });
|
|
1266
|
+
}
|
|
1267
|
+
return [
|
|
1268
|
+
...issues,
|
|
1269
|
+
...scopeIssues(spec),
|
|
1270
|
+
...onceOnly(spec, "time_preset"),
|
|
1271
|
+
...onceOnly(spec, "attribution_window"),
|
|
1272
|
+
...timePresetIssues(spec),
|
|
1273
|
+
...aggregateIssues(spec)
|
|
1274
|
+
];
|
|
1275
|
+
}
|
|
1276
|
+
var SCHEDULE_ONLY_FIELDS = /* @__PURE__ */ new Set([
|
|
1277
|
+
...SCHEDULE_ONLY_METADATA_FIELDS,
|
|
1278
|
+
...SCHEDULE_ONLY_INSIGHTS_FIELDS,
|
|
1279
|
+
"attribution_window"
|
|
1280
|
+
]);
|
|
1281
|
+
function scheduleOnlyFilter(filter) {
|
|
1282
|
+
const parsed = parseAdRuleField(filter.field);
|
|
1283
|
+
if ("refusal" in parsed) return false;
|
|
1284
|
+
if (parsed.kind === "advanced" || parsed.kind === "aggregate" || parsed.kind === "formula") return true;
|
|
1285
|
+
if (parsed.kind === "insights" && parsed.base !== filter.field) return true;
|
|
1286
|
+
return SCHEDULE_ONLY_FIELDS.has(parsed.base);
|
|
1287
|
+
}
|
|
1288
|
+
var STATS_CHANGE_OPERATORS = ["GREATER_THAN", "LESS_THAN", "IN_RANGE", "NOT_IN_RANGE"];
|
|
1289
|
+
var MILESTONE_MINIMUMS = {
|
|
1290
|
+
impressions: 1e3,
|
|
1291
|
+
unique_impressions: 1e3,
|
|
1292
|
+
reach: 1e3,
|
|
1293
|
+
clicks: 10,
|
|
1294
|
+
unique_clicks: 10,
|
|
1295
|
+
spent: 1e3,
|
|
1296
|
+
results: 5,
|
|
1297
|
+
...Object.fromEntries(
|
|
1298
|
+
[
|
|
1299
|
+
...APP_EVENTS.map((event) => `app_custom_event_fb_mobile_${event}`),
|
|
1300
|
+
"app_custom_event",
|
|
1301
|
+
"app_custom_event_other",
|
|
1302
|
+
"leadgen",
|
|
1303
|
+
"like",
|
|
1304
|
+
"link_click",
|
|
1305
|
+
"mobile_app_install",
|
|
1306
|
+
"offsite_conversion",
|
|
1307
|
+
"offsite_conversion_add_to_cart",
|
|
1308
|
+
"offsite_conversion_checkout",
|
|
1309
|
+
...PIXEL_EVENTS.map((event) => `offsite_conversion_fb_pixel_${event}`),
|
|
1310
|
+
"offsite_engagement",
|
|
1311
|
+
"post",
|
|
1312
|
+
"post_comment",
|
|
1313
|
+
"post_engagement",
|
|
1314
|
+
"post_like",
|
|
1315
|
+
"post_reaction",
|
|
1316
|
+
"view_content",
|
|
1317
|
+
"video_play",
|
|
1318
|
+
"video_view",
|
|
1319
|
+
"vote"
|
|
1320
|
+
].map((field) => [field, 1])
|
|
1321
|
+
)
|
|
1322
|
+
};
|
|
1323
|
+
function milestoneMinimumIssue(trigger) {
|
|
1324
|
+
if (trigger.type !== "STATS_MILESTONE" || !trigger.field) return void 0;
|
|
1325
|
+
const minimum = MILESTONE_MINIMUMS[trigger.field];
|
|
1326
|
+
return minimum !== void 0 && typeof trigger.value === "number" && trigger.value < minimum ? `a ${trigger.field} milestone steps by at least ${minimum}${trigger.field === "spent" ? " in the account's minor units (10.00 in a two-decimal currency)" : ""}` : void 0;
|
|
1327
|
+
}
|
|
1328
|
+
function milestoneIssue(trigger, spec) {
|
|
1329
|
+
if (trigger.operator !== "EQUAL") {
|
|
1330
|
+
return "a STATS_MILESTONE trigger takes operator EQUAL (it fires at every multiple of the value)";
|
|
1331
|
+
}
|
|
1332
|
+
const preset = filtersNamed(spec, "time_preset")[0]?.value;
|
|
1333
|
+
if (preset !== void 0 && preset !== "LIFETIME") return "a STATS_MILESTONE trigger needs time_preset LIFETIME";
|
|
1334
|
+
if (!trigger.field || MILESTONE_MINIMUMS[trigger.field] === void 0) {
|
|
1335
|
+
return `Meta counts milestones only of ${Object.keys(MILESTONE_MINIMUMS).slice(0, 7).join(", ")} and the conversion events \u2014 not ${trigger.field}; use a STATS_CHANGE trigger for it`;
|
|
1336
|
+
}
|
|
1337
|
+
if (!(typeof trigger.value === "number" && trigger.value > 0))
|
|
1338
|
+
return "a STATS_MILESTONE trigger takes a positive number";
|
|
1339
|
+
return trigger.field === "spent" ? void 0 : milestoneMinimumIssue(trigger);
|
|
1340
|
+
}
|
|
1341
|
+
function statsTriggerIssue(trigger, spec) {
|
|
1342
|
+
if (trigger.type === "STATS_MILESTONE") return milestoneIssue(trigger, spec);
|
|
1343
|
+
if (trigger.type === "STATS_CHANGE" && !(trigger.operator && STATS_CHANGE_OPERATORS.includes(trigger.operator))) {
|
|
1344
|
+
return `a STATS_CHANGE trigger takes ${STATS_CHANGE_OPERATORS.join(", ")}`;
|
|
1345
|
+
}
|
|
1346
|
+
return trigger.operator ? valueShapeIssue(trigger.operator, trigger.value, false) : "the trigger needs an operator";
|
|
1347
|
+
}
|
|
1348
|
+
function triggerShapeIssue(trigger, spec) {
|
|
1349
|
+
if (trigger.type === "METADATA_CREATION") return void 0;
|
|
1350
|
+
if (!trigger.field) return `a ${trigger.type} trigger needs a field`;
|
|
1351
|
+
if (trigger.type === "STATS_MILESTONE") return milestoneIssue(trigger, spec);
|
|
1352
|
+
const parsed = parseAdRuleField(trigger.field);
|
|
1353
|
+
if ("refusal" in parsed) return parsed.refusal;
|
|
1354
|
+
const isMetadata = parsed.kind === "metadata";
|
|
1355
|
+
if (trigger.type === "METADATA_UPDATE") {
|
|
1356
|
+
return isMetadata ? void 0 : `a METADATA_UPDATE trigger watches a metadata field, not ${trigger.field}`;
|
|
1357
|
+
}
|
|
1358
|
+
if (isMetadata || parsed.kind === "special") {
|
|
1359
|
+
return `a ${trigger.type} trigger watches a metric, not ${trigger.field}`;
|
|
1360
|
+
}
|
|
1361
|
+
return statsTriggerIssue(trigger, spec);
|
|
1362
|
+
}
|
|
1363
|
+
function triggerIssues(spec) {
|
|
1364
|
+
const at = (message, path = []) => ({
|
|
1365
|
+
path: ["evaluation_spec", ...path],
|
|
1366
|
+
message
|
|
1367
|
+
});
|
|
1368
|
+
if (spec.evaluation_type === "SCHEDULE") {
|
|
1369
|
+
return spec.trigger ? [at("a SCHEDULE rule has no trigger \u2014 use evaluation_type TRIGGER, or drop the trigger", ["trigger"])] : [];
|
|
1370
|
+
}
|
|
1371
|
+
const issues = [];
|
|
1372
|
+
if (!spec.trigger) {
|
|
1373
|
+
issues.push(
|
|
1374
|
+
at("a TRIGGER rule needs a trigger ({type, field, operator, value}) saying what change it reacts to", [
|
|
1375
|
+
"trigger"
|
|
1376
|
+
])
|
|
1377
|
+
);
|
|
1378
|
+
} else {
|
|
1379
|
+
const shape = triggerShapeIssue(spec.trigger, spec);
|
|
1380
|
+
if (shape) issues.push(at(shape, ["trigger"]));
|
|
1381
|
+
}
|
|
1382
|
+
for (const [index, filter] of spec.filters.entries()) {
|
|
1383
|
+
if (scheduleOnlyFilter(filter)) {
|
|
1384
|
+
issues.push(
|
|
1385
|
+
at(`${filter.field} works only on a schedule \u2014 use evaluation_type SCHEDULE, or drop it`, ["filters", index])
|
|
1386
|
+
);
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
const preset = filtersNamed(spec, "time_preset")[0]?.value;
|
|
1390
|
+
if (typeof preset === "string" && !TIME_PRESETS_WITH_TODAY.includes(preset)) {
|
|
1391
|
+
issues.push(
|
|
1392
|
+
at(
|
|
1393
|
+
`a TRIGGER rule evaluates in real time, so its time_preset must include today (${TIME_PRESETS_WITH_TODAY.join(", ")}) \u2014 not ${preset}`,
|
|
1394
|
+
["filters"]
|
|
1395
|
+
)
|
|
1396
|
+
);
|
|
1397
|
+
}
|
|
1398
|
+
return issues;
|
|
1399
|
+
}
|
|
1400
|
+
function evaluationIssues(spec) {
|
|
1401
|
+
return [...filterIssues(spec), ...triggerIssues(spec)];
|
|
1402
|
+
}
|
|
1403
|
+
function pairingIssues(rule, partial) {
|
|
1404
|
+
const evaluationType = rule.evaluation_spec?.evaluation_type;
|
|
1405
|
+
if (!evaluationType) return [];
|
|
1406
|
+
const issues = [];
|
|
1407
|
+
if (evaluationType === "TRIGGER") {
|
|
1408
|
+
const executionType = rule.execution_spec?.execution_type;
|
|
1409
|
+
if (executionType && !TRIGGER_EXECUTION_TYPES.has(executionType)) {
|
|
1410
|
+
issues.push({
|
|
1411
|
+
path: ["execution_spec", "execution_type"],
|
|
1412
|
+
message: `a TRIGGER rule can only PAUSE, UNPAUSE or NOTIFICATION \u2014 ${executionType} needs evaluation_type SCHEDULE`
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
if (rule.schedule_spec) {
|
|
1416
|
+
issues.push({
|
|
1417
|
+
path: ["schedule_spec"],
|
|
1418
|
+
message: "a TRIGGER rule runs the moment the metric changes, so it takes no schedule_spec \u2014 drop it"
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
} else if (!rule.schedule_spec && !partial) {
|
|
1422
|
+
issues.push({
|
|
1423
|
+
path: ["schedule_spec"],
|
|
1424
|
+
message: "a SCHEDULE rule needs a schedule_spec (schedule_type DAILY, HOURLY, SEMI_HOURLY or CUSTOM)"
|
|
1425
|
+
});
|
|
1426
|
+
}
|
|
1427
|
+
return issues;
|
|
1428
|
+
}
|
|
1429
|
+
var AD_RULE_CHANGE_UNITS = ["ACCOUNT_CURRENCY", "PERCENTAGE"];
|
|
1430
|
+
var AD_RULE_REBALANCE_TYPES = [
|
|
1431
|
+
"EVEN",
|
|
1432
|
+
"PROPORTIONAL",
|
|
1433
|
+
"NO_PAUSE_PROPORTIONAL",
|
|
1434
|
+
"MATCHED_ONLY_PROPORTIONAL"
|
|
1435
|
+
];
|
|
1436
|
+
var CHANGE_TYPES = /* @__PURE__ */ new Set(["CHANGE_BUDGET", "CHANGE_CAMPAIGN_BUDGET", "CHANGE_BID"]);
|
|
1437
|
+
var ACTION_LEVEL = {
|
|
1438
|
+
CHANGE_BUDGET: { level: "ADSET", words: "ad sets (CHANGE_CAMPAIGN_BUDGET changes a campaign's)" },
|
|
1439
|
+
CHANGE_BID: { level: "ADSET", words: "ad sets" },
|
|
1440
|
+
REBALANCE_BUDGET: { level: "ADSET", words: "ad sets" },
|
|
1441
|
+
CHANGE_CAMPAIGN_BUDGET: { level: "CAMPAIGN", words: "campaigns (CHANGE_BUDGET changes an ad set's)" },
|
|
1442
|
+
ROTATE: { level: "AD", words: "ads, rotating within the ad sets an adset.id filter names" }
|
|
1443
|
+
};
|
|
1444
|
+
function optionAllowed(field, executionType) {
|
|
1445
|
+
switch (field) {
|
|
1446
|
+
case "user_ids":
|
|
1447
|
+
case "alert_preferences":
|
|
1448
|
+
return true;
|
|
1449
|
+
case "change_spec":
|
|
1450
|
+
case "execution_count_limit":
|
|
1451
|
+
case "action_frequency":
|
|
1452
|
+
return CHANGE_TYPES.has(executionType);
|
|
1453
|
+
case "rebalance_spec":
|
|
1454
|
+
return executionType === "REBALANCE_BUDGET";
|
|
1455
|
+
default:
|
|
1456
|
+
return false;
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
function isPositiveInt(value) {
|
|
1460
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
|
1461
|
+
}
|
|
1462
|
+
function isRecord2(value) {
|
|
1463
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1464
|
+
}
|
|
1465
|
+
function isNumberPair(value) {
|
|
1466
|
+
return Array.isArray(value) && value.length === 2 && value.every((bound) => typeof bound === "number") && value[0] <= value[1];
|
|
1467
|
+
}
|
|
1468
|
+
function changeSpecIssue(value) {
|
|
1469
|
+
if (!isRecord2(value)) return "change_spec takes {amount, unit, limit?, target_field?}";
|
|
1470
|
+
if (typeof value.amount !== "number" || value.amount === 0) return "change_spec.amount takes a non-zero number";
|
|
1471
|
+
const target = value.target_field;
|
|
1472
|
+
if (target !== void 0) {
|
|
1473
|
+
if (typeof target !== "string" || !isInsightsField(target)) {
|
|
1474
|
+
return "change_spec.target_field takes an Insights metric (e.g. cost_per, website_purchase_roas)";
|
|
1475
|
+
}
|
|
1476
|
+
return value.limit === void 0 || isNumberPair(value.limit) ? void 0 : "with target_field, change_spec.limit takes a [lowest, highest] pair";
|
|
1477
|
+
}
|
|
1478
|
+
if (!AD_RULE_CHANGE_UNITS.includes(value.unit)) {
|
|
1479
|
+
return `change_spec.unit takes ${AD_RULE_CHANGE_UNITS.join(" or ")} (required unless target_field is set)`;
|
|
1480
|
+
}
|
|
1481
|
+
return value.limit === void 0 || typeof value.limit === "number" && value.limit > 0 ? void 0 : "change_spec.limit takes a positive amount \u2014 the most (or least) the budget or bid may reach";
|
|
1482
|
+
}
|
|
1483
|
+
function rebalanceSpecIssue(value) {
|
|
1484
|
+
if (!isRecord2(value) || !AD_RULE_REBALANCE_TYPES.includes(value.type)) {
|
|
1485
|
+
return `rebalance_spec.type takes ${AD_RULE_REBALANCE_TYPES.join(", ")}`;
|
|
1486
|
+
}
|
|
1487
|
+
const needsTarget = value.type !== "EVEN" || value.target_count !== void 0;
|
|
1488
|
+
if (needsTarget && (typeof value.target_field !== "string" || !isInsightsField(value.target_field))) {
|
|
1489
|
+
return "rebalance_spec needs a target_field (an Insights metric to rank by) unless type is EVEN with no target_count";
|
|
1490
|
+
}
|
|
1491
|
+
if (value.target_count !== void 0 && !isPositiveInt(value.target_count)) {
|
|
1492
|
+
return "rebalance_spec.target_count takes a positive whole number";
|
|
1493
|
+
}
|
|
1494
|
+
if (typeof value.target_field === "string" && isCostField(value.target_field) && value.is_inverse !== true) {
|
|
1495
|
+
return `rebalance_spec ranks by ${value.target_field}, a cost, so Meta would give the most budget to the most expensive ad sets \u2014 add is_inverse: true to favour the cheapest`;
|
|
1496
|
+
}
|
|
1497
|
+
return void 0;
|
|
1498
|
+
}
|
|
1499
|
+
function optionValueIssue(option) {
|
|
1500
|
+
switch (option.field) {
|
|
1501
|
+
case "change_spec":
|
|
1502
|
+
return changeSpecIssue(option.value);
|
|
1503
|
+
case "rebalance_spec":
|
|
1504
|
+
return rebalanceSpecIssue(option.value);
|
|
1505
|
+
case "execution_count_limit":
|
|
1506
|
+
case "action_frequency":
|
|
1507
|
+
return isPositiveInt(option.value) ? void 0 : `${option.field} takes a positive whole number`;
|
|
1508
|
+
case "user_ids":
|
|
1509
|
+
return Array.isArray(option.value) && option.value.length > 0 ? void 0 : "user_ids takes a list of Facebook user ids to notify";
|
|
1510
|
+
default:
|
|
1511
|
+
return void 0;
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
var REQUIRED_OPTION = {
|
|
1515
|
+
CHANGE_BUDGET: "change_spec",
|
|
1516
|
+
CHANGE_CAMPAIGN_BUDGET: "change_spec",
|
|
1517
|
+
CHANGE_BID: "change_spec",
|
|
1518
|
+
REBALANCE_BUDGET: "rebalance_spec"
|
|
1519
|
+
};
|
|
1520
|
+
function executionIssues(spec) {
|
|
1521
|
+
const issues = [];
|
|
1522
|
+
const options = spec.execution_options ?? [];
|
|
1523
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1524
|
+
for (const [index, option] of options.entries()) {
|
|
1525
|
+
const path = ["execution_spec", "execution_options", index];
|
|
1526
|
+
if (seen.has(option.field)) issues.push({ path, message: `${option.field} is given twice` });
|
|
1527
|
+
seen.add(option.field);
|
|
1528
|
+
if (!optionAllowed(option.field, spec.execution_type)) {
|
|
1529
|
+
issues.push({ path, message: `${spec.execution_type} does not take the ${option.field} option` });
|
|
1530
|
+
continue;
|
|
1531
|
+
}
|
|
1532
|
+
const message = optionValueIssue(option);
|
|
1533
|
+
if (message) issues.push({ path, message });
|
|
1534
|
+
}
|
|
1535
|
+
const required = REQUIRED_OPTION[spec.execution_type];
|
|
1536
|
+
if (required && !seen.has(required)) {
|
|
1537
|
+
issues.push({
|
|
1538
|
+
path: ["execution_spec", "execution_options"],
|
|
1539
|
+
message: `${spec.execution_type} needs a ${required} option saying how much to change`
|
|
1540
|
+
});
|
|
1541
|
+
}
|
|
1542
|
+
return issues;
|
|
1543
|
+
}
|
|
1544
|
+
function adRuleLevel(spec) {
|
|
1545
|
+
const value = spec ? filtersNamed(spec, "entity_type")[0]?.value : void 0;
|
|
1546
|
+
return AD_RULE_ENTITY_TYPES.includes(value) ? value : void 0;
|
|
1547
|
+
}
|
|
1548
|
+
function rotateIssues(rule) {
|
|
1549
|
+
if (rule.execution_spec?.execution_type !== "ROTATE" || !rule.evaluation_spec) return [];
|
|
1550
|
+
return filtersNamed(rule.evaluation_spec, "id").length > 0 ? [
|
|
1551
|
+
{
|
|
1552
|
+
path: ["evaluation_spec", "filters"],
|
|
1553
|
+
message: "a ROTATE rule names the ad sets whose ads it rotates with an adset.id filter (entity_type AD) \u2014 Meta refuses a bare id filter of ad sets"
|
|
1554
|
+
}
|
|
1555
|
+
] : [];
|
|
1556
|
+
}
|
|
1557
|
+
function levelIssues(rule) {
|
|
1558
|
+
const executionType = rule.execution_spec?.execution_type;
|
|
1559
|
+
const wanted = executionType ? ACTION_LEVEL[executionType] : void 0;
|
|
1560
|
+
const level = adRuleLevel(rule.evaluation_spec);
|
|
1561
|
+
if (!wanted || !level || level === wanted.level) return [];
|
|
1562
|
+
return [
|
|
1563
|
+
{
|
|
1564
|
+
path: ["execution_spec", "execution_type"],
|
|
1565
|
+
message: `${executionType} acts on ${wanted.words} \u2014 set entity_type ${wanted.level}, not ${level}`
|
|
1566
|
+
}
|
|
1567
|
+
];
|
|
1568
|
+
}
|
|
1569
|
+
var MINUTES_PER_DAY = 24 * 60;
|
|
1570
|
+
function minuteIssue(name, value) {
|
|
1571
|
+
if (value === void 0) return void 0;
|
|
1572
|
+
return value % 30 === 0 && value >= 0 && value <= MINUTES_PER_DAY ? void 0 : `${name} is minutes after midnight, a multiple of 30 between 0 and ${MINUTES_PER_DAY}`;
|
|
1573
|
+
}
|
|
1574
|
+
function scheduleItemIssue(item) {
|
|
1575
|
+
if (item.days === void 0 && item.start_minute === void 0 && item.end_minute === void 0) {
|
|
1576
|
+
return "a custom schedule entry names its days (0\u20136) and/or start_minute/end_minute \u2014 an empty one says nothing about when to run";
|
|
1577
|
+
}
|
|
1578
|
+
const minute = minuteIssue("start_minute", item.start_minute) ?? minuteIssue("end_minute", item.end_minute);
|
|
1579
|
+
if (minute) return minute;
|
|
1580
|
+
if (item.start_minute !== void 0 && item.end_minute !== void 0 && item.end_minute < item.start_minute) {
|
|
1581
|
+
return "end_minute must be at or after start_minute";
|
|
1582
|
+
}
|
|
1583
|
+
const days = item.days ?? [];
|
|
1584
|
+
if (days.some((day) => day < 0 || day > 6) || new Set(days).size !== days.length) {
|
|
1585
|
+
return "days are distinct numbers from 0 (Sunday) to 6 (Saturday)";
|
|
1586
|
+
}
|
|
1587
|
+
return void 0;
|
|
1588
|
+
}
|
|
1589
|
+
function scheduleIssues(spec, executionType) {
|
|
1590
|
+
const issues = [];
|
|
1591
|
+
const items = spec.schedule ?? [];
|
|
1592
|
+
if (spec.schedule_type === "CUSTOM" && items.length === 0) {
|
|
1593
|
+
issues.push({
|
|
1594
|
+
path: ["schedule_spec", "schedule"],
|
|
1595
|
+
message: "a CUSTOM schedule needs at least one {start_minute, end_minute, days} entry"
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1598
|
+
if (spec.schedule_type !== "CUSTOM" && items.length > 0) {
|
|
1599
|
+
issues.push({
|
|
1600
|
+
path: ["schedule_spec", "schedule"],
|
|
1601
|
+
message: `schedule entries need schedule_type CUSTOM, not ${spec.schedule_type}`
|
|
1602
|
+
});
|
|
1603
|
+
}
|
|
1604
|
+
for (const [index, item] of items.entries()) {
|
|
1605
|
+
const message = scheduleItemIssue(item);
|
|
1606
|
+
if (message) issues.push({ path: ["schedule_spec", "schedule", index], message });
|
|
1607
|
+
}
|
|
1608
|
+
if (executionType === "REBALANCE_BUDGET" && spec.schedule_type !== "DAILY" && spec.schedule_type !== "CUSTOM") {
|
|
1609
|
+
issues.push({
|
|
1610
|
+
path: ["schedule_spec", "schedule_type"],
|
|
1611
|
+
message: "a REBALANCE_BUDGET rule runs DAILY or on a CUSTOM schedule"
|
|
1612
|
+
});
|
|
1613
|
+
}
|
|
1614
|
+
return issues;
|
|
1615
|
+
}
|
|
1616
|
+
function adRuleIssues(rule, { partial = false } = {}) {
|
|
1617
|
+
return [
|
|
1618
|
+
...rule.evaluation_spec ? evaluationIssues(rule.evaluation_spec) : [],
|
|
1619
|
+
...rule.execution_spec ? executionIssues(rule.execution_spec) : [],
|
|
1620
|
+
...rule.schedule_spec ? scheduleIssues(rule.schedule_spec, rule.execution_spec?.execution_type) : [],
|
|
1621
|
+
...pairingIssues(rule, partial),
|
|
1622
|
+
...levelIssues(rule),
|
|
1623
|
+
...rotateIssues(rule)
|
|
1624
|
+
];
|
|
1625
|
+
}
|
|
1626
|
+
function addIssues(rule, ctx, partial = false) {
|
|
1627
|
+
for (const issue of adRuleIssues(rule, { partial })) {
|
|
1628
|
+
ctx.addIssue({ code: "custom", path: issue.path, message: issue.message });
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
var currencyCodeSchema = z4.string().regex(/^[A-Z]{3}$/).optional();
|
|
1632
|
+
var adRuleCreateSchema = z4.strictObject({
|
|
1633
|
+
name: z4.string().min(1).max(255),
|
|
1634
|
+
status: z4.enum(AD_RULE_CREATE_STATUSES).default("ENABLED"),
|
|
1635
|
+
evaluation_spec: adRuleEvaluationSpecSchema,
|
|
1636
|
+
execution_spec: adRuleExecutionSpecSchema,
|
|
1637
|
+
schedule_spec: adRuleScheduleSpecSchema.optional(),
|
|
1638
|
+
/** The account currency the money values are in — filled by the backend. */
|
|
1639
|
+
currencyCode: currencyCodeSchema
|
|
1640
|
+
}).superRefine((rule, ctx) => addIssues(rule, ctx));
|
|
1641
|
+
var MONEY_METADATA = /* @__PURE__ */ new Set(["daily_budget", "lifetime_budget", "spend_cap", "bid_amount"]);
|
|
1642
|
+
var MONEY_INSIGHTS = /* @__PURE__ */ new Set([
|
|
1643
|
+
"spent",
|
|
1644
|
+
"today_spent",
|
|
1645
|
+
"yesterday_spent",
|
|
1646
|
+
"lifetime_spent",
|
|
1647
|
+
"cpc",
|
|
1648
|
+
"cpm",
|
|
1649
|
+
"cpa",
|
|
1650
|
+
"cpp"
|
|
1651
|
+
]);
|
|
1652
|
+
function unitsField(field) {
|
|
1653
|
+
return field.split(/(\s+|[()])/).map((term) => {
|
|
1654
|
+
const object = /^(ad|adset|campaign)\./.exec(term)?.[0] ?? "";
|
|
1655
|
+
const bare = term.slice(object.length);
|
|
1656
|
+
return retiredAttributionPrefix(bare) ? `${object}${LIVE_ATTRIBUTION_PREFIX}${bare.slice(bare.indexOf(":"))}` : term;
|
|
1657
|
+
}).join("");
|
|
1658
|
+
}
|
|
1659
|
+
function isAdRuleMoneyField(field) {
|
|
1660
|
+
const parsed = parseAdRuleField(unitsField(field));
|
|
1661
|
+
if ("refusal" in parsed || parsed.kind === "formula" || parsed.kind === "special") return false;
|
|
1662
|
+
if (parsed.kind === "metadata") return MONEY_METADATA.has(parsed.base);
|
|
1663
|
+
return MONEY_INSIGHTS.has(parsed.base) || parsed.base.startsWith("cost_per");
|
|
1664
|
+
}
|
|
1665
|
+
var adRuleUpdateSchema = z4.strictObject({
|
|
1666
|
+
name: z4.string().min(1).max(255).optional(),
|
|
1667
|
+
status: z4.enum(AD_RULE_UPDATE_STATUSES).optional(),
|
|
1668
|
+
/** Each spec is replaced whole — Meta takes no partial spec on update. */
|
|
1669
|
+
evaluation_spec: adRuleEvaluationSpecSchema.optional(),
|
|
1670
|
+
execution_spec: adRuleExecutionSpecSchema.optional(),
|
|
1671
|
+
schedule_spec: adRuleScheduleSpecSchema.optional(),
|
|
1672
|
+
/**
|
|
1673
|
+
* Run the rule once, now (`POST /{rule}/execute`), after any change staged
|
|
1674
|
+
* beside it — the rule acts on what matches it at that moment, whatever its
|
|
1675
|
+
* schedule and even while it is off. Meta ignores `validate_only` there (a
|
|
1676
|
+
* validate-only execute ran the rule, its history shows it; live on v26.0,
|
|
1677
|
+
* 2026-10-15), so it is never dry-run.
|
|
1678
|
+
*/
|
|
1679
|
+
run_now: z4.literal(true).optional(),
|
|
1680
|
+
currencyCode: currencyCodeSchema
|
|
1681
|
+
}).refine(
|
|
1682
|
+
(p) => Object.entries(p).some(([key, value]) => key !== "currencyCode" && value !== void 0),
|
|
1683
|
+
"update needs at least one field"
|
|
1684
|
+
).refine((p) => !(p.run_now && p.status === "DELETED"), "a rule being deleted cannot also run now \u2014 pick one").superRefine((rule, ctx) => addIssues(rule, ctx, true));
|
|
1685
|
+
var metaAdRuleHistoryQuerySchema = z4.strictObject({
|
|
1686
|
+
accountId: z4.string().min(1),
|
|
1687
|
+
ruleId: z4.string().regex(/^\d+$/).optional(),
|
|
1688
|
+
objectId: z4.string().regex(/^\d+$/).optional(),
|
|
1689
|
+
/** Meta's action names, e.g. PAUSED, UNPAUSED, CHANGED_BUDGET, CHANGED_BID, NOTIFICATION. */
|
|
1690
|
+
action: z4.string().min(1).optional(),
|
|
1691
|
+
hideNoChanges: z4.boolean().optional(),
|
|
1692
|
+
limit: z4.number().int().min(1).max(500).optional()
|
|
1693
|
+
});
|
|
1694
|
+
|
|
1695
|
+
// ../api/src/ads-meta/adSetCompliance.ts
|
|
1696
|
+
import { z as z5 } from "zod";
|
|
1697
|
+
var placementSoftOptOutSchema = z5.strictObject({
|
|
1698
|
+
facebook_positions: z5.array(z5.enum(FACEBOOK_POSITIONS)).min(1).optional(),
|
|
1699
|
+
instagram_positions: z5.array(z5.enum(INSTAGRAM_POSITIONS)).min(1).optional(),
|
|
1700
|
+
audience_network_positions: z5.array(z5.enum(AUDIENCE_NETWORK_POSITIONS)).min(1).optional(),
|
|
1701
|
+
messenger_positions: z5.array(z5.enum(MESSENGER_POSITIONS)).min(1).optional(),
|
|
1702
|
+
threads_positions: z5.array(z5.enum(THREADS_POSITIONS)).min(1).optional()
|
|
1703
|
+
}).refine(
|
|
1704
|
+
(spec) => Object.values(spec).some((positions) => positions !== void 0),
|
|
1705
|
+
"name at least one position Meta may spend on (e.g. audience_network_positions: [classic])"
|
|
1706
|
+
);
|
|
1707
|
+
var REGIONAL_REGULATED_CATEGORIES = [
|
|
1708
|
+
"TAIWAN_FINSERV",
|
|
1709
|
+
"AUSTRALIA_FINSERV",
|
|
1710
|
+
"INDIA_FINSERV",
|
|
1711
|
+
"TAIWAN_UNIVERSAL",
|
|
1712
|
+
"SINGAPORE_UNIVERSAL",
|
|
1713
|
+
"THAILAND_UNIVERSAL",
|
|
1714
|
+
"BRAZIL_REGULATION"
|
|
1715
|
+
];
|
|
1716
|
+
var IDENTITY_PREFIX = {
|
|
1717
|
+
TAIWAN_FINSERV: "taiwan_finserv",
|
|
1718
|
+
AUSTRALIA_FINSERV: "australia_finserv",
|
|
1719
|
+
INDIA_FINSERV: "india_finserv",
|
|
1720
|
+
TAIWAN_UNIVERSAL: "taiwan_universal",
|
|
1721
|
+
SINGAPORE_UNIVERSAL: "singapore_universal",
|
|
1722
|
+
THAILAND_UNIVERSAL: "universal",
|
|
1723
|
+
BRAZIL_REGULATION: "universal"
|
|
1724
|
+
};
|
|
1725
|
+
var identityId = z5.string().regex(/^\d+$/, "a verified advertiser id (digits)");
|
|
1726
|
+
var IDENTITY_KEYS = [...new Set(Object.values(IDENTITY_PREFIX))].flatMap((prefix) => [
|
|
1727
|
+
`${prefix}_beneficiary`,
|
|
1728
|
+
`${prefix}_payer`
|
|
1729
|
+
]);
|
|
1730
|
+
var regionalRegulationIdentitiesSchema = z5.strictObject(
|
|
1731
|
+
Object.fromEntries(IDENTITY_KEYS.map((key) => [key, identityId.optional()]))
|
|
1732
|
+
);
|
|
1733
|
+
var regionalRegulationFields = {
|
|
1734
|
+
regional_regulated_categories: z5.array(z5.enum(REGIONAL_REGULATED_CATEGORIES)).min(1).optional(),
|
|
1735
|
+
regional_regulation_identities: regionalRegulationIdentitiesSchema.optional()
|
|
1736
|
+
};
|
|
1737
|
+
function validateRegionalRegulation(p, ctx) {
|
|
1738
|
+
const categories = p.regional_regulated_categories ?? [];
|
|
1739
|
+
const identities = p.regional_regulation_identities ?? {};
|
|
1740
|
+
for (const category of categories) {
|
|
1741
|
+
const prefix = IDENTITY_PREFIX[category];
|
|
1742
|
+
const missing = [`${prefix}_beneficiary`, `${prefix}_payer`].filter((key) => !identities[key]);
|
|
1743
|
+
if (missing.length > 0) {
|
|
1744
|
+
ctx.addIssue({
|
|
1745
|
+
code: "custom",
|
|
1746
|
+
path: ["regional_regulation_identities"],
|
|
1747
|
+
message: `${category} needs ${missing.join(" and ")} in regional_regulation_identities \u2014 the verified advertiser ids from Meta's advertiser verification (Meta refuses it without them, 3858548)`
|
|
1748
|
+
});
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
if (categories.includes("TAIWAN_FINSERV") && !categories.includes("TAIWAN_UNIVERSAL")) {
|
|
1752
|
+
ctx.addIssue({
|
|
1753
|
+
code: "custom",
|
|
1754
|
+
path: ["regional_regulated_categories"],
|
|
1755
|
+
message: "a Taiwan financial-services ad set declares TAIWAN_UNIVERSAL beside TAIWAN_FINSERV"
|
|
1756
|
+
});
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
var SOFT_PLATFORM = {
|
|
1760
|
+
facebook_positions: "facebook",
|
|
1761
|
+
instagram_positions: "instagram",
|
|
1762
|
+
audience_network_positions: "audience_network",
|
|
1763
|
+
messenger_positions: "messenger",
|
|
1764
|
+
threads_positions: "threads"
|
|
1765
|
+
};
|
|
1766
|
+
function softOptOutPlacementIssue(soft, targeting, isCreate) {
|
|
1767
|
+
if (!soft || !isCreate && targeting === void 0) return void 0;
|
|
1768
|
+
const platforms = Array.isArray(targeting?.publisher_platforms) ? targeting.publisher_platforms : [];
|
|
1769
|
+
if (platforms.length === 0) {
|
|
1770
|
+
return "limited spend reaches placements the ad set leaves out, so it needs manual placements (targeting.publisher_platforms and positions) \u2014 with automatic placements Meta drops it";
|
|
1771
|
+
}
|
|
1772
|
+
for (const [key, positions] of Object.entries(soft)) {
|
|
1773
|
+
const platform = SOFT_PLATFORM[key];
|
|
1774
|
+
const running = targeting?.[key];
|
|
1775
|
+
if (!platform || !positions || !platforms.includes(platform)) continue;
|
|
1776
|
+
const runs = Array.isArray(running) ? running : void 0;
|
|
1777
|
+
const overlap = positions.filter((position) => runs === void 0 || runs.includes(position));
|
|
1778
|
+
if (overlap.length > 0) {
|
|
1779
|
+
return `the ad set already runs on ${overlap.map((position) => `${platform} ${position}`).join(", ")} \u2014 limited spend names only positions it leaves out`;
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
return void 0;
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
// ../api/src/ads-meta/copies.ts
|
|
1786
|
+
import { z as z6 } from "zod";
|
|
1787
|
+
var COPY_STATUS_OPTIONS = ["PAUSED", "ACTIVE", "INHERITED_FROM_SOURCE"];
|
|
1788
|
+
var COPY_RENAME_STRATEGIES = ["DEEP_RENAME", "ONLY_TOP_LEVEL_RENAME", "NO_RENAME"];
|
|
1789
|
+
var DEEP_COPY_MAX_ADS = 51;
|
|
1790
|
+
var sourceIdSchema = z6.string().regex(
|
|
1791
|
+
NUMERIC_ID_REGEX,
|
|
1792
|
+
"the source must be a published Meta id \u2014 to repeat something staged in this chat, stage it again"
|
|
1793
|
+
);
|
|
1794
|
+
var destinationSchema = z6.union([
|
|
1795
|
+
z6.string().regex(NUMERIC_ID_REGEX, "expected a numeric id"),
|
|
1796
|
+
z6.string().regex(TEMP_REF_REGEX, "expected a meta_temp_* reference")
|
|
1797
|
+
]);
|
|
1798
|
+
var copiedTreeSchema = z6.strictObject({
|
|
1799
|
+
adSets: z6.number().int().nonnegative().optional(),
|
|
1800
|
+
ads: z6.number().int().nonnegative()
|
|
1801
|
+
});
|
|
1802
|
+
var naming = {
|
|
1803
|
+
status_option: z6.enum(COPY_STATUS_OPTIONS).optional(),
|
|
1804
|
+
rename_strategy: z6.enum(COPY_RENAME_STRATEGIES).optional(),
|
|
1805
|
+
rename_prefix: z6.string().min(1).max(100).optional(),
|
|
1806
|
+
rename_suffix: z6.string().min(1).max(100).optional()
|
|
1807
|
+
};
|
|
1808
|
+
var schedule = {
|
|
1809
|
+
start_time: z6.number().int().positive().optional(),
|
|
1810
|
+
end_time: z6.number().int().positive().optional()
|
|
1811
|
+
};
|
|
1812
|
+
function copyIssues(p, ctx) {
|
|
1813
|
+
if (p.rename_strategy === "NO_RENAME" && (p.rename_prefix || p.rename_suffix)) {
|
|
1814
|
+
ctx.addIssue({
|
|
1815
|
+
code: "custom",
|
|
1816
|
+
path: ["rename_strategy"],
|
|
1817
|
+
message: "NO_RENAME keeps every name as it is, so a prefix or suffix would be ignored \u2014 drop one of them"
|
|
1818
|
+
});
|
|
1819
|
+
}
|
|
1820
|
+
if (p.start_time !== void 0 && p.end_time !== void 0 && p.end_time <= p.start_time) {
|
|
1821
|
+
ctx.addIssue({ code: "custom", path: ["end_time"], message: "end_time must be after start_time" });
|
|
1822
|
+
}
|
|
1823
|
+
if (p.deep_copy && p.sourceTree && p.sourceTree.ads > DEEP_COPY_MAX_ADS) {
|
|
1824
|
+
ctx.addIssue({
|
|
1825
|
+
code: "custom",
|
|
1826
|
+
path: ["deep_copy"],
|
|
1827
|
+
message: `Meta copies at most ${DEEP_COPY_MAX_ADS} ads in one go and this holds ${p.sourceTree.ads} \u2014 copy its ad sets one by one (\`adsets copy <id> --deep --to-campaign <copy>\`)`
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
var campaignCopySchema = z6.strictObject({
|
|
1832
|
+
source_id: sourceIdSchema,
|
|
1833
|
+
/** Copy the ad sets and ads too. Without it Meta copies the campaign alone. */
|
|
1834
|
+
deep_copy: z6.boolean().optional(),
|
|
1835
|
+
...naming,
|
|
1836
|
+
/** For a deep copy only: when the copied ad sets start and end (else they keep the originals'). */
|
|
1837
|
+
...schedule,
|
|
1838
|
+
sourceTree: copiedTreeSchema.optional()
|
|
1839
|
+
}).superRefine((p, ctx) => {
|
|
1840
|
+
if (!p.deep_copy && (p.start_time !== void 0 || p.end_time !== void 0)) {
|
|
1841
|
+
ctx.addIssue({
|
|
1842
|
+
code: "custom",
|
|
1843
|
+
path: ["start_time"],
|
|
1844
|
+
message: "start and end apply to the copied ad sets, so they need a deep copy (--deep)"
|
|
1845
|
+
});
|
|
1846
|
+
}
|
|
1847
|
+
copyIssues(p, ctx);
|
|
1848
|
+
});
|
|
1849
|
+
var adSetCopySchema = z6.strictObject({
|
|
1850
|
+
source_id: sourceIdSchema,
|
|
1851
|
+
/** The campaign the copy goes into; omitted, it stays beside the original. */
|
|
1852
|
+
campaign_id: destinationSchema.optional(),
|
|
1853
|
+
/** Copy the ads too. */
|
|
1854
|
+
deep_copy: z6.boolean().optional(),
|
|
1855
|
+
...naming,
|
|
1856
|
+
...schedule,
|
|
1857
|
+
sourceTree: copiedTreeSchema.optional()
|
|
1858
|
+
}).superRefine(copyIssues);
|
|
1859
|
+
var adCopySchema = z6.strictObject({
|
|
1860
|
+
source_id: sourceIdSchema,
|
|
1861
|
+
/** The ad set the copy goes into; omitted, it stays beside the original. */
|
|
1862
|
+
adset_id: destinationSchema.optional(),
|
|
1863
|
+
...naming
|
|
1864
|
+
}).superRefine(copyIssues);
|
|
1865
|
+
|
|
1866
|
+
// ../api/src/ads-meta/instantExperiences.ts
|
|
1867
|
+
import { z as z7 } from "zod";
|
|
1868
|
+
var tempRefSchema2 = z7.string().regex(TEMP_REF_REGEX, "expected a meta_temp_* reference");
|
|
1869
|
+
var httpsUrlSchema2 = z7.string().url().refine((value) => value.startsWith("https://"), "must be an https URL");
|
|
1870
|
+
var CANVAS_LIMITS = { nameMax: 100, elementsMax: 20, textMax: 1e3, buttonTextMax: 30 };
|
|
1871
|
+
var textElement = z7.strictObject({
|
|
1872
|
+
type: z7.literal("text"),
|
|
1873
|
+
text: z7.string().trim().min(1).max(CANVAS_LIMITS.textMax)
|
|
1874
|
+
});
|
|
1875
|
+
var buttonFields = {
|
|
1876
|
+
text: z7.string().trim().min(1).max(CANVAS_LIMITS.buttonTextMax),
|
|
1877
|
+
url: httpsUrlSchema2
|
|
1878
|
+
};
|
|
1879
|
+
var buttonElement = z7.strictObject({
|
|
1880
|
+
type: z7.literal("button"),
|
|
1881
|
+
...buttonFields,
|
|
1882
|
+
/** Meta's `button_style`: BUTTON_FILLED or BUTTON_OUTLINE (the two it took, live). */
|
|
1883
|
+
style: z7.enum(["filled", "outline"]).default("filled")
|
|
1884
|
+
});
|
|
1885
|
+
var idOrRefSchema = z7.union([z7.string().regex(NUMERIC_ID_REGEX), tempRefSchema2]);
|
|
1886
|
+
var photoElement = z7.strictObject({
|
|
1887
|
+
type: z7.literal("photo"),
|
|
1888
|
+
/**
|
|
1889
|
+
* A photo on the Page — the only picture Meta takes in an experience: its id,
|
|
1890
|
+
* or the ref of a Page photo this chat uploads (`pages photos upload`).
|
|
1891
|
+
*/
|
|
1892
|
+
photo_id: idOrRefSchema
|
|
1893
|
+
});
|
|
1894
|
+
var videoElement = z7.strictObject({
|
|
1895
|
+
type: z7.literal("video"),
|
|
1896
|
+
/** A video of the ad account (`baker ads meta media`), by id. */
|
|
1897
|
+
video_id: z7.string().regex(NUMERIC_ID_REGEX).optional(),
|
|
1898
|
+
/** A library video this chat uploads (`media upload --kind video`). */
|
|
1899
|
+
videoRef: tempRefSchema2.optional()
|
|
1900
|
+
}).refine(
|
|
1901
|
+
(p) => p.video_id === void 0 !== (p.videoRef === void 0),
|
|
1902
|
+
"a video element names video_id OR videoRef"
|
|
1903
|
+
);
|
|
1904
|
+
var productSetElement = z7.strictObject({
|
|
1905
|
+
type: z7.literal("product_set"),
|
|
1906
|
+
/** A product set's id, or the ref of one this chat creates. */
|
|
1907
|
+
product_set_id: idOrRefSchema,
|
|
1908
|
+
/** Meta's `show_in_feed`: the products also show under the ad itself — what makes a collection ad. */
|
|
1909
|
+
show_in_feed: z7.boolean().default(true),
|
|
1910
|
+
/** Catalog tokens allowed, e.g. "{{product.name}}". */
|
|
1911
|
+
item_headline: z7.string().trim().min(1).max(200).optional(),
|
|
1912
|
+
item_description: z7.string().trim().min(1).max(200).optional()
|
|
1913
|
+
});
|
|
1914
|
+
var footerElement = z7.strictObject({ type: z7.literal("footer"), ...buttonFields });
|
|
1915
|
+
var canvasElementSchema = z7.discriminatedUnion("type", [
|
|
1916
|
+
textElement,
|
|
1917
|
+
buttonElement,
|
|
1918
|
+
photoElement,
|
|
1919
|
+
videoElement,
|
|
1920
|
+
productSetElement,
|
|
1921
|
+
footerElement
|
|
1922
|
+
]);
|
|
1923
|
+
var canvasCreateSchema = z7.strictObject({
|
|
1924
|
+
page_id: z7.string().regex(NUMERIC_ID_REGEX),
|
|
1925
|
+
name: z7.string().trim().min(1).max(CANVAS_LIMITS.nameMax),
|
|
1926
|
+
/** Six hex digits, no `#` — e.g. FFFFFF. */
|
|
1927
|
+
background_color: z7.string().regex(/^[0-9A-Fa-f]{6}$/, "a colour is six hex digits, e.g. FFFFFF").optional(),
|
|
1928
|
+
elements: z7.array(canvasElementSchema).min(1).max(CANVAS_LIMITS.elementsMax),
|
|
1929
|
+
/** Publish it with the Session: an ad can only open a published experience, and a published one is fixed. */
|
|
1930
|
+
publish: z7.boolean().default(true)
|
|
1931
|
+
}).superRefine((p, ctx) => {
|
|
1932
|
+
const footers = p.elements.flatMap((element, index) => element.type === "footer" ? [index] : []);
|
|
1933
|
+
if (footers.length > 1 || footers.length === 1 && footers[0] !== p.elements.length - 1) {
|
|
1934
|
+
ctx.addIssue({
|
|
1935
|
+
code: "custom",
|
|
1936
|
+
path: ["elements"],
|
|
1937
|
+
message: "an experience has at most one footer, and it is the last element"
|
|
1938
|
+
});
|
|
1939
|
+
}
|
|
1940
|
+
});
|
|
1941
|
+
var canvasUpdateSchema = z7.strictObject({
|
|
1942
|
+
name: z7.string().trim().min(1).max(CANVAS_LIMITS.nameMax).optional(),
|
|
1943
|
+
publish: z7.literal(true).optional()
|
|
1944
|
+
}).refine((p) => p.name !== void 0 || p.publish !== void 0, "rename the experience or publish it");
|
|
1945
|
+
|
|
1946
|
+
// ../api/src/ads-meta/leadForms.ts
|
|
1947
|
+
import { z as z8 } from "zod";
|
|
1948
|
+
var httpsUrlSchema3 = z8.string().url().refine((value) => value.startsWith("https://"), "must be an https URL");
|
|
1949
|
+
var LEAD_FORM_LOCALES = [
|
|
1950
|
+
"AR_AR",
|
|
1951
|
+
"CS_CZ",
|
|
1952
|
+
"DA_DK",
|
|
1953
|
+
"DE_DE",
|
|
1954
|
+
"EL_GR",
|
|
1955
|
+
"EN_GB",
|
|
1956
|
+
"EN_US",
|
|
1957
|
+
"ES_ES",
|
|
1958
|
+
"ES_LA",
|
|
1959
|
+
"FI_FI",
|
|
1960
|
+
"FR_FR",
|
|
1961
|
+
"HE_IL",
|
|
1962
|
+
"HI_IN",
|
|
1963
|
+
"HU_HU",
|
|
1964
|
+
"ID_ID",
|
|
1965
|
+
"IT_IT",
|
|
1966
|
+
"JA_JP",
|
|
1967
|
+
"KO_KR",
|
|
1968
|
+
"NB_NO",
|
|
1969
|
+
"NL_NL",
|
|
1970
|
+
"PL_PL",
|
|
1971
|
+
"PT_BR",
|
|
1972
|
+
"PT_PT",
|
|
1973
|
+
"RO_RO",
|
|
1974
|
+
"RU_RU",
|
|
1975
|
+
"SV_SE",
|
|
1976
|
+
"TH_TH",
|
|
1977
|
+
"TR_TR",
|
|
1978
|
+
"VI_VN",
|
|
1979
|
+
"ZH_CN",
|
|
1980
|
+
"ZH_HK",
|
|
1981
|
+
"ZH_TW"
|
|
1982
|
+
];
|
|
1983
|
+
var LEAD_FORM_QUESTION_TYPES = [
|
|
1984
|
+
"CUSTOM",
|
|
1985
|
+
"FULL_NAME",
|
|
1986
|
+
"FIRST_NAME",
|
|
1987
|
+
"LAST_NAME",
|
|
1988
|
+
"EMAIL",
|
|
1989
|
+
"PHONE",
|
|
1990
|
+
"PHONE_OTP",
|
|
1991
|
+
"WORK_EMAIL",
|
|
1992
|
+
"WORK_PHONE_NUMBER",
|
|
1993
|
+
"WHATSAPP_NUMBER",
|
|
1994
|
+
"COMPANY_NAME",
|
|
1995
|
+
"JOB_TITLE",
|
|
1996
|
+
"WEBSITE",
|
|
1997
|
+
"STREET_ADDRESS",
|
|
1998
|
+
"ADDRESS_LINE_TWO",
|
|
1999
|
+
"CITY",
|
|
2000
|
+
"STATE",
|
|
2001
|
+
"PROVINCE",
|
|
2002
|
+
"COUNTRY",
|
|
2003
|
+
"ZIP",
|
|
2004
|
+
"POST_CODE",
|
|
2005
|
+
"DOB",
|
|
2006
|
+
"GENDER",
|
|
2007
|
+
"MARITIAL_STATUS",
|
|
2008
|
+
"RELATIONSHIP_STATUS",
|
|
2009
|
+
"MILITARY_STATUS",
|
|
2010
|
+
"EDUCATION_LEVEL",
|
|
2011
|
+
"VIN",
|
|
2012
|
+
"LICENSE_PLATE",
|
|
2013
|
+
"EMAIL_ALIAS",
|
|
2014
|
+
"DATE_TIME",
|
|
2015
|
+
"SLIDER",
|
|
2016
|
+
"ID_CPF",
|
|
2017
|
+
"ID_AR_DNI",
|
|
2018
|
+
"ID_CL_RUT",
|
|
2019
|
+
"ID_CO_CC",
|
|
2020
|
+
"ID_EC_CI",
|
|
2021
|
+
"ID_PE_DNI",
|
|
2022
|
+
"ID_MX_RFC",
|
|
2023
|
+
"USER_PROVIDED_PHONE_NUMBER",
|
|
2024
|
+
"STORE_LOOKUP",
|
|
2025
|
+
"STORE_LOOKUP_WITH_TYPEAHEAD",
|
|
2026
|
+
"LOCAL_DEALER",
|
|
2027
|
+
"JOIN_CODE",
|
|
2028
|
+
"FACEBOOK_LEAD_ID",
|
|
2029
|
+
"MESSENGER",
|
|
2030
|
+
"THREAD_LINK"
|
|
2031
|
+
];
|
|
2032
|
+
var WORDED_QUESTION_TYPES = /* @__PURE__ */ new Set(["CUSTOM", "DATE_TIME", "SLIDER"]);
|
|
2033
|
+
var NATIONAL_ID_COUNTRY = {
|
|
2034
|
+
ID_AR_DNI: "AR",
|
|
2035
|
+
ID_CPF: "BR",
|
|
2036
|
+
ID_CL_RUT: "CL",
|
|
2037
|
+
ID_CO_CC: "CO",
|
|
2038
|
+
ID_EC_CI: "EC",
|
|
2039
|
+
ID_PE_DNI: "PE",
|
|
2040
|
+
ID_MX_RFC: "MX"
|
|
2041
|
+
};
|
|
2042
|
+
var REFUSED_QUESTION_TYPES = {
|
|
2043
|
+
USER_PROVIDED_PHONE_NUMBER: "Meta refuses it on a form made through its API \u2014 ask for PHONE instead",
|
|
2044
|
+
LOCAL_DEALER: "Meta refuses it on a form made through its API (it needs a dealer setup in Ads Manager)",
|
|
2045
|
+
JOIN_CODE: "Meta refuses it on a form made through its API",
|
|
2046
|
+
FACEBOOK_LEAD_ID: "Meta refuses it on a form made through its API",
|
|
2047
|
+
MESSENGER: "Meta refuses it on a form made through its API",
|
|
2048
|
+
THREAD_LINK: "Meta refuses it on a form made through its API",
|
|
2049
|
+
STORE_LOOKUP: "a store locator needs a store-locations setup Meta does not open to Baker (#3, no capability) \u2014 build that form in Ads Manager",
|
|
2050
|
+
STORE_LOOKUP_WITH_TYPEAHEAD: "a store locator needs a store-locations setup Meta does not open to Baker (#3, no capability) \u2014 build that form in Ads Manager"
|
|
2051
|
+
};
|
|
2052
|
+
var LEAD_FORM_LIMITS = {
|
|
2053
|
+
nameMax: 255,
|
|
2054
|
+
labelMax: 255,
|
|
2055
|
+
questionsMax: 50,
|
|
2056
|
+
wordedQuestionsMax: 15,
|
|
2057
|
+
optionsMin: 2,
|
|
2058
|
+
optionsMax: 100,
|
|
2059
|
+
optionMax: 255,
|
|
2060
|
+
privacyLinkTextMax: 70,
|
|
2061
|
+
textMax: 1e3,
|
|
2062
|
+
introItemsMax: 5,
|
|
2063
|
+
buttonTextMax: 60,
|
|
2064
|
+
checkboxesMax: 10,
|
|
2065
|
+
trackingParametersMax: 20
|
|
2066
|
+
};
|
|
2067
|
+
var keySchema = z8.string().trim().min(1).max(100).regex(/^[A-Za-z0-9_]+$/, "a key is letters, digits and underscores");
|
|
2068
|
+
var optionSchema = z8.strictObject({
|
|
2069
|
+
value: z8.string().trim().min(1).max(LEAD_FORM_LIMITS.optionMax),
|
|
2070
|
+
/** What the answer is stored as on a lead; Baker numbers them when left out (Meta needs one). */
|
|
2071
|
+
key: keySchema.optional()
|
|
2072
|
+
});
|
|
2073
|
+
var questionTypeSchema = z8.preprocess(
|
|
2074
|
+
(value) => value === "ZIP_CODE" ? "ZIP" : value,
|
|
2075
|
+
z8.enum(LEAD_FORM_QUESTION_TYPES)
|
|
2076
|
+
);
|
|
2077
|
+
var leadFormQuestionSchema = z8.strictObject({
|
|
2078
|
+
type: questionTypeSchema,
|
|
2079
|
+
/** The question's wording — CUSTOM, DATE_TIME and SLIDER only; Meta words the rest in the form's language. */
|
|
2080
|
+
label: z8.string().trim().min(1).max(LEAD_FORM_LIMITS.labelMax).optional(),
|
|
2081
|
+
/** The field name the answer is stored under on a lead (Meta picks one when left out). */
|
|
2082
|
+
key: keySchema.optional(),
|
|
2083
|
+
/** A short note under the question, e.g. "We'll call to confirm the time." */
|
|
2084
|
+
inlineContext: z8.string().trim().min(1).max(LEAD_FORM_LIMITS.textMax).optional(),
|
|
2085
|
+
/** Multiple choice answers (CUSTOM only). */
|
|
2086
|
+
options: z8.array(optionSchema).max(LEAD_FORM_LIMITS.optionsMax).optional()
|
|
2087
|
+
});
|
|
2088
|
+
var THANK_YOU_BUTTON_TYPES = [
|
|
2089
|
+
"VIEW_WEBSITE",
|
|
2090
|
+
"DOWNLOAD",
|
|
2091
|
+
"BOOK_ON_WEBSITE",
|
|
2092
|
+
"CALL_BUSINESS",
|
|
2093
|
+
"WHATSAPP",
|
|
2094
|
+
"P2B_MESSENGER",
|
|
2095
|
+
"PROMO_CODE",
|
|
2096
|
+
"NONE",
|
|
2097
|
+
"MESSAGE_BUSINESS",
|
|
2098
|
+
"SCHEDULE_APPOINTMENT",
|
|
2099
|
+
"VIEW_ON_FACEBOOK"
|
|
2100
|
+
];
|
|
2101
|
+
var REFUSED_THANK_YOU_BUTTONS = {
|
|
2102
|
+
MESSAGE_BUSINESS: 'Meta answers "This button type is not yet supported" \u2014 use P2B_MESSENGER to open Messenger',
|
|
2103
|
+
SCHEDULE_APPOINTMENT: 'Meta answers "Appointment integration is missing" \u2014 it needs a booking integration set up in Meta Business Suite',
|
|
2104
|
+
VIEW_ON_FACEBOOK: "it opens a downloadable file Meta takes only as an upload Baker cannot send \u2014 use DOWNLOAD with a link"
|
|
2105
|
+
};
|
|
2106
|
+
var WEBSITE_BUTTONS = /* @__PURE__ */ new Set(["VIEW_WEBSITE", "DOWNLOAD", "BOOK_ON_WEBSITE"]);
|
|
2107
|
+
var thankYouSchema = z8.strictObject({
|
|
2108
|
+
title: z8.string().trim().min(1).max(LEAD_FORM_LIMITS.nameMax),
|
|
2109
|
+
body: z8.string().trim().min(1).max(LEAD_FORM_LIMITS.textMax).optional(),
|
|
2110
|
+
buttonType: z8.enum(THANK_YOU_BUTTON_TYPES).default("VIEW_WEBSITE"),
|
|
2111
|
+
buttonText: z8.string().trim().min(1).max(LEAD_FORM_LIMITS.buttonTextMax).optional(),
|
|
2112
|
+
/** Where VIEW_WEBSITE, DOWNLOAD and BOOK_ON_WEBSITE go; defaults to the form's follow-up link. */
|
|
2113
|
+
websiteUrl: httpsUrlSchema3.optional(),
|
|
2114
|
+
/** CALL_BUSINESS: the number, with its country. */
|
|
2115
|
+
businessPhoneNumber: z8.string().trim().min(5).max(30).optional(),
|
|
2116
|
+
countryCode: z8.string().regex(/^[A-Z]{2}$/, "a two-letter country code, e.g. US").optional(),
|
|
2117
|
+
/** Also offer to continue the conversation in Messenger. */
|
|
2118
|
+
enableMessenger: z8.boolean().optional()
|
|
2119
|
+
});
|
|
2120
|
+
var introSchema = z8.strictObject({
|
|
2121
|
+
title: z8.string().trim().min(1).max(LEAD_FORM_LIMITS.nameMax),
|
|
2122
|
+
/** PARAGRAPH_STYLE: one paragraph. LIST_STYLE: up to five bullet points. */
|
|
2123
|
+
style: z8.enum(["PARAGRAPH_STYLE", "LIST_STYLE"]).default("PARAGRAPH_STYLE"),
|
|
2124
|
+
content: z8.array(z8.string().trim().min(1).max(LEAD_FORM_LIMITS.textMax)).min(1).max(LEAD_FORM_LIMITS.introItemsMax),
|
|
2125
|
+
buttonText: z8.string().trim().min(1).max(LEAD_FORM_LIMITS.buttonTextMax).optional(),
|
|
2126
|
+
/** A photo already on the Page, by id — the only cover Meta takes from Baker. */
|
|
2127
|
+
coverPhotoId: z8.string().regex(NUMERIC_ID_REGEX).optional()
|
|
2128
|
+
});
|
|
2129
|
+
var disclaimerSchema = z8.strictObject({
|
|
2130
|
+
title: z8.string().trim().min(1).max(LEAD_FORM_LIMITS.nameMax).optional(),
|
|
2131
|
+
body: z8.string().trim().min(1).max(LEAD_FORM_LIMITS.textMax),
|
|
2132
|
+
/** Words of the body that link somewhere — each `text` must appear in the body. */
|
|
2133
|
+
links: z8.array(z8.strictObject({ text: z8.string().trim().min(1), url: httpsUrlSchema3 })).max(5).optional(),
|
|
2134
|
+
checkboxes: z8.array(
|
|
2135
|
+
z8.strictObject({
|
|
2136
|
+
text: z8.string().trim().min(1).max(LEAD_FORM_LIMITS.textMax),
|
|
2137
|
+
key: keySchema.optional(),
|
|
2138
|
+
/** Whether the box must be ticked to submit (Meta's default: required). */
|
|
2139
|
+
required: z8.boolean().default(true),
|
|
2140
|
+
checkedByDefault: z8.boolean().default(false)
|
|
2141
|
+
})
|
|
2142
|
+
).max(LEAD_FORM_LIMITS.checkboxesMax).optional()
|
|
2143
|
+
});
|
|
2144
|
+
function refuseQuestion(question, index, ctx) {
|
|
2145
|
+
const path = ["questions", index];
|
|
2146
|
+
const refused = REFUSED_QUESTION_TYPES[question.type];
|
|
2147
|
+
if (refused) {
|
|
2148
|
+
ctx.addIssue({ code: "custom", path: [...path, "type"], message: `${question.type}: ${refused}` });
|
|
2149
|
+
}
|
|
2150
|
+
const worded = WORDED_QUESTION_TYPES.has(question.type);
|
|
2151
|
+
if (worded && !question.label) {
|
|
2152
|
+
ctx.addIssue({ code: "custom", path: [...path, "label"], message: `${question.type} questions need a label` });
|
|
2153
|
+
}
|
|
2154
|
+
if (!worded && question.label) {
|
|
2155
|
+
ctx.addIssue({
|
|
2156
|
+
code: "custom",
|
|
2157
|
+
path: [...path, "label"],
|
|
2158
|
+
message: `Meta words ${question.type} itself, in the form's language \u2014 drop the label, or ask a CUSTOM question`
|
|
2159
|
+
});
|
|
2160
|
+
}
|
|
2161
|
+
if (question.options && question.type !== "CUSTOM") {
|
|
2162
|
+
ctx.addIssue({ code: "custom", path: [...path, "options"], message: "only a CUSTOM question takes answers" });
|
|
2163
|
+
}
|
|
2164
|
+
if (question.options && question.options.length < LEAD_FORM_LIMITS.optionsMin) {
|
|
2165
|
+
ctx.addIssue({
|
|
2166
|
+
code: "custom",
|
|
2167
|
+
path: [...path, "options"],
|
|
2168
|
+
message: "a multiple-choice question needs at least 2 answers"
|
|
2169
|
+
});
|
|
2170
|
+
}
|
|
2171
|
+
if (question.inlineContext && !worded) {
|
|
2172
|
+
ctx.addIssue({
|
|
2173
|
+
code: "custom",
|
|
2174
|
+
path: [...path, "inlineContext"],
|
|
2175
|
+
message: "a note under the question goes on a CUSTOM, DATE_TIME or SLIDER question"
|
|
2176
|
+
});
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
function storedKey(question) {
|
|
2180
|
+
return question.key ?? (WORDED_QUESTION_TYPES.has(question.type) ? void 0 : question.type.toLowerCase());
|
|
2181
|
+
}
|
|
2182
|
+
function refuseQuestions(questions, ctx) {
|
|
2183
|
+
questions.forEach((question, index) => {
|
|
2184
|
+
refuseQuestion(question, index, ctx);
|
|
2185
|
+
});
|
|
2186
|
+
const worded = questions.filter((question) => WORDED_QUESTION_TYPES.has(question.type)).length;
|
|
2187
|
+
if (worded > LEAD_FORM_LIMITS.wordedQuestionsMax) {
|
|
2188
|
+
ctx.addIssue({
|
|
2189
|
+
code: "custom",
|
|
2190
|
+
path: ["questions"],
|
|
2191
|
+
message: `Meta allows at most ${LEAD_FORM_LIMITS.wordedQuestionsMax} custom questions (CUSTOM, DATE_TIME, SLIDER) on a form`
|
|
2192
|
+
});
|
|
2193
|
+
}
|
|
2194
|
+
const ids = questions.filter((question) => question.type in NATIONAL_ID_COUNTRY);
|
|
2195
|
+
if (ids.length > 1) {
|
|
2196
|
+
ctx.addIssue({
|
|
2197
|
+
code: "custom",
|
|
2198
|
+
path: ["questions"],
|
|
2199
|
+
message: "a form asks for one national ID at most, and its ads must target only that ID's country"
|
|
2200
|
+
});
|
|
2201
|
+
}
|
|
2202
|
+
const seen = /* @__PURE__ */ new Map();
|
|
2203
|
+
questions.forEach((question, index) => {
|
|
2204
|
+
const key = storedKey(question);
|
|
2205
|
+
const typeKey = `${question.type}:${question.label ?? ""}`;
|
|
2206
|
+
for (const id of [key && `key:${key}`, `q:${typeKey}`]) {
|
|
2207
|
+
if (!id) continue;
|
|
2208
|
+
if (seen.has(id)) {
|
|
2209
|
+
ctx.addIssue({
|
|
2210
|
+
code: "custom",
|
|
2211
|
+
path: ["questions", index],
|
|
2212
|
+
message: id.startsWith("key:") ? `two questions share the key "${key}"` : "duplicate question"
|
|
2213
|
+
});
|
|
2214
|
+
}
|
|
2215
|
+
seen.set(id, index);
|
|
2216
|
+
}
|
|
2217
|
+
});
|
|
2218
|
+
}
|
|
2219
|
+
function refuseThankYou(thankYou, followUpActionUrl, ctx) {
|
|
2220
|
+
const path = ["thankYou"];
|
|
2221
|
+
const refused = REFUSED_THANK_YOU_BUTTONS[thankYou.buttonType];
|
|
2222
|
+
if (refused) {
|
|
2223
|
+
ctx.addIssue({ code: "custom", path: [...path, "buttonType"], message: `${thankYou.buttonType}: ${refused}` });
|
|
2224
|
+
}
|
|
2225
|
+
if (thankYou.buttonType !== "NONE" && thankYou.buttonType !== "VIEW_WEBSITE" && !thankYou.buttonText) {
|
|
2226
|
+
ctx.addIssue({ code: "custom", path: [...path, "buttonText"], message: "the thank-you button needs its text" });
|
|
2227
|
+
}
|
|
2228
|
+
if (WEBSITE_BUTTONS.has(thankYou.buttonType) && !thankYou.websiteUrl && !followUpActionUrl) {
|
|
2229
|
+
ctx.addIssue({
|
|
2230
|
+
code: "custom",
|
|
2231
|
+
path: [...path, "websiteUrl"],
|
|
2232
|
+
message: `a ${thankYou.buttonType} button needs the website it opens (websiteUrl or followUpActionUrl)`
|
|
2233
|
+
});
|
|
2234
|
+
}
|
|
2235
|
+
if (thankYou.buttonType === "CALL_BUSINESS" && (!thankYou.businessPhoneNumber || !thankYou.countryCode)) {
|
|
2236
|
+
ctx.addIssue({
|
|
2237
|
+
code: "custom",
|
|
2238
|
+
path: [...path, "businessPhoneNumber"],
|
|
2239
|
+
message: "a CALL_BUSINESS button needs businessPhoneNumber and its countryCode"
|
|
2240
|
+
});
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
function refuseIntro(intro, ctx) {
|
|
2244
|
+
if (intro.style === "PARAGRAPH_STYLE" && intro.content.length !== 1) {
|
|
2245
|
+
ctx.addIssue({
|
|
2246
|
+
code: "custom",
|
|
2247
|
+
path: ["intro", "content"],
|
|
2248
|
+
message: "a PARAGRAPH_STYLE intro holds exactly one paragraph \u2014 use LIST_STYLE for bullet points"
|
|
2249
|
+
});
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
function refuseDisclaimer(disclaimer, ctx) {
|
|
2253
|
+
for (const [index, link] of (disclaimer.links ?? []).entries()) {
|
|
2254
|
+
if (!disclaimer.body.includes(link.text)) {
|
|
2255
|
+
ctx.addIssue({
|
|
2256
|
+
code: "custom",
|
|
2257
|
+
path: ["disclaimer", "links", index, "text"],
|
|
2258
|
+
message: `"${link.text}" is not in the disclaimer's text \u2014 a link is words of the text`
|
|
2259
|
+
});
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
var leadFormCreateSchema = z8.strictObject({
|
|
2264
|
+
page_id: z8.string().regex(NUMERIC_ID_REGEX),
|
|
2265
|
+
/** Unique among the Page's forms — Meta refuses a name the Page already has. */
|
|
2266
|
+
name: z8.string().trim().min(1).max(LEAD_FORM_LIMITS.nameMax),
|
|
2267
|
+
locale: z8.enum(LEAD_FORM_LOCALES).default("EN_US"),
|
|
2268
|
+
/** The headline above the questions. */
|
|
2269
|
+
questionPageHeadline: z8.string().trim().min(1).max(LEAD_FORM_LIMITS.nameMax).optional(),
|
|
2270
|
+
questions: z8.array(leadFormQuestionSchema).min(1).max(LEAD_FORM_LIMITS.questionsMax),
|
|
2271
|
+
privacyPolicyUrl: httpsUrlSchema3,
|
|
2272
|
+
privacyPolicyLinkText: z8.string().trim().min(1).max(LEAD_FORM_LIMITS.privacyLinkTextMax).default("Privacy Policy"),
|
|
2273
|
+
/** Where "View website" goes after submitting. Required without a thank-you screen. */
|
|
2274
|
+
followUpActionUrl: httpsUrlSchema3.optional(),
|
|
2275
|
+
/** Higher intent: people review and confirm their answers before submitting. Off = more volume. */
|
|
2276
|
+
optimizedForQuality: z8.boolean().default(false),
|
|
2277
|
+
/** The intro screen before the questions (Meta's context card). */
|
|
2278
|
+
intro: introSchema.optional(),
|
|
2279
|
+
/** Extra terms and consent checkboxes shown with the privacy policy. */
|
|
2280
|
+
disclaimer: disclaimerSchema.optional(),
|
|
2281
|
+
thankYou: thankYouSchema.optional(),
|
|
2282
|
+
/**
|
|
2283
|
+
* "restricted": only people the ad was shown to can open the form (Meta's
|
|
2284
|
+
* default when unset). "open": anyone the ad is shared with can.
|
|
2285
|
+
*/
|
|
2286
|
+
sharing: z8.enum(["restricted", "open"]).optional(),
|
|
2287
|
+
/** Confirm the phone number with a code sent by text message. Needs a PHONE question. */
|
|
2288
|
+
smsVerification: z8.boolean().optional(),
|
|
2289
|
+
/** Name/value pairs Meta adds to every lead's answers, e.g. { "campaign": "spring" }. */
|
|
2290
|
+
trackingParameters: z8.record(keySchema, z8.string().trim().min(1).max(LEAD_FORM_LIMITS.optionMax)).refine(
|
|
2291
|
+
(value) => Object.keys(value).length <= LEAD_FORM_LIMITS.trackingParametersMax,
|
|
2292
|
+
`at most ${LEAD_FORM_LIMITS.trackingParametersMax} tracking parameters`
|
|
2293
|
+
).optional()
|
|
2294
|
+
}).superRefine((form, ctx) => {
|
|
2295
|
+
refuseQuestions(form.questions, ctx);
|
|
2296
|
+
if (form.thankYou) {
|
|
2297
|
+
refuseThankYou(form.thankYou, form.followUpActionUrl, ctx);
|
|
2298
|
+
} else if (!form.followUpActionUrl) {
|
|
2299
|
+
ctx.addIssue({
|
|
2300
|
+
code: "custom",
|
|
2301
|
+
path: ["followUpActionUrl"],
|
|
2302
|
+
message: "a form without a thank-you screen needs followUpActionUrl (Meta's View website button)"
|
|
2303
|
+
});
|
|
2304
|
+
}
|
|
2305
|
+
if (form.intro) refuseIntro(form.intro, ctx);
|
|
2306
|
+
if (form.disclaimer) refuseDisclaimer(form.disclaimer, ctx);
|
|
2307
|
+
if (form.smsVerification && !form.questions.some((question) => question.type === "PHONE")) {
|
|
2308
|
+
ctx.addIssue({
|
|
2309
|
+
code: "custom",
|
|
2310
|
+
path: ["smsVerification"],
|
|
2311
|
+
message: "text-message verification needs a PHONE question"
|
|
2312
|
+
});
|
|
2313
|
+
}
|
|
2314
|
+
});
|
|
2315
|
+
var leadFormUpdateSchema = z8.looseObject({ status: z8.enum(["ARCHIVED", "ACTIVE"]).optional() }).superRefine((p, ctx) => {
|
|
2316
|
+
const others = Object.keys(p).filter((key) => key !== "status");
|
|
2317
|
+
if (others.length > 0) {
|
|
2318
|
+
ctx.addIssue({
|
|
2319
|
+
code: "custom",
|
|
2320
|
+
path: [others[0] ?? "status"],
|
|
2321
|
+
message: `Meta keeps an Instant Form's ${others.join(", ")} as they were once the form exists (it answers success and changes nothing). Make a changed copy instead: \`baker ads meta lead-forms duplicate <formId> --name "\u2026" --file changes.json\`, point the ads at the copy, then \`lead-forms archive <formId>\`.`
|
|
2322
|
+
});
|
|
2323
|
+
}
|
|
2324
|
+
if (p.status === void 0 && others.length === 0) {
|
|
2325
|
+
ctx.addIssue({ code: "custom", path: ["status"], message: "archive the form or unarchive it (status)" });
|
|
2326
|
+
}
|
|
2327
|
+
});
|
|
2328
|
+
|
|
2329
|
+
// ../api/src/ads-meta/mediaSpecs.ts
|
|
2330
|
+
var RATIO_4_5 = 4 / 5;
|
|
2331
|
+
var RATIO_9_16 = 9 / 16;
|
|
2332
|
+
var VIDEO_MAX_SECONDS = [
|
|
2333
|
+
["Facebook Stories", 3 * 60],
|
|
2334
|
+
["Reels", 15 * 60],
|
|
2335
|
+
["Instagram feed and Stories", 60 * 60],
|
|
2336
|
+
["Facebook feed", 241 * 60]
|
|
2337
|
+
];
|
|
2338
|
+
var POSITION_MAX_SECONDS = {
|
|
2339
|
+
facebook: {
|
|
2340
|
+
feed: ["Facebook feed", 241 * 60],
|
|
2341
|
+
story: ["Facebook Stories", 3 * 60],
|
|
2342
|
+
facebook_reels: ["Facebook Reels", 15 * 60],
|
|
2343
|
+
instream_video: ["Facebook in-stream", 10 * 60]
|
|
2344
|
+
},
|
|
2345
|
+
instagram: {
|
|
2346
|
+
stream: ["Instagram feed", 60 * 60],
|
|
2347
|
+
story: ["Instagram Stories", 60 * 60],
|
|
2348
|
+
explore: ["Instagram Explore", 60 * 60],
|
|
2349
|
+
reels: ["Instagram Reels", 15 * 60],
|
|
2350
|
+
profile_reels: ["Instagram profile Reels", 15 * 60]
|
|
2351
|
+
},
|
|
2352
|
+
messenger: { story: ["Messenger Stories", 60] }
|
|
2353
|
+
};
|
|
2354
|
+
var PLACEMENT_SLOTS = ["feed", "stories", "reels", "right_column", "default"];
|
|
2355
|
+
|
|
2356
|
+
// ../api/src/ads-meta/messaging.ts
|
|
2357
|
+
import { z as z9 } from "zod";
|
|
2358
|
+
var APP_DESTINATIONS = ["MESSENGER", "WHATSAPP", "INSTAGRAM_DIRECT"];
|
|
2359
|
+
var MESSAGING_CTA_DESTINATION = {
|
|
2360
|
+
MESSAGE_PAGE: "MESSENGER",
|
|
2361
|
+
WHATSAPP_MESSAGE: "WHATSAPP",
|
|
2362
|
+
INSTAGRAM_MESSAGE: "INSTAGRAM_DIRECT"
|
|
2363
|
+
};
|
|
2364
|
+
var MESSAGING_DESTINATION_TYPES = [
|
|
2365
|
+
"MESSENGER",
|
|
2366
|
+
"WHATSAPP",
|
|
2367
|
+
"INSTAGRAM_DIRECT",
|
|
2368
|
+
"MESSAGING_MESSENGER_WHATSAPP",
|
|
2369
|
+
"MESSAGING_INSTAGRAM_DIRECT_MESSENGER",
|
|
2370
|
+
"MESSAGING_INSTAGRAM_DIRECT_WHATSAPP",
|
|
2371
|
+
"MESSAGING_INSTAGRAM_DIRECT_MESSENGER_WHATSAPP"
|
|
2372
|
+
];
|
|
2373
|
+
var WELCOME_LIMITS = { textMax: 300, iceBreakerTitleMax: 80, iceBreakerResponseMax: 300, iceBreakersMax: 4 };
|
|
2374
|
+
var welcomeMessageSchema = z9.strictObject({
|
|
2375
|
+
greeting: z9.string().trim().min(1).max(WELCOME_LIMITS.textMax),
|
|
2376
|
+
iceBreakers: z9.array(
|
|
2377
|
+
z9.strictObject({
|
|
2378
|
+
question: z9.string().trim().min(1).max(WELCOME_LIMITS.iceBreakerTitleMax),
|
|
2379
|
+
answer: z9.string().trim().min(1).max(WELCOME_LIMITS.iceBreakerResponseMax)
|
|
2380
|
+
})
|
|
2381
|
+
).min(1).max(WELCOME_LIMITS.iceBreakersMax).optional(),
|
|
2382
|
+
autofill: z9.string().trim().min(1).max(WELCOME_LIMITS.textMax).optional()
|
|
2383
|
+
}).refine((p) => !(p.iceBreakers && p.autofill), {
|
|
2384
|
+
message: "a greeting offers questions to tap OR a message to send, not both",
|
|
2385
|
+
path: ["autofill"]
|
|
2386
|
+
});
|
|
2387
|
+
|
|
2388
|
+
// ../api/src/ads-meta/pagePhotos.ts
|
|
2389
|
+
import { z as z10 } from "zod";
|
|
2390
|
+
var httpsUrlSchema4 = z10.string().url().refine((value) => value.startsWith("https://"), "must be an https URL");
|
|
2391
|
+
var pagePhotoCreateSchema = z10.strictObject({
|
|
2392
|
+
page_id: z10.string().regex(NUMERIC_ID_REGEX),
|
|
2393
|
+
/** A Baker library image — staging fills `url` with its Baker address. */
|
|
2394
|
+
bakerImageId: z10.string().min(1).max(64).optional(),
|
|
2395
|
+
/** A public https picture address Meta can fetch. */
|
|
2396
|
+
url: httpsUrlSchema4.optional(),
|
|
2397
|
+
/** Shown with the photo in the Page's library only; it is never posted. */
|
|
2398
|
+
caption: z10.string().trim().min(1).max(2e3).optional()
|
|
2399
|
+
}).refine((p) => p.bakerImageId !== void 0 || p.url !== void 0, "a Page photo names bakerImageId or url");
|
|
2400
|
+
var pagePhotoUpdateSchema = z10.strictObject({ status: z10.literal("DELETED") });
|
|
2401
|
+
|
|
2402
|
+
// ../api/src/ads-meta/reachFrequency.ts
|
|
2403
|
+
import { z as z11 } from "zod";
|
|
2404
|
+
var unixSeconds = z11.number().int().positive();
|
|
2405
|
+
var INSTREAM_PACKAGES = [
|
|
2406
|
+
"NORMAL",
|
|
2407
|
+
"PREMIUM",
|
|
2408
|
+
"SPORTS",
|
|
2409
|
+
"ENTERTAINMENT",
|
|
2410
|
+
"BEAUTY",
|
|
2411
|
+
"FOOD",
|
|
2412
|
+
"SPANISH",
|
|
2413
|
+
"REGULAR_ANIMALS_PETS",
|
|
2414
|
+
"REGULAR_FOOD",
|
|
2415
|
+
"REGULAR_GAMES",
|
|
2416
|
+
"REGULAR_POLITICS",
|
|
2417
|
+
"REGULAR_SPORTS",
|
|
2418
|
+
"REGULAR_STYLE",
|
|
2419
|
+
"REGULAR_TV_MOVIES"
|
|
2420
|
+
];
|
|
2421
|
+
var reachFrequencyPredictionRequestSchema = z11.strictObject({
|
|
2422
|
+
accountId: z11.string().regex(NUMERIC_ID_REGEX),
|
|
2423
|
+
/** `reach`: how many people a budget reaches. `budget`: what a reach costs. */
|
|
2424
|
+
solveFor: z11.enum(["reach", "budget"]),
|
|
2425
|
+
/** Lifetime budget, major units — with solveFor reach. */
|
|
2426
|
+
budget: z11.string().regex(/^\d+(\.\d{1,2})?$/).optional(),
|
|
2427
|
+
/** People to reach — with solveFor budget. Meta's floor is usually 200,000 per country. */
|
|
2428
|
+
reach: z11.number().int().positive().optional(),
|
|
2429
|
+
start_time: unixSeconds,
|
|
2430
|
+
end_time: unixSeconds,
|
|
2431
|
+
/** Most times one person sees the ad in each `frequencyCapDays`. */
|
|
2432
|
+
frequencyCap: z11.number().int().min(1).max(90).optional(),
|
|
2433
|
+
frequencyCapDays: z11.number().int().min(1).max(90).optional(),
|
|
2434
|
+
objective: z11.enum([
|
|
2435
|
+
"REACH",
|
|
2436
|
+
"BRAND_AWARENESS",
|
|
2437
|
+
"VIDEO_VIEWS",
|
|
2438
|
+
"POST_ENGAGEMENT",
|
|
2439
|
+
"LINK_CLICKS",
|
|
2440
|
+
"OUTCOME_AWARENESS",
|
|
2441
|
+
"OUTCOME_ENGAGEMENT"
|
|
2442
|
+
]).default("REACH"),
|
|
2443
|
+
/**
|
|
2444
|
+
* Balanced frequency (`is_balanced_frequency` + `target_frequency`): Meta
|
|
2445
|
+
* aims each person at this many views per `targetFrequencyDays` rather than
|
|
2446
|
+
* capping. Live on v26.0 (2026-10-15) it quoted only under
|
|
2447
|
+
* OUTCOME_AWARENESS / OUTCOME_ENGAGEMENT with a REACH or THRUPLAY goal
|
|
2448
|
+
* ("Target frequency can only be used when…"), and a plain quote without it
|
|
2449
|
+
* needs a frequency cap.
|
|
2450
|
+
*/
|
|
2451
|
+
targetFrequency: z11.number().int().min(1).max(90).optional(),
|
|
2452
|
+
targetFrequencyDays: z11.number().int().min(1).max(90).optional(),
|
|
2453
|
+
optimization_goal: z11.string().min(1).max(64).optional(),
|
|
2454
|
+
/** The Page (and Instagram account) the ads run as. */
|
|
2455
|
+
page_id: z11.string().regex(NUMERIC_ID_REGEX),
|
|
2456
|
+
instagram_user_id: z11.string().regex(NUMERIC_ID_REGEX).optional(),
|
|
2457
|
+
/** Meta targeting: ONE country, no website custom audiences. */
|
|
2458
|
+
targeting: z11.record(z11.string(), z11.unknown()),
|
|
2459
|
+
/**
|
|
2460
|
+
* Run only in these hours (`day_parting_schedule`): minutes from midnight
|
|
2461
|
+
* and days (0 = Sunday). Live on v26.0 (2026-10-15) a weekday 09–18 quote
|
|
2462
|
+
* came back with its own reach.
|
|
2463
|
+
*/
|
|
2464
|
+
day_parting_schedule: z11.array(
|
|
2465
|
+
z11.strictObject({
|
|
2466
|
+
start_minute: z11.number().int().min(0).max(1440),
|
|
2467
|
+
end_minute: z11.number().int().min(0).max(1440),
|
|
2468
|
+
days: z11.array(z11.number().int().min(0).max(6)).min(1)
|
|
2469
|
+
})
|
|
2470
|
+
).min(1).optional(),
|
|
2471
|
+
/** In-stream video content packages the ads may run in (`instream_packages`). */
|
|
2472
|
+
instream_packages: z11.array(z11.enum(INSTREAM_PACKAGES)).min(1).optional()
|
|
2473
|
+
}).superRefine((p, ctx) => {
|
|
2474
|
+
if (p.solveFor === "reach" && p.budget === void 0) {
|
|
2475
|
+
ctx.addIssue({ code: "custom", path: ["budget"], message: "solving for reach needs --budget" });
|
|
2476
|
+
}
|
|
2477
|
+
if (p.solveFor === "budget" && p.reach === void 0) {
|
|
2478
|
+
ctx.addIssue({ code: "custom", path: ["reach"], message: "solving for budget needs --reach" });
|
|
2479
|
+
}
|
|
2480
|
+
if (p.end_time <= p.start_time) {
|
|
2481
|
+
ctx.addIssue({ code: "custom", path: ["end_time"], message: "the end comes after the start" });
|
|
2482
|
+
}
|
|
2483
|
+
if (p.targetFrequency !== void 0) {
|
|
2484
|
+
const objectiveOk = p.objective === "OUTCOME_AWARENESS" || p.objective === "OUTCOME_ENGAGEMENT";
|
|
2485
|
+
const goalOk = p.optimization_goal === "REACH" || p.optimization_goal === "THRUPLAY";
|
|
2486
|
+
if (!objectiveOk || !goalOk) {
|
|
2487
|
+
ctx.addIssue({
|
|
2488
|
+
code: "custom",
|
|
2489
|
+
path: ["targetFrequency"],
|
|
2490
|
+
message: "balanced frequency needs --objective OUTCOME_AWARENESS (or OUTCOME_ENGAGEMENT) and --optimization-goal REACH (or THRUPLAY)"
|
|
2491
|
+
});
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
if (p.end_time - p.start_time > 90 * 86400) {
|
|
2495
|
+
ctx.addIssue({ code: "custom", path: ["end_time"], message: "Meta reserves at most 90 days" });
|
|
2496
|
+
}
|
|
2497
|
+
});
|
|
2498
|
+
var reachFrequencyReservationCreateSchema = z11.strictObject({
|
|
2499
|
+
prediction_id: z11.string().regex(NUMERIC_ID_REGEX)
|
|
2500
|
+
});
|
|
2501
|
+
var reachFrequencyReservationUpdateSchema = z11.strictObject({ status: z11.literal("CANCELLED") });
|
|
2502
|
+
|
|
2503
|
+
// ../api/src/ads-meta/ops.ts
|
|
2504
|
+
var tempRefSchema3 = z12.string().regex(TEMP_REF_REGEX, "expected a meta_temp_* reference");
|
|
2505
|
+
var parentRefSchema = z12.union([z12.string().regex(NUMERIC_ID_REGEX, "expected a numeric id"), tempRefSchema3]);
|
|
2506
|
+
var moneySchema = z12.strictObject({
|
|
2507
|
+
amount: z12.string().regex(/^\d+(\.\d{1,2})?$/, "expected a decimal amount like 50 or 50.00").refine((val) => Number(val) > 0, "amount must be greater than zero"),
|
|
2508
|
+
currencyCode: z12.string().length(3).optional()
|
|
2509
|
+
});
|
|
2510
|
+
var httpsUrlSchema5 = z12.string().url().refine((u) => u.startsWith("https://"), "destination URLs must be https");
|
|
2511
|
+
var bakerMediaIdSchema = z12.string().min(1);
|
|
2512
|
+
var stageableStatusSchema = z12.enum(STAGEABLE_CREATE_STATUSES);
|
|
2513
|
+
var updateStatusSchema = z12.enum(UPDATE_STATUSES);
|
|
2514
|
+
var labelFields = {
|
|
2515
|
+
adlabels: z12.array(parentRefSchema).max(META_LIMITS.adLabel.perObjectMax).optional(),
|
|
2516
|
+
addLabels: z12.array(parentRefSchema).min(1).optional(),
|
|
2517
|
+
removeLabels: z12.array(parentRefSchema).min(1).optional()
|
|
2518
|
+
};
|
|
2519
|
+
var createLabelFields = { adlabels: labelFields.adlabels };
|
|
2520
|
+
function currencyMinimums(currencyCode) {
|
|
2521
|
+
return CURRENCY_MINIMUMS[currencyCode] ?? DEFAULT_CURRENCY_MINIMUM;
|
|
2522
|
+
}
|
|
2523
|
+
function validateDailyBudgetFloor(money, ctx, path) {
|
|
2524
|
+
if (money?.currencyCode) {
|
|
2525
|
+
const min = currencyMinimums(money.currencyCode).dailyBudgetMin;
|
|
2526
|
+
if (Number(money.amount) < min) {
|
|
2527
|
+
ctx.addIssue({ code: "custom", path, message: `below the ${min} ${money.currencyCode} daily minimum` });
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
var geoLocationsSchema = z12.object({
|
|
2532
|
+
countries: z12.array(z12.string().length(2)).optional(),
|
|
2533
|
+
regions: z12.array(z12.object({ key: z12.string() })).optional(),
|
|
2534
|
+
cities: z12.array(z12.object({ key: z12.string(), radius: z12.number().optional(), distance_unit: z12.string().optional() })).optional(),
|
|
2535
|
+
zips: z12.array(z12.object({ key: z12.string() })).optional(),
|
|
2536
|
+
location_types: z12.array(z12.string()).optional()
|
|
2537
|
+
}).catchall(z12.unknown());
|
|
2538
|
+
var idNameSchema = z12.object({ id: z12.string(), name: z12.string().optional() });
|
|
2539
|
+
var productAudienceEventSchema = z12.strictObject({
|
|
2540
|
+
retention_seconds: z12.number().int().min(3600).max(180 * 86400),
|
|
2541
|
+
rule: z12.record(z12.string(), z12.unknown()).refine((rule) => JSON.stringify(rule).includes('"event"'), {
|
|
2542
|
+
message: 'a product audience rule names the event it counts, e.g. {"event":{"eq":"ViewContent"}}'
|
|
2543
|
+
})
|
|
2544
|
+
});
|
|
2545
|
+
var productAudienceSpecSchema = z12.strictObject({
|
|
2546
|
+
/** A product set's id, or the meta_temp_* ref of one this chat creates. */
|
|
2547
|
+
product_set_id: parentRefSchema,
|
|
2548
|
+
inclusions: z12.array(productAudienceEventSchema).min(1),
|
|
2549
|
+
exclusions: z12.array(productAudienceEventSchema).min(1).optional()
|
|
2550
|
+
});
|
|
2551
|
+
var metaTargetingSchema = z12.object({
|
|
2552
|
+
geo_locations: geoLocationsSchema.optional(),
|
|
2553
|
+
excluded_geo_locations: geoLocationsSchema.optional(),
|
|
2554
|
+
age_min: z12.number().int().min(13).max(65).optional(),
|
|
2555
|
+
age_max: z12.number().int().min(13).max(65).optional(),
|
|
2556
|
+
genders: z12.array(z12.union([z12.literal(1), z12.literal(2)])).optional(),
|
|
2557
|
+
locales: z12.array(z12.number().int()).optional(),
|
|
2558
|
+
interests: z12.array(idNameSchema).optional(),
|
|
2559
|
+
behaviors: z12.array(idNameSchema).optional(),
|
|
2560
|
+
custom_audiences: z12.array(z12.object({ id: parentRefSchema })).optional(),
|
|
2561
|
+
excluded_custom_audiences: z12.array(z12.object({ id: parentRefSchema })).optional(),
|
|
2562
|
+
flexible_spec: z12.array(z12.record(z12.string(), z12.unknown())).optional(),
|
|
2563
|
+
product_audience_specs: z12.array(productAudienceSpecSchema).min(1).optional(),
|
|
2564
|
+
excluded_product_audience_specs: z12.array(productAudienceSpecSchema).min(1).optional(),
|
|
2565
|
+
/**
|
|
2566
|
+
* Retired by Meta: live on v25.0 (2026-10-14) a create excluding one
|
|
2567
|
+
* interest was refused — "Detailed targeting exclusions are no longer
|
|
2568
|
+
* available for ad sets" (subcode 3858492). Excluding people goes through
|
|
2569
|
+
* `excluded_custom_audiences`.
|
|
2570
|
+
*/
|
|
2571
|
+
exclusions: z12.record(z12.string(), z12.unknown()).refine((value) => Object.keys(value).length === 0, {
|
|
2572
|
+
message: "Meta no longer lets an ad set exclude detailed targeting (interests, behaviors, demographics) \u2014 take targeting.exclusions out; to keep people out, exclude a custom audience (excluded_custom_audiences)"
|
|
2573
|
+
}).optional(),
|
|
2574
|
+
publisher_platforms: z12.array(z12.string()).optional(),
|
|
2575
|
+
facebook_positions: z12.array(z12.string()).optional(),
|
|
2576
|
+
instagram_positions: z12.array(z12.string()).optional(),
|
|
2577
|
+
audience_network_positions: z12.array(z12.string()).optional(),
|
|
2578
|
+
messenger_positions: z12.array(z12.string()).optional(),
|
|
2579
|
+
threads_positions: z12.array(z12.string()).optional(),
|
|
2580
|
+
device_platforms: z12.array(z12.string()).optional(),
|
|
2581
|
+
targeting_automation: z12.object({ advantage_audience: z12.union([z12.literal(0), z12.literal(1)]) }).partial().optional(),
|
|
2582
|
+
...brandSafetyTargetingFields
|
|
2583
|
+
}).catchall(z12.unknown());
|
|
2584
|
+
var GEO_INCLUSION_KEYS = [
|
|
2585
|
+
"countries",
|
|
2586
|
+
"country_groups",
|
|
2587
|
+
"regions",
|
|
2588
|
+
"cities",
|
|
2589
|
+
"zips",
|
|
2590
|
+
"geo_markets",
|
|
2591
|
+
"places",
|
|
2592
|
+
"custom_locations",
|
|
2593
|
+
"electoral_districts",
|
|
2594
|
+
"subcities",
|
|
2595
|
+
"neighborhoods",
|
|
2596
|
+
"subneighborhoods",
|
|
2597
|
+
"metro_areas",
|
|
2598
|
+
"large_geo_areas",
|
|
2599
|
+
"medium_geo_areas",
|
|
2600
|
+
"small_geo_areas"
|
|
2601
|
+
];
|
|
2602
|
+
function targetingHasGeo(t) {
|
|
2603
|
+
const geo = t?.geo_locations;
|
|
2604
|
+
return Boolean(
|
|
2605
|
+
geo && GEO_INCLUSION_KEYS.some((key) => Array.isArray(geo[key]) && geo[key].length > 0)
|
|
2606
|
+
);
|
|
2607
|
+
}
|
|
2608
|
+
function targetingHasGeoOrAudience(t) {
|
|
2609
|
+
if (!t) {
|
|
2610
|
+
return false;
|
|
2611
|
+
}
|
|
2612
|
+
return targetingHasGeo(t) || Boolean(t.custom_audiences?.length);
|
|
2613
|
+
}
|
|
2614
|
+
function advantageAudienceSetting(t) {
|
|
2615
|
+
const value = t.targeting_automation?.advantage_audience;
|
|
2616
|
+
return value === 0 || value === 1 ? value : void 0;
|
|
2617
|
+
}
|
|
2618
|
+
function targetingHardLimitsDemographics(t) {
|
|
2619
|
+
if (!t) {
|
|
2620
|
+
return false;
|
|
2621
|
+
}
|
|
2622
|
+
const narrowsAgeMax = typeof t.age_max === "number" && t.age_max < ADVANTAGE_AUDIENCE_OPEN_AGE_MAX;
|
|
2623
|
+
const narrowsAgeMin = typeof t.age_min === "number" && t.age_min > ADVANTAGE_AUDIENCE_MAX_HARD_AGE_MIN;
|
|
2624
|
+
const narrowsGender = Array.isArray(t.genders) && t.genders.length === 1;
|
|
2625
|
+
return narrowsAgeMax || narrowsAgeMin || narrowsGender;
|
|
2626
|
+
}
|
|
2627
|
+
var QUARTER_HOUR_MS = 15 * 6e4;
|
|
2628
|
+
var MIN_HIGH_DEMAND_PERIOD_MS = 3 * 36e5;
|
|
2629
|
+
var budgetScheduleSchema = z12.strictObject({
|
|
2630
|
+
time_start: z12.number().int().positive(),
|
|
2631
|
+
time_end: z12.number().int().positive(),
|
|
2632
|
+
increase: moneySchema.optional(),
|
|
2633
|
+
increasePercent: z12.number().int().min(1).max(800).optional()
|
|
2634
|
+
}).superRefine((period, ctx) => {
|
|
2635
|
+
if (period.increase === void 0 === (period.increasePercent === void 0)) {
|
|
2636
|
+
ctx.addIssue({
|
|
2637
|
+
code: "custom",
|
|
2638
|
+
path: ["increase"],
|
|
2639
|
+
message: "a budget schedule raises the budget by an amount (increase) or a percentage (increasePercent) \u2014 one of them"
|
|
2640
|
+
});
|
|
2641
|
+
}
|
|
2642
|
+
if (period.time_end - period.time_start < MIN_HIGH_DEMAND_PERIOD_MS) {
|
|
2643
|
+
ctx.addIssue({ code: "custom", path: ["time_end"], message: "a high-demand period lasts at least 3 hours" });
|
|
2644
|
+
}
|
|
2645
|
+
if (period.time_start % QUARTER_HOUR_MS !== 0 || period.time_end % QUARTER_HOUR_MS !== 0) {
|
|
2646
|
+
ctx.addIssue({
|
|
2647
|
+
code: "custom",
|
|
2648
|
+
path: ["time_start"],
|
|
2649
|
+
message: "Meta starts and ends a high-demand period on a quarter hour (:00, :15, :30 or :45)"
|
|
2650
|
+
});
|
|
2651
|
+
}
|
|
2652
|
+
});
|
|
2653
|
+
var budgetSchedulesSchema = z12.array(budgetScheduleSchema).min(1).max(META_LIMITS.adSet.budgetSchedulesMax).refine(
|
|
2654
|
+
(periods) => new Set(periods.map((period) => period.increase === void 0)).size <= 1,
|
|
2655
|
+
"Meta raises a budget by amounts or by percentages, not both \u2014 use one kind for every period"
|
|
2656
|
+
);
|
|
2657
|
+
function validateBudgetSchedules(p, ctx) {
|
|
2658
|
+
if (p.budgetSchedules && !p.dailyBudget) {
|
|
2659
|
+
ctx.addIssue({
|
|
2660
|
+
code: "custom",
|
|
2661
|
+
path: ["budgetSchedules"],
|
|
2662
|
+
message: "a budget schedule raises a daily budget, and Meta needs that daily budget in the same change \u2014 pass dailyBudget (the current one, on an update)"
|
|
2663
|
+
});
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
var boostMoveSchema = z12.strictObject({
|
|
2667
|
+
id: z12.string().regex(NUMERIC_ID_REGEX, "a boost is named by its id from `baker ads meta high-demand-periods`"),
|
|
2668
|
+
time_start: z12.number().int().positive(),
|
|
2669
|
+
time_end: z12.number().int().positive(),
|
|
2670
|
+
increase: moneySchema.optional(),
|
|
2671
|
+
increasePercent: z12.number().int().min(1).max(800).optional()
|
|
2672
|
+
}).superRefine((move, ctx) => {
|
|
2673
|
+
if (move.increase !== void 0 && move.increasePercent !== void 0) {
|
|
2674
|
+
ctx.addIssue({
|
|
2675
|
+
code: "custom",
|
|
2676
|
+
path: ["increase"],
|
|
2677
|
+
message: "a boost raises the budget by an amount or by a percentage, not both \u2014 a move keeps the live boost's own"
|
|
2678
|
+
});
|
|
2679
|
+
}
|
|
2680
|
+
if (move.time_end - move.time_start < MIN_HIGH_DEMAND_PERIOD_MS) {
|
|
2681
|
+
ctx.addIssue({ code: "custom", path: ["time_end"], message: "a high-demand period lasts at least 3 hours" });
|
|
2682
|
+
}
|
|
2683
|
+
if (move.time_start % QUARTER_HOUR_MS !== 0 || move.time_end % QUARTER_HOUR_MS !== 0) {
|
|
2684
|
+
ctx.addIssue({
|
|
2685
|
+
code: "custom",
|
|
2686
|
+
path: ["time_start"],
|
|
2687
|
+
message: "Meta starts and ends a high-demand period on a quarter hour (:00, :15, :30 or :45)"
|
|
2688
|
+
});
|
|
2689
|
+
}
|
|
2690
|
+
});
|
|
2691
|
+
var boostEditFields = {
|
|
2692
|
+
removeBoosts: z12.array(z12.string().regex(NUMERIC_ID_REGEX, "a boost is named by its id from `baker ads meta high-demand-periods`")).min(1).max(META_LIMITS.adSet.budgetSchedulesMax).optional(),
|
|
2693
|
+
moveBoosts: z12.array(boostMoveSchema).min(1).max(META_LIMITS.adSet.budgetSchedulesMax).optional()
|
|
2694
|
+
};
|
|
2695
|
+
function validateBoostEdits(p, ctx) {
|
|
2696
|
+
const removed = new Set(p.removeBoosts ?? []);
|
|
2697
|
+
for (const move of p.moveBoosts ?? []) {
|
|
2698
|
+
if (removed.has(move.id)) {
|
|
2699
|
+
ctx.addIssue({
|
|
2700
|
+
code: "custom",
|
|
2701
|
+
path: ["moveBoosts"],
|
|
2702
|
+
message: `boost ${move.id} is both moved and removed \u2014 pick one`
|
|
2703
|
+
});
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
if (p.clear?.includes("budgetSchedules") && (p.removeBoosts || p.moveBoosts)) {
|
|
2707
|
+
ctx.addIssue({
|
|
2708
|
+
code: "custom",
|
|
2709
|
+
path: ["clear"],
|
|
2710
|
+
message: "clearing every boost already removes the ones named \u2014 drop removeBoosts/moveBoosts or the clear"
|
|
2711
|
+
});
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
var specialAdCategoriesSchema = z12.array(z12.enum(SPECIAL_AD_CATEGORIES)).default(["NONE"]);
|
|
2715
|
+
var campaignCreateSchema = z12.strictObject({
|
|
2716
|
+
name: z12.string().min(1).max(META_LIMITS.campaign.nameMax),
|
|
2717
|
+
objective: z12.enum(OBJECTIVES),
|
|
2718
|
+
status: stageableStatusSchema.default("PAUSED"),
|
|
2719
|
+
special_ad_categories: specialAdCategoriesSchema,
|
|
2720
|
+
special_ad_category_country: z12.array(z12.string().length(2)).optional(),
|
|
2721
|
+
buying_type: z12.enum(BUYING_TYPES).default("AUCTION"),
|
|
2722
|
+
bid_strategy: z12.enum(BID_STRATEGIES).optional(),
|
|
2723
|
+
/** Campaign Budget Optimization (Advantage campaign budget) — mutually exclusive with ad-set budgets. */
|
|
2724
|
+
dailyBudget: moneySchema.optional(),
|
|
2725
|
+
lifetimeBudget: moneySchema.optional(),
|
|
2726
|
+
spendCap: moneySchema.optional(),
|
|
2727
|
+
/**
|
|
2728
|
+
* Ad-set budget sharing: lets ad sets lend up to 20% of their budget to each
|
|
2729
|
+
* other. Meta requires an explicit true/false on any campaign that carries no
|
|
2730
|
+
* campaign budget (subcode 4834011); Baker sends false unless asked, because
|
|
2731
|
+
* turning it on moves real spend between ad sets nobody asked to move.
|
|
2732
|
+
*/
|
|
2733
|
+
adSetBudgetSharing: z12.boolean().optional(),
|
|
2734
|
+
budgetSchedules: budgetSchedulesSchema.optional(),
|
|
2735
|
+
start_time: z12.number().int().positive().optional(),
|
|
2736
|
+
stop_time: z12.number().int().positive().optional(),
|
|
2737
|
+
/**
|
|
2738
|
+
* Advantage+ catalog ads: the catalog the campaign sells from. Meta fixes it
|
|
2739
|
+
* at creation; each ad set then names one of its product sets.
|
|
2740
|
+
*/
|
|
2741
|
+
promoted_object: z12.strictObject({ product_catalog_id: parentRefSchema }).optional(),
|
|
2742
|
+
...createLabelFields
|
|
2743
|
+
}).superRefine((p, ctx) => {
|
|
2744
|
+
if (p.dailyBudget && p.lifetimeBudget) {
|
|
2745
|
+
ctx.addIssue({ code: "custom", path: ["dailyBudget"], message: "set only one of dailyBudget or lifetimeBudget" });
|
|
2746
|
+
}
|
|
2747
|
+
validateDailyBudgetFloor(p.dailyBudget, ctx, ["dailyBudget", "amount"]);
|
|
2748
|
+
validateBudgetSchedules(p, ctx);
|
|
2749
|
+
if (p.stop_time && p.start_time && p.stop_time <= p.start_time) {
|
|
2750
|
+
ctx.addIssue({ code: "custom", path: ["stop_time"], message: "stop_time must be after start_time" });
|
|
2751
|
+
}
|
|
2752
|
+
const needsCountry = p.special_ad_categories.some((c) => SPECIAL_CATEGORIES_REQUIRING_COUNTRY.includes(c));
|
|
2753
|
+
if (needsCountry && !p.special_ad_category_country?.length) {
|
|
2754
|
+
ctx.addIssue({
|
|
2755
|
+
code: "custom",
|
|
2756
|
+
path: ["special_ad_category_country"],
|
|
2757
|
+
message: "ISSUES_ELECTIONS_POLITICS campaigns need special_ad_category_country \u2014 pass the 2-letter country code(s) the ads run in"
|
|
2758
|
+
});
|
|
2759
|
+
}
|
|
2760
|
+
const euCountries = (p.special_ad_category_country ?? []).filter((c) => EU_DSA_COUNTRIES.has(c.toUpperCase()));
|
|
2761
|
+
if (needsCountry && euCountries.length > 0) {
|
|
2762
|
+
ctx.addIssue({
|
|
2763
|
+
code: "custom",
|
|
2764
|
+
path: ["special_ad_category_country"],
|
|
2765
|
+
message: `Meta no longer runs ads about social issues, elections or politics in the EU (since 6 October 2025), so it refuses this campaign for ${euCountries.join(", ")} \u2014 drop the EU countries, or run these ads outside Meta`
|
|
2766
|
+
});
|
|
2767
|
+
}
|
|
2768
|
+
});
|
|
2769
|
+
var AD_SET_CLEARABLE_CONTROLS = [
|
|
2770
|
+
"dailyMinSpendTarget",
|
|
2771
|
+
"dailySpendCap",
|
|
2772
|
+
"lifetimeMinSpendTarget",
|
|
2773
|
+
"lifetimeSpendCap",
|
|
2774
|
+
"adset_schedule",
|
|
2775
|
+
"budgetSchedules"
|
|
2776
|
+
];
|
|
2777
|
+
var CAMPAIGN_CLEARABLE_CONTROLS = ["budgetSchedules"];
|
|
2778
|
+
function validateClear(p, ctx) {
|
|
2779
|
+
for (const field of p.clear ?? []) {
|
|
2780
|
+
if (p[field] !== void 0) {
|
|
2781
|
+
ctx.addIssue({ code: "custom", path: ["clear"], message: `${field} is both set and cleared \u2014 pick one` });
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
var campaignUpdateSchema = z12.strictObject({
|
|
2786
|
+
name: z12.string().min(1).max(META_LIMITS.campaign.nameMax).optional(),
|
|
2787
|
+
status: updateStatusSchema.optional(),
|
|
2788
|
+
bid_strategy: z12.enum(BID_STRATEGIES).optional(),
|
|
2789
|
+
dailyBudget: moneySchema.optional(),
|
|
2790
|
+
lifetimeBudget: moneySchema.optional(),
|
|
2791
|
+
spendCap: moneySchema.optional(),
|
|
2792
|
+
budgetSchedules: budgetSchedulesSchema.optional(),
|
|
2793
|
+
start_time: z12.number().int().positive().optional(),
|
|
2794
|
+
stop_time: z12.number().int().positive().optional(),
|
|
2795
|
+
/** Budget boosts to take away — every one the campaign had when this was staged. */
|
|
2796
|
+
clear: z12.array(z12.enum(CAMPAIGN_CLEARABLE_CONTROLS)).min(1).optional(),
|
|
2797
|
+
...boostEditFields,
|
|
2798
|
+
...labelFields
|
|
2799
|
+
}).superRefine((p, ctx) => {
|
|
2800
|
+
if (!Object.values(p).some((val) => val !== void 0)) {
|
|
2801
|
+
ctx.addIssue({ code: "custom", message: "update needs at least one field" });
|
|
2802
|
+
}
|
|
2803
|
+
if (p.dailyBudget && p.lifetimeBudget) {
|
|
2804
|
+
ctx.addIssue({ code: "custom", path: ["dailyBudget"], message: "set only one of dailyBudget or lifetimeBudget" });
|
|
2805
|
+
}
|
|
2806
|
+
validateDailyBudgetFloor(p.dailyBudget, ctx, ["dailyBudget", "amount"]);
|
|
2807
|
+
validateBudgetSchedules(p, ctx);
|
|
2808
|
+
validateClear(p, ctx);
|
|
2809
|
+
validateBoostEdits(p, ctx);
|
|
2810
|
+
});
|
|
2811
|
+
var promotedObjectFields = {
|
|
2812
|
+
page_id: parentRefSchema.optional(),
|
|
2813
|
+
pixel_id: z12.string().regex(NUMERIC_ID_REGEX).optional(),
|
|
2814
|
+
custom_event_type: z12.enum(CUSTOM_EVENT_TYPES).optional(),
|
|
2815
|
+
/**
|
|
2816
|
+
* A Custom Conversion to optimize toward, instead of a raw pixel event.
|
|
2817
|
+
* Named alone by the caller: the stage reads the conversion off Meta and
|
|
2818
|
+
* fills `pixel_id`, `custom_event_type` and `pixel_rule` from it, so the
|
|
2819
|
+
* staged payload matches what Ads Manager stores and the review shows the
|
|
2820
|
+
* event the ad set will actually chase.
|
|
2821
|
+
*/
|
|
2822
|
+
custom_conversion_id: z12.union([z12.string().regex(NUMERIC_ID_REGEX), tempRefSchema3]).optional(),
|
|
2823
|
+
/** The custom conversion's own filter, filled by the stage. Never written by hand. */
|
|
2824
|
+
pixel_rule: z12.string().optional(),
|
|
2825
|
+
application_id: z12.string().regex(NUMERIC_ID_REGEX).optional(),
|
|
2826
|
+
object_store_url: z12.string().url().optional(),
|
|
2827
|
+
product_catalog_id: parentRefSchema.optional(),
|
|
2828
|
+
/** A product set's id, or the meta_temp_* ref of one this chat creates. */
|
|
2829
|
+
product_set_id: parentRefSchema.optional(),
|
|
2830
|
+
whatsapp_phone_number: z12.string().optional(),
|
|
2831
|
+
offline_conversion_data_set_id: z12.string().regex(NUMERIC_ID_REGEX).optional()
|
|
2832
|
+
};
|
|
2833
|
+
var promotedObjectSchema = z12.strictObject(promotedObjectFields, {
|
|
2834
|
+
error: (issue) => issue.code === "unrecognized_keys" ? `promoted_object does not take ${issue.keys.map((key) => `"${key}"`).join(", ")} \u2014 it takes ${Object.keys(promotedObjectFields).join(", ")}` : void 0
|
|
2835
|
+
});
|
|
2836
|
+
var attributionSpecSchema = z12.array(
|
|
2837
|
+
z12.strictObject({
|
|
2838
|
+
event_type: z12.enum(ATTRIBUTION_EVENT_TYPES),
|
|
2839
|
+
window_days: z12.union([z12.literal(1), z12.literal(7)])
|
|
2840
|
+
}).refine((w) => w.window_days <= ATTRIBUTION_MAX_WINDOW_DAYS[w.event_type], {
|
|
2841
|
+
error: "Meta counts a view or an engaged video view for 1 day only \u2014 window_days 1",
|
|
2842
|
+
path: ["window_days"]
|
|
2843
|
+
})
|
|
2844
|
+
);
|
|
2845
|
+
var minRoasSchema = z12.number().min(META_LIMITS.adSet.minRoasMin, `minRoas must be at least ${META_LIMITS.adSet.minRoasMin} (Meta's floor)`).max(META_LIMITS.adSet.minRoasMax, `minRoas must be at most ${META_LIMITS.adSet.minRoasMax} (Meta's ceiling)`).refine(
|
|
2846
|
+
(roas) => Math.round(roas * ROAS_FLOOR_SCALE) / ROAS_FLOOR_SCALE === roas,
|
|
2847
|
+
"Meta keeps a minimum ROAS to 4 decimals (2.3333, not 2.33333)"
|
|
2848
|
+
);
|
|
2849
|
+
var frequencyCapSchema = z12.strictObject({
|
|
2850
|
+
event: z12.enum(FREQUENCY_CAP_EVENTS).default("IMPRESSIONS"),
|
|
2851
|
+
interval_days: z12.number().int().min(1).max(META_LIMITS.adSet.frequencyIntervalDaysMax),
|
|
2852
|
+
max_frequency: z12.number().int().min(1).max(META_LIMITS.adSet.frequencyMaxImpressionsMax),
|
|
2853
|
+
type: z12.enum(FREQUENCY_CAP_TYPES).optional()
|
|
2854
|
+
});
|
|
2855
|
+
var spendLimitFields = {
|
|
2856
|
+
dailyMinSpendTarget: moneySchema.optional(),
|
|
2857
|
+
dailySpendCap: moneySchema.optional(),
|
|
2858
|
+
lifetimeMinSpendTarget: moneySchema.optional(),
|
|
2859
|
+
lifetimeSpendCap: moneySchema.optional()
|
|
2860
|
+
};
|
|
2861
|
+
function minAboveCap(min, cap) {
|
|
2862
|
+
return min !== void 0 && cap !== void 0 && Number(min.amount) > Number(cap.amount);
|
|
2863
|
+
}
|
|
2864
|
+
function validateSpendLimits(p, ctx) {
|
|
2865
|
+
const daily = p.dailyMinSpendTarget !== void 0 || p.dailySpendCap !== void 0;
|
|
2866
|
+
const lifetime = p.lifetimeMinSpendTarget !== void 0 || p.lifetimeSpendCap !== void 0;
|
|
2867
|
+
if ((daily || lifetime) && (p.dailyBudget || p.lifetimeBudget)) {
|
|
2868
|
+
ctx.addIssue({
|
|
2869
|
+
code: "custom",
|
|
2870
|
+
path: [daily ? "dailySpendCap" : "lifetimeSpendCap"],
|
|
2871
|
+
message: "spend limits share out a campaign budget \u2014 an ad set with its own budget takes none; drop the budget or the limits"
|
|
2872
|
+
});
|
|
2873
|
+
}
|
|
2874
|
+
if (daily && lifetime) {
|
|
2875
|
+
ctx.addIssue({
|
|
2876
|
+
code: "custom",
|
|
2877
|
+
path: ["lifetimeSpendCap"],
|
|
2878
|
+
message: "use daily limits under a daily campaign budget and lifetime limits under a lifetime one, never both"
|
|
2879
|
+
});
|
|
2880
|
+
}
|
|
2881
|
+
if (minAboveCap(p.dailyMinSpendTarget, p.dailySpendCap) || minAboveCap(p.lifetimeMinSpendTarget, p.lifetimeSpendCap)) {
|
|
2882
|
+
ctx.addIssue({
|
|
2883
|
+
code: "custom",
|
|
2884
|
+
path: [daily ? "dailyMinSpendTarget" : "lifetimeMinSpendTarget"],
|
|
2885
|
+
message: "the minimum spend can't be higher than the spend cap"
|
|
2886
|
+
});
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
var MINUTES_PER_DAY2 = 24 * 60;
|
|
2890
|
+
var scheduleBlockSchema = z12.strictObject({
|
|
2891
|
+
start_minute: z12.number().int().min(0).max(MINUTES_PER_DAY2),
|
|
2892
|
+
end_minute: z12.number().int().min(0).max(MINUTES_PER_DAY2),
|
|
2893
|
+
days: z12.array(z12.number().int().min(0).max(6)).min(1),
|
|
2894
|
+
timezone_type: z12.enum(SCHEDULE_TIMEZONE_TYPES).optional()
|
|
2895
|
+
}).superRefine((block, ctx) => {
|
|
2896
|
+
if (block.start_minute % 60 !== 0 || block.end_minute % 60 !== 0) {
|
|
2897
|
+
ctx.addIssue({
|
|
2898
|
+
code: "custom",
|
|
2899
|
+
path: ["start_minute"],
|
|
2900
|
+
message: "Meta schedules whole hours \u2014 start_minute and end_minute must be multiples of 60 (540 = 9:00)"
|
|
2901
|
+
});
|
|
2902
|
+
}
|
|
2903
|
+
if (block.end_minute - block.start_minute < 60) {
|
|
2904
|
+
ctx.addIssue({
|
|
2905
|
+
code: "custom",
|
|
2906
|
+
path: ["end_minute"],
|
|
2907
|
+
message: "a schedule block must end at least one hour after it starts"
|
|
2908
|
+
});
|
|
2909
|
+
}
|
|
2910
|
+
});
|
|
2911
|
+
var adSetFields = {
|
|
2912
|
+
name: z12.string().min(1).max(META_LIMITS.adSet.nameMax),
|
|
2913
|
+
campaign_id: parentRefSchema,
|
|
2914
|
+
status: stageableStatusSchema.default("PAUSED"),
|
|
2915
|
+
dailyBudget: moneySchema.optional(),
|
|
2916
|
+
lifetimeBudget: moneySchema.optional(),
|
|
2917
|
+
bidAmount: moneySchema.optional(),
|
|
2918
|
+
bid_strategy: z12.enum(BID_STRATEGIES).optional(),
|
|
2919
|
+
minRoas: minRoasSchema.optional(),
|
|
2920
|
+
frequency_control_specs: z12.array(frequencyCapSchema).min(1).optional(),
|
|
2921
|
+
adset_schedule: z12.array(scheduleBlockSchema).min(1).optional(),
|
|
2922
|
+
pacing: z12.enum(PACING_TYPES).optional(),
|
|
2923
|
+
...spendLimitFields,
|
|
2924
|
+
/**
|
|
2925
|
+
* Create-only on an ad set. Meta refuses `budget_schedule_specs` on an ad set
|
|
2926
|
+
* update even beside its daily budget ("Ad set must have daily budget to
|
|
2927
|
+
* support budget schedule", subcode 3858199 — live on v25.0, 2026-10-14);
|
|
2928
|
+
* only the `/budget_schedules` edge adds one later, and that edge ignores
|
|
2929
|
+
* `validate_only` (a dry run created a real period), so Baker does not use it.
|
|
2930
|
+
*/
|
|
2931
|
+
budgetSchedules: budgetSchedulesSchema.optional(),
|
|
2932
|
+
/**
|
|
2933
|
+
* A Dynamic Creative ad set: its one ad mixes the images, videos and texts of
|
|
2934
|
+
* an asset-feed (`dynamic`) creative. Create-only — Meta fixes it for the ad
|
|
2935
|
+
* set's life — and such an ad set holds exactly one ad (subcode 1885553).
|
|
2936
|
+
*/
|
|
2937
|
+
is_dynamic_creative: z12.boolean().optional(),
|
|
2938
|
+
/**
|
|
2939
|
+
* Reach and frequency: the reservation this ad set is bought on (its id, or
|
|
2940
|
+
* the meta_temp_* ref of a `reachFrequencyReservation.create` in this chat).
|
|
2941
|
+
* Its campaign is `buying_type: RESERVED` (`reachFrequency.ts`).
|
|
2942
|
+
*/
|
|
2943
|
+
rf_prediction_id: parentRefSchema.optional(),
|
|
2944
|
+
billing_event: z12.enum(BILLING_EVENTS),
|
|
2945
|
+
optimization_goal: z12.enum(OPTIMIZATION_GOALS),
|
|
2946
|
+
destination_type: z12.enum(DESTINATION_TYPES).optional(),
|
|
2947
|
+
promoted_object: promotedObjectSchema.optional(),
|
|
2948
|
+
attribution_spec: attributionSpecSchema.optional(),
|
|
2949
|
+
start_time: z12.number().int().positive().optional(),
|
|
2950
|
+
end_time: z12.number().int().positive().optional(),
|
|
2951
|
+
targeting: metaTargetingSchema,
|
|
2952
|
+
/**
|
|
2953
|
+
* The live ad set this one was copied from, verbatim, by `duplicate`. Baker
|
|
2954
|
+
* only — `adSetCommonBody` never sends it to Meta. Set exclusively when the
|
|
2955
|
+
* targeting IS the source's targeting, because that is what it licenses:
|
|
2956
|
+
* placement validation is skipped for it (see `validateAdSetPlacements`).
|
|
2957
|
+
*/
|
|
2958
|
+
clonedFrom: z12.string().regex(NUMERIC_ID_REGEX).optional(),
|
|
2959
|
+
/** EU Digital Services Act: who benefits from / pays for the ad. Auto-filled from the account for EU geo when omitted. */
|
|
2960
|
+
dsa_beneficiary: z12.string().min(1).max(100).optional(),
|
|
2961
|
+
dsa_payor: z12.string().min(1).max(100).optional(),
|
|
2962
|
+
/** Advantage+ placements with limited spend — Sales and Leads campaigns only (`adSetCompliance.ts`). */
|
|
2963
|
+
placement_soft_opt_out: placementSoftOptOutSchema.optional(),
|
|
2964
|
+
...regionalRegulationFields,
|
|
2965
|
+
...createLabelFields
|
|
2966
|
+
};
|
|
2967
|
+
var MIN_ROAS = "LOWEST_COST_WITH_MIN_ROAS";
|
|
2968
|
+
function validateMinRoas(p, ctx, requireFloor) {
|
|
2969
|
+
if (p.minRoas !== void 0 && p.bid_strategy !== void 0 && p.bid_strategy !== MIN_ROAS) {
|
|
2970
|
+
ctx.addIssue({
|
|
2971
|
+
code: "custom",
|
|
2972
|
+
path: ["minRoas"],
|
|
2973
|
+
message: `minRoas only works with bid_strategy ${MIN_ROAS} \u2014 Meta refuses a ROAS floor beside ${p.bid_strategy}`
|
|
2974
|
+
});
|
|
2975
|
+
}
|
|
2976
|
+
if (requireFloor && p.bid_strategy === MIN_ROAS && p.minRoas === void 0) {
|
|
2977
|
+
ctx.addIssue({
|
|
2978
|
+
code: "custom",
|
|
2979
|
+
path: ["minRoas"],
|
|
2980
|
+
message: `${MIN_ROAS} needs minRoas \u2014 the return every euro spent must average, e.g. 2.5 for 250% (--min-roas 2.5)`
|
|
2981
|
+
});
|
|
2982
|
+
}
|
|
2983
|
+
}
|
|
2984
|
+
var ONE_DAY_CLICK_GOALS = /* @__PURE__ */ new Set(["LINK_CLICKS", "LANDING_PAGE_VIEWS", "REACH"]);
|
|
2985
|
+
function validateAttribution(p, ctx) {
|
|
2986
|
+
const goal = p.optimization_goal;
|
|
2987
|
+
if (!p.attribution_spec || !goal || !ONE_DAY_CLICK_GOALS.has(goal)) return;
|
|
2988
|
+
const oneDayClick = p.attribution_spec.every((w) => w.event_type === "CLICK_THROUGH" && w.window_days === 1);
|
|
2989
|
+
if (!oneDayClick) {
|
|
2990
|
+
ctx.addIssue({
|
|
2991
|
+
code: "custom",
|
|
2992
|
+
path: ["attribution_spec"],
|
|
2993
|
+
message: `Meta attributes a ${goal} ad set on a 1-day click only \u2014 use --attribution 1d_click, or leave it out`
|
|
2994
|
+
});
|
|
2995
|
+
}
|
|
2996
|
+
}
|
|
2997
|
+
function validateFrequencyCap(p, ctx) {
|
|
2998
|
+
const goal = p.optimization_goal;
|
|
2999
|
+
if (p.frequency_control_specs && goal && !OPTIMIZATION_GOALS_WITH_FREQUENCY_CAP.includes(goal)) {
|
|
3000
|
+
ctx.addIssue({
|
|
3001
|
+
code: "custom",
|
|
3002
|
+
path: ["frequency_control_specs"],
|
|
3003
|
+
message: `Meta caps frequency only on ${OPTIMIZATION_GOALS_WITH_FREQUENCY_CAP.join(" or ")} ad sets, not ${goal}`
|
|
3004
|
+
});
|
|
3005
|
+
}
|
|
3006
|
+
if (!p.rf_prediction_id && (p.frequency_control_specs ?? []).some((cap) => cap.type === "TARGET")) {
|
|
3007
|
+
ctx.addIssue({
|
|
3008
|
+
code: "custom",
|
|
3009
|
+
path: ["frequency_control_specs"],
|
|
3010
|
+
message: "Meta takes a TARGET frequency only on a reach-and-frequency ad set with a prediction attached (subcode 3858652) \u2014 use a CAP (leave type out)"
|
|
3011
|
+
});
|
|
3012
|
+
}
|
|
3013
|
+
const events = (p.frequency_control_specs ?? []).map((cap) => cap.event);
|
|
3014
|
+
if (events.includes("VIDEO_VIEWS")) {
|
|
3015
|
+
ctx.addIssue({
|
|
3016
|
+
code: "custom",
|
|
3017
|
+
path: ["frequency_control_specs"],
|
|
3018
|
+
message: "Meta fails a VIDEO_VIEWS frequency cap with an unknown error \u2014 count VIDEO_VIEWS_2S or VIDEO_VIEWS_15S, or IMPRESSIONS"
|
|
3019
|
+
});
|
|
3020
|
+
}
|
|
3021
|
+
if (events.includes("VIDEO_VIEWS_6S") && goal && goal !== "REACH") {
|
|
3022
|
+
ctx.addIssue({
|
|
3023
|
+
code: "custom",
|
|
3024
|
+
path: ["frequency_control_specs"],
|
|
3025
|
+
message: `Meta counts VIDEO_VIEWS_6S caps on REACH ad sets only, not ${goal} \u2014 use VIDEO_VIEWS_2S or VIDEO_VIEWS_15S`
|
|
3026
|
+
});
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
3029
|
+
function validatePacing(p, ctx) {
|
|
3030
|
+
if (p.adset_schedule && p.pacing !== void 0 && p.pacing !== "day_parting") {
|
|
3031
|
+
ctx.addIssue({
|
|
3032
|
+
code: "custom",
|
|
3033
|
+
path: ["pacing"],
|
|
3034
|
+
message: `an ad schedule runs only with day_parting pacing, not ${p.pacing} \u2014 drop pacing and Baker sends day_parting`
|
|
3035
|
+
});
|
|
3036
|
+
}
|
|
3037
|
+
if (p.adset_schedule && p.dailyBudget) {
|
|
3038
|
+
ctx.addIssue({
|
|
3039
|
+
code: "custom",
|
|
3040
|
+
path: ["adset_schedule"],
|
|
3041
|
+
message: "Meta runs an ad schedule only on a lifetime budget \u2014 use lifetimeBudget with an end_time"
|
|
3042
|
+
});
|
|
3043
|
+
}
|
|
3044
|
+
if (p.pacing === "day_parting" && !p.adset_schedule) {
|
|
3045
|
+
ctx.addIssue({
|
|
3046
|
+
code: "custom",
|
|
3047
|
+
path: ["pacing"],
|
|
3048
|
+
message: "day_parting pacing needs adset_schedule \u2014 the hours the ads may run"
|
|
3049
|
+
});
|
|
3050
|
+
}
|
|
3051
|
+
if (p.pacing === "no_pacing" && p.bid_strategy !== void 0 && p.bid_strategy !== "LOWEST_COST_WITH_BID_CAP") {
|
|
3052
|
+
ctx.addIssue({
|
|
3053
|
+
code: "custom",
|
|
3054
|
+
path: ["pacing"],
|
|
3055
|
+
message: `accelerated delivery (no_pacing) works only with LOWEST_COST_WITH_BID_CAP, not ${p.bid_strategy}`
|
|
3056
|
+
});
|
|
3057
|
+
}
|
|
3058
|
+
}
|
|
3059
|
+
function validateBidStrategy(p, ctx) {
|
|
3060
|
+
const strategy = p.bid_strategy;
|
|
3061
|
+
if (!strategy) {
|
|
3062
|
+
return;
|
|
3063
|
+
}
|
|
3064
|
+
if (BID_STRATEGIES_REQUIRING_AMOUNT.includes(strategy) && !p.bidAmount) {
|
|
3065
|
+
ctx.addIssue({ code: "custom", path: ["bidAmount"], message: `${strategy} requires a bidAmount` });
|
|
3066
|
+
}
|
|
3067
|
+
if (BID_STRATEGIES_FORBIDDING_AMOUNT.includes(strategy) && p.bidAmount) {
|
|
3068
|
+
ctx.addIssue({
|
|
3069
|
+
code: "custom",
|
|
3070
|
+
path: ["bidAmount"],
|
|
3071
|
+
message: `${strategy} must not set a bidAmount \u2014 Meta rejects one (it bids automatically)`
|
|
3072
|
+
});
|
|
3073
|
+
}
|
|
3074
|
+
if (strategy === "COST_CAP" && p.billing_event && p.billing_event !== "IMPRESSIONS") {
|
|
3075
|
+
ctx.addIssue({
|
|
3076
|
+
code: "custom",
|
|
3077
|
+
path: ["billing_event"],
|
|
3078
|
+
message: "COST_CAP requires billing_event IMPRESSIONS"
|
|
3079
|
+
});
|
|
3080
|
+
}
|
|
3081
|
+
if (strategy === "LOWEST_COST_WITH_MIN_ROAS" && p.optimization_goal && p.optimization_goal !== "VALUE") {
|
|
3082
|
+
ctx.addIssue({
|
|
3083
|
+
code: "custom",
|
|
3084
|
+
path: ["bid_strategy"],
|
|
3085
|
+
message: "LOWEST_COST_WITH_MIN_ROAS requires optimization_goal VALUE"
|
|
3086
|
+
});
|
|
3087
|
+
}
|
|
3088
|
+
}
|
|
3089
|
+
function validateAdSetBudgetAndBid(p, ctx) {
|
|
3090
|
+
if (p.dailyBudget && p.lifetimeBudget) {
|
|
3091
|
+
ctx.addIssue({ code: "custom", path: ["dailyBudget"], message: "set only one of dailyBudget or lifetimeBudget" });
|
|
3092
|
+
}
|
|
3093
|
+
validateDailyBudgetFloor(p.dailyBudget, ctx, ["dailyBudget", "amount"]);
|
|
3094
|
+
if (p.lifetimeBudget && !p.end_time) {
|
|
3095
|
+
ctx.addIssue({ code: "custom", path: ["end_time"], message: "lifetimeBudget ad sets need an end_time" });
|
|
3096
|
+
}
|
|
3097
|
+
validateBidStrategy(p, ctx);
|
|
3098
|
+
if (p.end_time && p.start_time && p.end_time <= p.start_time) {
|
|
3099
|
+
ctx.addIssue({ code: "custom", path: ["end_time"], message: "end_time must be after start_time" });
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3102
|
+
function validateAdSetPromotedObject(p, ctx) {
|
|
3103
|
+
const goal = p.optimization_goal;
|
|
3104
|
+
if (!goal) {
|
|
3105
|
+
return;
|
|
3106
|
+
}
|
|
3107
|
+
const po = p.promoted_object;
|
|
3108
|
+
const namesConversion = Boolean(po?.custom_conversion_id);
|
|
3109
|
+
const hasPixel = Boolean(po?.pixel_id) || namesConversion || Boolean(po?.product_set_id);
|
|
3110
|
+
const hasEventType = Boolean(po?.custom_event_type) || namesConversion;
|
|
3111
|
+
if (OPTIMIZATION_GOALS_REQUIRING_PAGE.includes(goal) && !po?.page_id) {
|
|
3112
|
+
ctx.addIssue({
|
|
3113
|
+
code: "custom",
|
|
3114
|
+
path: ["promoted_object", "page_id"],
|
|
3115
|
+
message: `optimization_goal ${goal} needs promoted_object.page_id \u2014 pass --page-id with the Facebook Page the leads/likes belong to`
|
|
3116
|
+
});
|
|
3117
|
+
}
|
|
3118
|
+
if (OPTIMIZATION_GOALS_REQUIRING_PIXEL.includes(goal) && !hasPixel) {
|
|
3119
|
+
ctx.addIssue({
|
|
3120
|
+
code: "custom",
|
|
3121
|
+
path: ["promoted_object", "pixel_id"],
|
|
3122
|
+
message: `optimization_goal ${goal} needs promoted_object.pixel_id \u2014 pass --pixel-id with the conversion pixel, or --custom-conversion-id to optimize toward a custom conversion`
|
|
3123
|
+
});
|
|
3124
|
+
}
|
|
3125
|
+
if (OPTIMIZATION_GOALS_REQUIRING_CONVERSION_SOURCE.includes(goal) && !hasPixel && !po?.application_id) {
|
|
3126
|
+
ctx.addIssue({
|
|
3127
|
+
code: "custom",
|
|
3128
|
+
path: ["promoted_object", "pixel_id"],
|
|
3129
|
+
message: `optimization_goal ${goal} needs a conversion source \u2014 pass promoted_object.pixel_id (web value optimization) or promoted_object.application_id (app value optimization)`
|
|
3130
|
+
});
|
|
3131
|
+
}
|
|
3132
|
+
if ((OPTIMIZATION_GOALS_REQUIRING_PIXEL.includes(goal) || OPTIMIZATION_GOALS_REQUIRING_CONVERSION_SOURCE.includes(goal)) && !hasEventType) {
|
|
3133
|
+
ctx.addIssue({
|
|
3134
|
+
code: "custom",
|
|
3135
|
+
path: ["promoted_object", "custom_event_type"],
|
|
3136
|
+
message: `optimization_goal ${goal} needs promoted_object.custom_event_type \u2014 pass --custom-event-type (e.g. PURCHASE, LEAD), or --custom-conversion-id to optimize toward a custom conversion`
|
|
3137
|
+
});
|
|
3138
|
+
}
|
|
3139
|
+
if (OPTIMIZATION_GOALS_REQUIRING_APP.includes(goal) && !po?.application_id) {
|
|
3140
|
+
ctx.addIssue({
|
|
3141
|
+
code: "custom",
|
|
3142
|
+
path: ["promoted_object", "application_id"],
|
|
3143
|
+
message: `optimization_goal ${goal} needs promoted_object.application_id`
|
|
3144
|
+
});
|
|
3145
|
+
}
|
|
3146
|
+
}
|
|
3147
|
+
function validateMessagingAdSet(p, ctx) {
|
|
3148
|
+
const opensConversation = MESSAGING_DESTINATION_TYPES.some((type) => type === p.destination_type);
|
|
3149
|
+
if (opensConversation && !p.promoted_object?.page_id) {
|
|
3150
|
+
ctx.addIssue({
|
|
3151
|
+
code: "custom",
|
|
3152
|
+
path: ["promoted_object", "page_id"],
|
|
3153
|
+
message: `destination_type ${p.destination_type} opens a conversation with a Page \u2014 pass promoted_object.page_id (--page-id) with that Page`
|
|
3154
|
+
});
|
|
3155
|
+
}
|
|
3156
|
+
if (p.optimization_goal === "CONVERSATIONS" && p.destination_type !== void 0 && !opensConversation) {
|
|
3157
|
+
ctx.addIssue({
|
|
3158
|
+
code: "custom",
|
|
3159
|
+
path: ["destination_type"],
|
|
3160
|
+
message: `optimization_goal CONVERSATIONS needs destination_type MESSENGER, WHATSAPP, INSTAGRAM_DIRECT or a MESSAGING_* mix \u2014 ${p.destination_type} opens no conversation`
|
|
3161
|
+
});
|
|
3162
|
+
}
|
|
3163
|
+
if (p.optimization_goal === "CONVERSATIONS" && p.destination_type === void 0) {
|
|
3164
|
+
ctx.addIssue({
|
|
3165
|
+
code: "custom",
|
|
3166
|
+
path: ["destination_type"],
|
|
3167
|
+
message: "optimization_goal CONVERSATIONS needs destination_type MESSENGER, WHATSAPP, INSTAGRAM_DIRECT or a MESSAGING_* mix \u2014 where the conversation opens"
|
|
3168
|
+
});
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
function validateAdvantageAudience(p, ctx) {
|
|
3172
|
+
const t = p.targeting;
|
|
3173
|
+
if (t && targetingHardLimitsDemographics(t) && advantageAudienceSetting(t) === 1) {
|
|
3174
|
+
ctx.addIssue({
|
|
3175
|
+
code: "custom",
|
|
3176
|
+
path: ["targeting", "targeting_automation", "advantage_audience"],
|
|
3177
|
+
message: "Advantage+ Audience (advantage_audience: 1) treats age and gender as suggestions, so a hard age/gender limit is rejected \u2014 set advantage_audience: 0 to enforce the limit, or drop the age_max/age_min/gender restriction"
|
|
3178
|
+
});
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
function validateAdSetPlacements(p, ctx) {
|
|
3182
|
+
if (p.clonedFrom) {
|
|
3183
|
+
for (const issue of invalidPlacementValues(p.targeting)) {
|
|
3184
|
+
if (issue.kind === "retired") {
|
|
3185
|
+
ctx.addIssue({
|
|
3186
|
+
code: "custom",
|
|
3187
|
+
path: ["targeting", issue.field],
|
|
3188
|
+
message: `${issue.field} no longer accepts "${issue.value}". ${issue.fix} (${PLACEMENT_TARGETING_DOC})`
|
|
3189
|
+
});
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
return;
|
|
3193
|
+
}
|
|
3194
|
+
const missingFeed = positionsMissingRequiredFeed(p.targeting);
|
|
3195
|
+
if (missingFeed.length > 0) {
|
|
3196
|
+
ctx.addIssue({
|
|
3197
|
+
code: "custom",
|
|
3198
|
+
path: ["targeting", "facebook_positions"],
|
|
3199
|
+
message: `Meta requires "feed" alongside ${missingFeed.join(", ")} \u2014 add "feed" to facebook_positions or drop those positions (${PLACEMENT_TARGETING_DOC})`
|
|
3200
|
+
});
|
|
3201
|
+
}
|
|
3202
|
+
for (const issue of invalidPlacementValues(p.targeting)) {
|
|
3203
|
+
if (issue.kind === "unknown") {
|
|
3204
|
+
continue;
|
|
3205
|
+
}
|
|
3206
|
+
ctx.addIssue({
|
|
3207
|
+
code: "custom",
|
|
3208
|
+
path: ["targeting", issue.field],
|
|
3209
|
+
message: issue.kind === "retired" ? `${issue.field} no longer accepts "${issue.value}". ${issue.fix} (${PLACEMENT_TARGETING_DOC})` : `${issue.field} does not accept "${issue.value}". ${issue.fix} (${PLACEMENT_TARGETING_DOC})`
|
|
3210
|
+
});
|
|
3211
|
+
}
|
|
3212
|
+
for (const message of placementCoRequirementIssues(p.targeting)) {
|
|
3213
|
+
ctx.addIssue({ code: "custom", path: ["targeting"], message: `${message} (${PLACEMENT_TARGETING_DOC})` });
|
|
3214
|
+
}
|
|
3215
|
+
}
|
|
3216
|
+
function validateSoftOptOut(p, ctx, isCreate) {
|
|
3217
|
+
const issue = softOptOutPlacementIssue(
|
|
3218
|
+
p.placement_soft_opt_out,
|
|
3219
|
+
p.targeting,
|
|
3220
|
+
isCreate
|
|
3221
|
+
);
|
|
3222
|
+
if (issue) ctx.addIssue({ code: "custom", path: ["placement_soft_opt_out"], message: issue });
|
|
3223
|
+
}
|
|
3224
|
+
var adSetCreateSchema = z12.strictObject(adSetFields).superRefine((p, ctx) => {
|
|
3225
|
+
validateAdSetBudgetAndBid(p, ctx);
|
|
3226
|
+
validateMinRoas(p, ctx, true);
|
|
3227
|
+
validateFrequencyCap(p, ctx);
|
|
3228
|
+
validateAttribution(p, ctx);
|
|
3229
|
+
validatePacing(p, ctx);
|
|
3230
|
+
validateSpendLimits(p, ctx);
|
|
3231
|
+
validateBudgetSchedules(p, ctx);
|
|
3232
|
+
validateAdSetPromotedObject(p, ctx);
|
|
3233
|
+
validateMessagingAdSet(p, ctx);
|
|
3234
|
+
validateAdvantageAudience(p, ctx);
|
|
3235
|
+
validateAdSetPlacements(p, ctx);
|
|
3236
|
+
validateRegionalRegulation(p, ctx);
|
|
3237
|
+
validateSoftOptOut(p, ctx, true);
|
|
3238
|
+
if (!targetingHasGeoOrAudience(p.targeting)) {
|
|
3239
|
+
ctx.addIssue({
|
|
3240
|
+
code: "custom",
|
|
3241
|
+
path: ["targeting", "geo_locations"],
|
|
3242
|
+
message: 'targeting needs a location \u2014 set targeting.geo_locations (e.g. countries: ["US"]) or include a custom audience; Meta rejects an ad set with no geo'
|
|
3243
|
+
});
|
|
3244
|
+
}
|
|
3245
|
+
});
|
|
3246
|
+
var adSetUpdateSchema = z12.strictObject({
|
|
3247
|
+
name: adSetFields.name.optional(),
|
|
3248
|
+
status: updateStatusSchema.optional(),
|
|
3249
|
+
dailyBudget: moneySchema.optional(),
|
|
3250
|
+
lifetimeBudget: moneySchema.optional(),
|
|
3251
|
+
bidAmount: moneySchema.optional(),
|
|
3252
|
+
bid_strategy: z12.enum(BID_STRATEGIES).optional(),
|
|
3253
|
+
minRoas: minRoasSchema.optional(),
|
|
3254
|
+
frequency_control_specs: z12.array(frequencyCapSchema).min(1).optional(),
|
|
3255
|
+
adset_schedule: z12.array(scheduleBlockSchema).min(1).optional(),
|
|
3256
|
+
pacing: z12.enum(PACING_TYPES).optional(),
|
|
3257
|
+
...spendLimitFields,
|
|
3258
|
+
optimization_goal: z12.enum(OPTIMIZATION_GOALS).optional(),
|
|
3259
|
+
destination_type: z12.enum(DESTINATION_TYPES).optional(),
|
|
3260
|
+
promoted_object: promotedObjectSchema.optional(),
|
|
3261
|
+
attribution_spec: attributionSpecSchema.optional(),
|
|
3262
|
+
start_time: z12.number().int().positive().optional(),
|
|
3263
|
+
end_time: z12.number().int().positive().optional(),
|
|
3264
|
+
targeting: metaTargetingSchema.optional(),
|
|
3265
|
+
dsa_beneficiary: z12.string().min(1).max(100).optional(),
|
|
3266
|
+
dsa_payor: z12.string().min(1).max(100).optional(),
|
|
3267
|
+
placement_soft_opt_out: placementSoftOptOutSchema.optional(),
|
|
3268
|
+
...regionalRegulationFields,
|
|
3269
|
+
/**
|
|
3270
|
+
* Boosts ADDED to a live ad set. Meta refuses `budget_schedule_specs` in an
|
|
3271
|
+
* ad set update (subcode 3858199) but takes each period on the
|
|
3272
|
+
* `/{adset}/budget_schedules` edge without the budget restated (live on
|
|
3273
|
+
* v26.0, 2026-10-15), so the executor sends them there.
|
|
3274
|
+
*/
|
|
3275
|
+
budgetSchedules: budgetSchedulesSchema.optional(),
|
|
3276
|
+
/** Delivery controls to take away (`AD_SET_CLEARABLE_CONTROLS`). */
|
|
3277
|
+
clear: z12.array(z12.enum(AD_SET_CLEARABLE_CONTROLS)).min(1).optional(),
|
|
3278
|
+
...boostEditFields,
|
|
3279
|
+
...labelFields
|
|
3280
|
+
}).superRefine((p, ctx) => {
|
|
3281
|
+
if (!Object.values(p).some((val) => val !== void 0)) {
|
|
3282
|
+
ctx.addIssue({ code: "custom", message: "update needs at least one field" });
|
|
3283
|
+
}
|
|
3284
|
+
validateClear(p, ctx);
|
|
3285
|
+
validateBoostEdits(p, ctx);
|
|
3286
|
+
validateAdSetBudgetAndBid(p, ctx);
|
|
3287
|
+
validateMinRoas(p, ctx, false);
|
|
3288
|
+
validateFrequencyCap(p, ctx);
|
|
3289
|
+
validateAttribution(p, ctx);
|
|
3290
|
+
validatePacing(p, ctx);
|
|
3291
|
+
validateSpendLimits(p, ctx);
|
|
3292
|
+
if (p.optimization_goal) {
|
|
3293
|
+
validateAdSetPromotedObject(p, ctx);
|
|
3294
|
+
}
|
|
3295
|
+
if (p.destination_type) {
|
|
3296
|
+
validateMessagingAdSet(p, ctx);
|
|
3297
|
+
}
|
|
3298
|
+
validateAdvantageAudience(p, ctx);
|
|
3299
|
+
validateAdSetPlacements(p, ctx);
|
|
3300
|
+
validateRegionalRegulation(p, ctx);
|
|
3301
|
+
validateSoftOptOut(p, ctx, false);
|
|
3302
|
+
});
|
|
3303
|
+
var messageSchema = z12.string().min(1).max(META_LIMITS.creative.messageHardMax);
|
|
3304
|
+
var headlineSchema = z12.string().min(1).max(META_LIMITS.creative.headlineMax);
|
|
3305
|
+
var descriptionSchema = z12.string().min(1).max(META_LIMITS.creative.descriptionMax);
|
|
3306
|
+
var callToActionSchema = z12.strictObject({
|
|
3307
|
+
type: z12.enum(CTA_TYPES),
|
|
3308
|
+
/** Overrides the base link for the CTA button; defaults to the ad's link. */
|
|
3309
|
+
link: httpsUrlSchema5.optional(),
|
|
3310
|
+
/**
|
|
3311
|
+
* The instant form the button opens — Meta's `call_to_action.value.lead_gen_form_id`.
|
|
3312
|
+
* This is the ONLY place an instant form is named: the ad set says leads open
|
|
3313
|
+
* on the ad (`destination_type: ON_AD`), and the creative says which form.
|
|
3314
|
+
* Without it Meta refuses every ad in that ad set with subcode 3390001,
|
|
3315
|
+
* "Choose or create an instant form for your leads campaign".
|
|
3316
|
+
*/
|
|
3317
|
+
lead_gen_form_id: parentRefSchema.optional(),
|
|
3318
|
+
/** The app a click-to-message button opens a conversation in — `value.app_destination`. */
|
|
3319
|
+
app_destination: z12.enum(APP_DESTINATIONS).optional(),
|
|
3320
|
+
/**
|
|
3321
|
+
* App ads: the deep link the button opens in an installed app (`value.app_link`,
|
|
3322
|
+
* e.g. `myapp://product/12`). Meta refuses one without an app-store `link`
|
|
3323
|
+
* beside it (subcode 1885949, live on v25.0, 2026-10-15).
|
|
3324
|
+
*/
|
|
3325
|
+
app_link: z12.string().min(3).max(1e3).optional(),
|
|
3326
|
+
/**
|
|
3327
|
+
* App ads: the Meta app the button installs or opens (`value.application`).
|
|
3328
|
+
* Meta refuses one whose store does not match the button's link (subcode
|
|
3329
|
+
* 1885270, live on v25.0, 2026-10-15).
|
|
3330
|
+
*/
|
|
3331
|
+
application: z12.string().regex(NUMERIC_ID_REGEX).optional()
|
|
3332
|
+
}).superRefine((p, ctx) => {
|
|
3333
|
+
validateMessagingButton(p, ctx);
|
|
3334
|
+
if (p.lead_gen_form_id && !LEAD_FORM_CTA_TYPES.some((allowed) => allowed === p.type)) {
|
|
3335
|
+
ctx.addIssue({
|
|
3336
|
+
code: "custom",
|
|
3337
|
+
path: ["type"],
|
|
3338
|
+
message: `${p.type} cannot open an instant form \u2014 Meta refuses it (subcode 1856030). Use one of ${LEAD_FORM_CTA_TYPES.join(", ")}, or drop lead_gen_form_id if this ad should go to the website.`
|
|
3339
|
+
});
|
|
3340
|
+
}
|
|
3341
|
+
});
|
|
3342
|
+
function validateMessagingButton(p, ctx) {
|
|
3343
|
+
const opens = MESSAGING_CTA_DESTINATION[p.type];
|
|
3344
|
+
if (opens && p.app_destination && p.app_destination !== opens) {
|
|
3345
|
+
ctx.addIssue({
|
|
3346
|
+
code: "custom",
|
|
3347
|
+
path: ["app_destination"],
|
|
3348
|
+
message: `${p.type} opens ${opens} \u2014 set app_destination ${opens}, or pick the button of ${p.app_destination}`
|
|
3349
|
+
});
|
|
3350
|
+
}
|
|
3351
|
+
if (p.app_destination && p.lead_gen_form_id) {
|
|
3352
|
+
ctx.addIssue({
|
|
3353
|
+
code: "custom",
|
|
3354
|
+
path: ["app_destination"],
|
|
3355
|
+
message: "a button opens a conversation or an instant form, not both \u2014 drop app_destination or lead_gen_form_id"
|
|
3356
|
+
});
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3359
|
+
var creativeEnhancementsSchema = z12.strictObject({
|
|
3360
|
+
standardEnhancements: z12.enum(ENROLL_STATUSES).optional(),
|
|
3361
|
+
features: z12.record(z12.string(), z12.enum(ENROLL_STATUSES)).optional()
|
|
3362
|
+
});
|
|
3363
|
+
var multiAdvertiserSchema = z12.enum(["opt_in", "opt_out"]);
|
|
3364
|
+
var partnerSchema = z12.strictObject({
|
|
3365
|
+
facebook_page_id: z12.string().regex(NUMERIC_ID_REGEX).optional(),
|
|
3366
|
+
instagram_user_id: z12.string().regex(NUMERIC_ID_REGEX).optional(),
|
|
3367
|
+
ad_format: z12.enum(["both", "first_only", "auto"]).optional()
|
|
3368
|
+
}).refine(
|
|
3369
|
+
(p) => p.facebook_page_id !== void 0 || p.instagram_user_id !== void 0,
|
|
3370
|
+
"name the partner's Page or Instagram account"
|
|
3371
|
+
);
|
|
3372
|
+
var creativeSharedFields = {
|
|
3373
|
+
name: z12.string().max(META_LIMITS.creative.nameMax).optional(),
|
|
3374
|
+
/** Facebook Page id backing the ad's identity. */
|
|
3375
|
+
page_id: parentRefSchema,
|
|
3376
|
+
/** Instagram account id for IG placements (aka instagram_actor_id on read). */
|
|
3377
|
+
instagram_user_id: z12.string().regex(NUMERIC_ID_REGEX).optional(),
|
|
3378
|
+
/** URL tracking parameters appended to the destination, e.g. "utm_source=fb&utm_campaign=x". */
|
|
3379
|
+
url_tags: z12.string().max(1e3).optional(),
|
|
3380
|
+
enhancements: creativeEnhancementsSchema.optional(),
|
|
3381
|
+
/**
|
|
3382
|
+
* v26: on an advertiser with a shop, Meta sends eligible new ads to the shop
|
|
3383
|
+
* as well as the website (`destination_spec` WEBSITE_AND_SHOP) unless told
|
|
3384
|
+
* not to. `website_only` opts out (WEBSITE_AND_SHOP_OPT_OUT); unset leaves
|
|
3385
|
+
* Meta's default. Live on v26.0: both values validate, any other is refused.
|
|
3386
|
+
*/
|
|
3387
|
+
shop_destination: z12.enum(["website_and_shop", "website_only"]).optional(),
|
|
3388
|
+
multi_advertiser: multiAdvertiserSchema.optional(),
|
|
3389
|
+
partner: partnerSchema.optional()
|
|
3390
|
+
};
|
|
3391
|
+
var conversationAndAppFields = {
|
|
3392
|
+
welcome_message: welcomeMessageSchema.optional(),
|
|
3393
|
+
/**
|
|
3394
|
+
* How a deep link opens: `deeplink_with_web_fallback` (the app, else the
|
|
3395
|
+
* website), `deeplink_with_appstore_fallback` (the app, else its store page),
|
|
3396
|
+
* `web_only`, or `automatic` (Meta decides).
|
|
3397
|
+
*/
|
|
3398
|
+
applink_treatment: z12.enum(["automatic", "deeplink_with_web_fallback", "deeplink_with_appstore_fallback", "web_only"]).optional()
|
|
3399
|
+
};
|
|
3400
|
+
var imageMediaFields = {
|
|
3401
|
+
imageHash: z12.string().regex(IMAGE_HASH_REGEX).optional(),
|
|
3402
|
+
imageRef: tempRefSchema3.optional()
|
|
3403
|
+
};
|
|
3404
|
+
var videoMediaFields = {
|
|
3405
|
+
videoId: z12.string().regex(NUMERIC_ID_REGEX).optional(),
|
|
3406
|
+
videoRef: tempRefSchema3.optional(),
|
|
3407
|
+
/** The video's cover: a picture already in the ad account, by hash. */
|
|
3408
|
+
thumbnailHash: z12.string().regex(IMAGE_HASH_REGEX).optional(),
|
|
3409
|
+
/** The video's cover: a library picture staged as a `media upload --kind image` in this chat. */
|
|
3410
|
+
thumbnailRef: tempRefSchema3.optional(),
|
|
3411
|
+
/** The video's cover: a public picture URL Meta fetches. */
|
|
3412
|
+
imageUrl: z12.string().url().optional()
|
|
3413
|
+
};
|
|
3414
|
+
function validateDeepLink(cta, creativeLink, ctx, path) {
|
|
3415
|
+
if (!cta?.app_link) return;
|
|
3416
|
+
const link = cta.link ?? creativeLink;
|
|
3417
|
+
if (!link || !isAppStoreUrl(link)) {
|
|
3418
|
+
ctx.addIssue({
|
|
3419
|
+
code: "custom",
|
|
3420
|
+
path,
|
|
3421
|
+
message: `a deep link (app_link) needs the button to lead to the app's store page \u2014 Meta refuses it beside ${link ?? "no link"} (subcode 1885949); set the link to the App Store or Google Play URL`
|
|
3422
|
+
});
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
function isAppStoreUrl(url) {
|
|
3426
|
+
try {
|
|
3427
|
+
const host = new URL(url).hostname.toLowerCase();
|
|
3428
|
+
return ["apps.apple.com", "itunes.apple.com", "play.google.com"].includes(host);
|
|
3429
|
+
} catch {
|
|
3430
|
+
return false;
|
|
3431
|
+
}
|
|
3432
|
+
}
|
|
3433
|
+
function validateWelcomeOpensConversation(p, ctx) {
|
|
3434
|
+
if (p.welcome_message && !p.call_to_action?.app_destination) {
|
|
3435
|
+
ctx.addIssue({
|
|
3436
|
+
code: "custom",
|
|
3437
|
+
path: ["welcome_message"],
|
|
3438
|
+
message: "a greeting needs a button that opens a conversation \u2014 set call_to_action.app_destination (MESSENGER, WHATSAPP or INSTAGRAM_DIRECT)"
|
|
3439
|
+
});
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3442
|
+
function countImageRefs(p) {
|
|
3443
|
+
return [p.imageHash, p.imageRef].filter(Boolean).length;
|
|
3444
|
+
}
|
|
3445
|
+
function countVideoRefs(p) {
|
|
3446
|
+
return [p.videoId, p.videoRef].filter(Boolean).length;
|
|
3447
|
+
}
|
|
3448
|
+
var singleCreativeSchema = z12.strictObject({
|
|
3449
|
+
creativeType: z12.literal("single"),
|
|
3450
|
+
...creativeSharedFields,
|
|
3451
|
+
/** Primary text. */
|
|
3452
|
+
message: messageSchema,
|
|
3453
|
+
link: httpsUrlSchema5,
|
|
3454
|
+
/** Headline. */
|
|
3455
|
+
headline: headlineSchema.optional(),
|
|
3456
|
+
description: descriptionSchema.optional(),
|
|
3457
|
+
/** Display URL / caption shown under the headline. */
|
|
3458
|
+
caption: z12.string().max(255).optional(),
|
|
3459
|
+
call_to_action: callToActionSchema.optional(),
|
|
3460
|
+
...imageMediaFields,
|
|
3461
|
+
...videoMediaFields,
|
|
3462
|
+
...conversationAndAppFields
|
|
3463
|
+
}).superRefine((p, ctx) => {
|
|
3464
|
+
validateWelcomeOpensConversation(p, ctx);
|
|
3465
|
+
validateDeepLink(p.call_to_action, p.link, ctx, ["call_to_action", "app_link"]);
|
|
3466
|
+
const images = countImageRefs(p);
|
|
3467
|
+
const videos = countVideoRefs(p);
|
|
3468
|
+
if (images + videos === 0) {
|
|
3469
|
+
ctx.addIssue({ code: "custom", path: ["imageHash"], message: "a single ad needs one image or one video" });
|
|
3470
|
+
}
|
|
3471
|
+
if (images > 1) {
|
|
3472
|
+
ctx.addIssue({ code: "custom", path: ["imageHash"], message: "provide exactly one image reference" });
|
|
3473
|
+
}
|
|
3474
|
+
if (videos > 1) {
|
|
3475
|
+
ctx.addIssue({ code: "custom", path: ["videoId"], message: "provide exactly one video reference" });
|
|
3476
|
+
}
|
|
3477
|
+
if (images > 0 && videos > 0) {
|
|
3478
|
+
ctx.addIssue({
|
|
3479
|
+
code: "custom",
|
|
3480
|
+
path: ["videoId"],
|
|
3481
|
+
message: "a single ad is either an image OR a video, not both"
|
|
3482
|
+
});
|
|
3483
|
+
}
|
|
3484
|
+
});
|
|
3485
|
+
var carouselCardSchema = z12.strictObject({
|
|
3486
|
+
link: httpsUrlSchema5,
|
|
3487
|
+
headline: z12.string().max(META_LIMITS.creative.headlineMax).optional(),
|
|
3488
|
+
description: z12.string().max(META_LIMITS.creative.descriptionMax).optional(),
|
|
3489
|
+
call_to_action: callToActionSchema.optional(),
|
|
3490
|
+
...imageMediaFields,
|
|
3491
|
+
...videoMediaFields
|
|
3492
|
+
}).superRefine((p, ctx) => {
|
|
3493
|
+
const images = countImageRefs(p);
|
|
3494
|
+
const videos = countVideoRefs(p);
|
|
3495
|
+
if (images + videos === 0) {
|
|
3496
|
+
ctx.addIssue({ code: "custom", path: ["imageHash"], message: "each card needs an image or a video" });
|
|
3497
|
+
}
|
|
3498
|
+
if (images > 1) {
|
|
3499
|
+
ctx.addIssue({ code: "custom", path: ["imageHash"], message: "each card needs exactly one image reference" });
|
|
3500
|
+
}
|
|
3501
|
+
if (videos > 1) {
|
|
3502
|
+
ctx.addIssue({ code: "custom", path: ["videoId"], message: "each card needs exactly one video reference" });
|
|
3503
|
+
}
|
|
3504
|
+
if (images > 0 && videos > 0) {
|
|
3505
|
+
ctx.addIssue({ code: "custom", path: ["videoId"], message: "each card is an image OR a video, not both" });
|
|
3506
|
+
}
|
|
3507
|
+
});
|
|
3508
|
+
var carouselCreativeSchema = z12.strictObject({
|
|
3509
|
+
creativeType: z12.literal("carousel"),
|
|
3510
|
+
...creativeSharedFields,
|
|
3511
|
+
message: messageSchema,
|
|
3512
|
+
/** Optional "see more" card destination applied when a card has no own link. */
|
|
3513
|
+
link: httpsUrlSchema5.optional(),
|
|
3514
|
+
call_to_action: callToActionSchema.optional(),
|
|
3515
|
+
cards: z12.array(carouselCardSchema).min(META_LIMITS.creative.carouselCardsMin).max(META_LIMITS.creative.carouselCardsMax),
|
|
3516
|
+
/**
|
|
3517
|
+
* `multi_share_optimized`. Meta's default (`optimized`) reorders the cards
|
|
3518
|
+
* by performance and may show only the top five; `fixed` — Baker's default —
|
|
3519
|
+
* shows every card in the order it was built.
|
|
3520
|
+
*/
|
|
3521
|
+
card_order: z12.enum(["fixed", "optimized"]).default("fixed"),
|
|
3522
|
+
/** `multi_share_end_card`: Meta adds a card linking to the Page after the last one. Off unless asked. */
|
|
3523
|
+
end_card: z12.boolean().default(false),
|
|
3524
|
+
...conversationAndAppFields
|
|
3525
|
+
}).superRefine((p, ctx) => {
|
|
3526
|
+
validateWelcomeOpensConversation(p, ctx);
|
|
3527
|
+
validateDeepLink(p.call_to_action, p.link ?? p.cards[0]?.link, ctx, ["call_to_action", "app_link"]);
|
|
3528
|
+
p.cards.forEach((card, index) => {
|
|
3529
|
+
validateDeepLink(card.call_to_action, card.link, ctx, ["cards", index, "call_to_action", "app_link"]);
|
|
3530
|
+
});
|
|
3531
|
+
const forms = new Set(
|
|
3532
|
+
[p.call_to_action?.lead_gen_form_id, ...p.cards.map((card) => card.call_to_action?.lead_gen_form_id)].filter(
|
|
3533
|
+
Boolean
|
|
3534
|
+
)
|
|
3535
|
+
);
|
|
3536
|
+
if (forms.size > 1) {
|
|
3537
|
+
ctx.addIssue({
|
|
3538
|
+
code: "custom",
|
|
3539
|
+
path: ["cards"],
|
|
3540
|
+
message: `every card of a carousel must open the SAME instant form \u2014 this one names ${[...forms].join(" and ")}`
|
|
3541
|
+
});
|
|
3542
|
+
}
|
|
3543
|
+
});
|
|
3544
|
+
var dynamicImageSchema = z12.strictObject({ ...imageMediaFields }).refine((p) => countImageRefs(p) === 1, "each dynamic image needs exactly one reference");
|
|
3545
|
+
var dynamicVideoSchema = z12.strictObject({
|
|
3546
|
+
videoId: videoMediaFields.videoId,
|
|
3547
|
+
videoRef: videoMediaFields.videoRef,
|
|
3548
|
+
thumbnailHash: videoMediaFields.thumbnailHash,
|
|
3549
|
+
thumbnailRef: videoMediaFields.thumbnailRef
|
|
3550
|
+
}).refine((p) => countVideoRefs(p) === 1, "each dynamic video needs exactly one reference");
|
|
3551
|
+
var DYN = META_LIMITS.creative;
|
|
3552
|
+
function assetFeedCount(p) {
|
|
3553
|
+
return p.bodies.length + p.titles.length + (p.descriptions?.length ?? 0) + (p.images?.length ?? 0) + (p.videos?.length ?? 0) + p.link_urls.length + (p.call_to_action_types?.length ?? 0) + 1;
|
|
3554
|
+
}
|
|
3555
|
+
var dynamicCreativeSchema = z12.strictObject({
|
|
3556
|
+
creativeType: z12.literal("dynamic"),
|
|
3557
|
+
...creativeSharedFields,
|
|
3558
|
+
bodies: z12.array(z12.strictObject({ text: z12.string().min(1).max(DYN.assetFeedBodyMax) })).min(DYN.dynamicTextsMin).max(DYN.dynamicTextsMax),
|
|
3559
|
+
titles: z12.array(z12.strictObject({ text: z12.string().min(1).max(DYN.assetFeedTitleMax) })).min(DYN.dynamicTextsMin).max(DYN.dynamicTextsMax),
|
|
3560
|
+
descriptions: z12.array(z12.strictObject({ text: z12.string().min(1).max(DYN.assetFeedTitleMax) })).max(DYN.dynamicTextsMax).optional(),
|
|
3561
|
+
images: z12.array(dynamicImageSchema).max(DYN.assetFeedImagesMax).optional(),
|
|
3562
|
+
videos: z12.array(dynamicVideoSchema).max(DYN.assetFeedVideosMax).optional(),
|
|
3563
|
+
/**
|
|
3564
|
+
* Exactly one format ("Only one ad_format is allowed per asset feed").
|
|
3565
|
+
* Meta's doc calls it required; when it is left out the builder fills it
|
|
3566
|
+
* from the media (`AUTOMATIC_FORMAT` for pictures and videos together) —
|
|
3567
|
+
* except on a flexible (`DEGREES_OF_FREEDOM`) creative, every one of which
|
|
3568
|
+
* read back off a live account omits it.
|
|
3569
|
+
*/
|
|
3570
|
+
ad_formats: z12.array(z12.enum(AD_FORMATS)).length(1).optional(),
|
|
3571
|
+
call_to_action_types: z12.array(z12.string()).max(DYN.dynamicTextsMax).optional(),
|
|
3572
|
+
/**
|
|
3573
|
+
* The button on the creative's own `link_data` — a flexible ad carries both
|
|
3574
|
+
* envelopes, and this is the one that takes `SEE_DETAILS`. Kept separate
|
|
3575
|
+
* from `call_to_action_types` because Meta validates the two against
|
|
3576
|
+
* different enums.
|
|
3577
|
+
*/
|
|
3578
|
+
call_to_action: callToActionSchema.optional(),
|
|
3579
|
+
link_urls: z12.array(z12.strictObject({ website_url: httpsUrlSchema5, display_url: z12.string().optional() })).min(1).max(DYN.dynamicTextsMax),
|
|
3580
|
+
/**
|
|
3581
|
+
* `asset_feed_spec.optimization_type` — passed through as a free string on
|
|
3582
|
+
* purpose. Meta's own 400 enumerates ten values and then accepts
|
|
3583
|
+
* `DEGREES_OF_FREEDOM`, which is not among them and is what every
|
|
3584
|
+
* Advantage+ flexible creative on a live account reports. An enum here
|
|
3585
|
+
* would refuse the value Meta itself hands back.
|
|
3586
|
+
*/
|
|
3587
|
+
optimization_type: z12.string().optional(),
|
|
3588
|
+
/** Multi-language / placement customization — structural passthrough for v1. */
|
|
3589
|
+
asset_customization_rules: z12.array(z12.record(z12.string(), z12.unknown())).optional()
|
|
3590
|
+
}).superRefine((p, ctx) => {
|
|
3591
|
+
if (!(p.images?.length || p.videos?.length)) {
|
|
3592
|
+
ctx.addIssue({
|
|
3593
|
+
code: "custom",
|
|
3594
|
+
path: ["images"],
|
|
3595
|
+
message: "a dynamic creative needs at least one image or video"
|
|
3596
|
+
});
|
|
3597
|
+
}
|
|
3598
|
+
const assets = assetFeedCount(p);
|
|
3599
|
+
if (assets > DYN.assetFeedAssetsMax) {
|
|
3600
|
+
ctx.addIssue({
|
|
3601
|
+
code: "custom",
|
|
3602
|
+
path: ["images"],
|
|
3603
|
+
message: `a dynamic creative carries at most ${DYN.assetFeedAssetsMax} assets in all (pictures, videos, texts, links, buttons and the format each count) \u2014 this has ${assets}`
|
|
3604
|
+
});
|
|
3605
|
+
}
|
|
3606
|
+
p.call_to_action_types?.forEach((type, index) => {
|
|
3607
|
+
if (ASSET_FEED_CTA_TYPES.some((allowed) => allowed === type)) {
|
|
3608
|
+
return;
|
|
3609
|
+
}
|
|
3610
|
+
const isLinkButton = CTA_TYPES.some((allowed) => allowed === type);
|
|
3611
|
+
ctx.addIssue({
|
|
3612
|
+
code: "custom",
|
|
3613
|
+
path: ["call_to_action_types", index],
|
|
3614
|
+
message: isLinkButton ? `${type} is not one of the buttons Meta accepts in asset_feed_spec.call_to_action_types \u2014 it is a link_data button. Pass it as call_to_action: { type: "${type}" } instead.` : `${type} is not a Meta call-to-action button.`
|
|
3615
|
+
});
|
|
3616
|
+
});
|
|
3617
|
+
});
|
|
3618
|
+
var placementAssetSchema = z12.strictObject({
|
|
3619
|
+
...imageMediaFields,
|
|
3620
|
+
...videoMediaFields,
|
|
3621
|
+
placements: z12.array(z12.enum(PLACEMENT_SLOTS)).min(1)
|
|
3622
|
+
}).superRefine((p, ctx) => {
|
|
3623
|
+
const images = countImageRefs(p);
|
|
3624
|
+
const videos = countVideoRefs(p);
|
|
3625
|
+
if (images + videos !== 1) {
|
|
3626
|
+
ctx.addIssue({ code: "custom", path: ["imageRef"], message: "each asset is exactly one picture or one video" });
|
|
3627
|
+
}
|
|
3628
|
+
if (p.placements.includes("default") && p.placements.length > 1) {
|
|
3629
|
+
ctx.addIssue({
|
|
3630
|
+
code: "custom",
|
|
3631
|
+
path: ["placements"],
|
|
3632
|
+
message: '"default" already means every placement no other asset names \u2014 give it alone'
|
|
3633
|
+
});
|
|
3634
|
+
}
|
|
3635
|
+
});
|
|
3636
|
+
var placementCreativeSchema = z12.strictObject({
|
|
3637
|
+
creativeType: z12.literal("placement"),
|
|
3638
|
+
...creativeSharedFields,
|
|
3639
|
+
message: z12.string().min(1).max(META_LIMITS.creative.assetFeedBodyMax),
|
|
3640
|
+
link: httpsUrlSchema5,
|
|
3641
|
+
headline: z12.string().min(1).max(META_LIMITS.creative.assetFeedTitleMax).optional(),
|
|
3642
|
+
description: z12.string().min(1).max(META_LIMITS.creative.assetFeedTitleMax).optional(),
|
|
3643
|
+
call_to_action: callToActionSchema.optional(),
|
|
3644
|
+
assets: z12.array(placementAssetSchema).min(2).max(META_LIMITS.creative.assetFeedImagesMax)
|
|
3645
|
+
}).superRefine((p, ctx) => {
|
|
3646
|
+
const carried = ["lead_gen_form_id", "link", "app_destination", "app_link", "application"].filter(
|
|
3647
|
+
(key) => p.call_to_action?.[key] !== void 0
|
|
3648
|
+
);
|
|
3649
|
+
if (carried.length > 0) {
|
|
3650
|
+
ctx.addIssue({
|
|
3651
|
+
code: "custom",
|
|
3652
|
+
path: ["call_to_action"],
|
|
3653
|
+
message: `a placement creative's button is its type only \u2014 Meta keeps no ${carried.join(", ")} on it. For an Instant Form, a different button link or a messaging app, use a single or carousel creative`
|
|
3654
|
+
});
|
|
3655
|
+
}
|
|
3656
|
+
const defaults = p.assets.filter((asset) => asset.placements.includes("default")).length;
|
|
3657
|
+
if (defaults !== 1) {
|
|
3658
|
+
ctx.addIssue({
|
|
3659
|
+
code: "custom",
|
|
3660
|
+
path: ["assets"],
|
|
3661
|
+
message: `exactly one asset must be "default" \u2014 the one every other placement shows (this has ${defaults})`
|
|
3662
|
+
});
|
|
3663
|
+
}
|
|
3664
|
+
const claimed = /* @__PURE__ */ new Map();
|
|
3665
|
+
p.assets.forEach((asset, index) => {
|
|
3666
|
+
for (const slot of asset.placements) {
|
|
3667
|
+
if (slot !== "default" && claimed.has(slot)) {
|
|
3668
|
+
ctx.addIssue({
|
|
3669
|
+
code: "custom",
|
|
3670
|
+
path: ["assets", index, "placements"],
|
|
3671
|
+
message: `${slot} is already given to asset ${claimed.get(slot)} \u2014 one asset per placement`
|
|
3672
|
+
});
|
|
3673
|
+
}
|
|
3674
|
+
claimed.set(slot, index);
|
|
3675
|
+
}
|
|
3676
|
+
});
|
|
3677
|
+
const kinds = new Set(p.assets.map((asset) => countVideoRefs(asset) > 0 ? "video" : "image"));
|
|
3678
|
+
if (kinds.size > 1) {
|
|
3679
|
+
ctx.addIssue({
|
|
3680
|
+
code: "custom",
|
|
3681
|
+
path: ["assets"],
|
|
3682
|
+
message: "every asset of a placement creative is a picture, or every one is a video \u2014 Meta takes one format per ad"
|
|
3683
|
+
});
|
|
3684
|
+
}
|
|
3685
|
+
});
|
|
3686
|
+
var existingPostCreativeSchema = z12.strictObject({
|
|
3687
|
+
creativeType: z12.literal("existing_post"),
|
|
3688
|
+
name: creativeSharedFields.name,
|
|
3689
|
+
/** "<page_id>_<post_id>" object story id of the Facebook post to promote. */
|
|
3690
|
+
object_story_id: z12.string().regex(/^\d+_\d+$/, 'expected "<page_id>_<post_id>"').optional(),
|
|
3691
|
+
/** An Instagram post's media id — from `baker ads meta instagram-media`. */
|
|
3692
|
+
source_instagram_media_id: z12.string().regex(NUMERIC_ID_REGEX).optional(),
|
|
3693
|
+
/** A creator's partnership ad code for their Instagram post. */
|
|
3694
|
+
instagram_ad_code: z12.string().trim().min(8).max(500).optional(),
|
|
3695
|
+
/** The brand's Page the ad runs as — needed with an Instagram post or an ad code. */
|
|
3696
|
+
page_id: z12.string().regex(NUMERIC_ID_REGEX).optional(),
|
|
3697
|
+
instagram_user_id: creativeSharedFields.instagram_user_id,
|
|
3698
|
+
url_tags: creativeSharedFields.url_tags,
|
|
3699
|
+
enhancements: creativeSharedFields.enhancements,
|
|
3700
|
+
multi_advertiser: creativeSharedFields.multi_advertiser,
|
|
3701
|
+
partner: creativeSharedFields.partner
|
|
3702
|
+
}).superRefine((p, ctx) => {
|
|
3703
|
+
const sources = [p.object_story_id, p.source_instagram_media_id, p.instagram_ad_code].filter(Boolean).length;
|
|
3704
|
+
if (sources !== 1) {
|
|
3705
|
+
ctx.addIssue({
|
|
3706
|
+
code: "custom",
|
|
3707
|
+
path: ["object_story_id"],
|
|
3708
|
+
message: "boost ONE post: object_story_id (Facebook), source_instagram_media_id (Instagram) or instagram_ad_code"
|
|
3709
|
+
});
|
|
3710
|
+
}
|
|
3711
|
+
if ((p.source_instagram_media_id || p.instagram_ad_code) && !p.page_id) {
|
|
3712
|
+
ctx.addIssue({
|
|
3713
|
+
code: "custom",
|
|
3714
|
+
path: ["page_id"],
|
|
3715
|
+
message: "an Instagram post runs as the brand's Facebook Page \u2014 add page_id"
|
|
3716
|
+
});
|
|
3717
|
+
}
|
|
3718
|
+
});
|
|
3719
|
+
var specCreativeSchema = z12.strictObject({
|
|
3720
|
+
creativeType: z12.literal("spec"),
|
|
3721
|
+
name: creativeSharedFields.name,
|
|
3722
|
+
url_tags: creativeSharedFields.url_tags,
|
|
3723
|
+
/** Promote the existing post — keeps the ad's likes, comments and shares. */
|
|
3724
|
+
object_story_id: z12.string().regex(/^\d+_\d+$/, 'expected "<page_id>_<post_id>"').optional(),
|
|
3725
|
+
object_story_spec: z12.record(z12.string(), z12.unknown()).optional(),
|
|
3726
|
+
asset_feed_spec: z12.record(z12.string(), z12.unknown()).optional(),
|
|
3727
|
+
degrees_of_freedom_spec: z12.record(z12.string(), z12.unknown()).optional(),
|
|
3728
|
+
instagram_user_id: creativeSharedFields.instagram_user_id,
|
|
3729
|
+
/** The creative this spec was cloned from, for the review diff. */
|
|
3730
|
+
clonedFrom: z12.string().regex(NUMERIC_ID_REGEX).optional()
|
|
3731
|
+
}).superRefine((p, ctx) => {
|
|
3732
|
+
if (!(p.object_story_id || p.object_story_spec || p.asset_feed_spec)) {
|
|
3733
|
+
ctx.addIssue({
|
|
3734
|
+
code: "custom",
|
|
3735
|
+
path: ["object_story_spec"],
|
|
3736
|
+
message: "a spec creative needs object_story_id, object_story_spec or asset_feed_spec"
|
|
3737
|
+
});
|
|
3738
|
+
}
|
|
3739
|
+
});
|
|
3740
|
+
var CATALOG_TOKEN_NAMESPACES = [
|
|
3741
|
+
"product",
|
|
3742
|
+
"category",
|
|
3743
|
+
"hotel",
|
|
3744
|
+
"flight",
|
|
3745
|
+
"destination",
|
|
3746
|
+
"home_listing",
|
|
3747
|
+
"vehicle",
|
|
3748
|
+
"vehicle_offer",
|
|
3749
|
+
"trip"
|
|
3750
|
+
];
|
|
3751
|
+
function unknownCatalogTokens(text) {
|
|
3752
|
+
return [...(text ?? "").matchAll(/\{\{\s*([^}|\s]+)[^}]*\}\}/g)].filter((match) => !CATALOG_TOKEN_NAMESPACES.some((ns) => match[1]?.startsWith(`${ns}.`))).map((match) => match[0]);
|
|
3753
|
+
}
|
|
3754
|
+
var templateText = (max) => z12.string().trim().min(1).max(max);
|
|
3755
|
+
var catalogCreativeSchema = z12.strictObject({
|
|
3756
|
+
creativeType: z12.literal("catalog"),
|
|
3757
|
+
name: creativeSharedFields.name,
|
|
3758
|
+
page_id: creativeSharedFields.page_id,
|
|
3759
|
+
instagram_user_id: creativeSharedFields.instagram_user_id,
|
|
3760
|
+
url_tags: creativeSharedFields.url_tags,
|
|
3761
|
+
/** A product set's id, or the meta_temp_* ref of one this chat creates. */
|
|
3762
|
+
product_set_id: parentRefSchema,
|
|
3763
|
+
message: templateText(META_LIMITS.creative.messageHardMax),
|
|
3764
|
+
link: httpsUrlSchema5,
|
|
3765
|
+
headline: templateText(255).optional(),
|
|
3766
|
+
description: templateText(255).optional(),
|
|
3767
|
+
call_to_action: z12.strictObject({ type: z12.enum(CTA_TYPES) }).optional(),
|
|
3768
|
+
/**
|
|
3769
|
+
* `carousel` (Meta picks the products, one card each — the default),
|
|
3770
|
+
* `single` (one product per ad: `force_single_link`), or `slideshow` (each
|
|
3771
|
+
* card cycles a product's pictures: `format_option: carousel_slideshows`).
|
|
3772
|
+
*/
|
|
3773
|
+
format: z12.enum(["carousel", "single", "slideshow"]).default("carousel"),
|
|
3774
|
+
/** `multi_share_end_card`: a last card linking to the Page. Off unless asked. */
|
|
3775
|
+
end_card: z12.boolean().default(false)
|
|
3776
|
+
}).superRefine((p, ctx) => {
|
|
3777
|
+
for (const key of ["message", "headline", "description"]) {
|
|
3778
|
+
const unknown = unknownCatalogTokens(p[key]);
|
|
3779
|
+
if (unknown.length > 0) {
|
|
3780
|
+
ctx.addIssue({
|
|
3781
|
+
code: "custom",
|
|
3782
|
+
path: [key],
|
|
3783
|
+
message: `${unknown.join(", ")} reads no catalog field \u2014 a token names its catalog type, e.g. {{product.name}}, {{product.price}}`
|
|
3784
|
+
});
|
|
3785
|
+
}
|
|
3786
|
+
}
|
|
3787
|
+
});
|
|
3788
|
+
var instantExperienceCreativeSchema = z12.strictObject({
|
|
3789
|
+
creativeType: z12.literal("instant_experience"),
|
|
3790
|
+
name: creativeSharedFields.name,
|
|
3791
|
+
page_id: creativeSharedFields.page_id,
|
|
3792
|
+
instagram_user_id: creativeSharedFields.instagram_user_id,
|
|
3793
|
+
url_tags: creativeSharedFields.url_tags,
|
|
3794
|
+
/** The experience: its id, or the meta_temp_* ref of one this chat creates. */
|
|
3795
|
+
canvas_id: parentRefSchema,
|
|
3796
|
+
message: messageSchema,
|
|
3797
|
+
headline: headlineSchema.optional(),
|
|
3798
|
+
description: descriptionSchema.optional(),
|
|
3799
|
+
call_to_action: z12.strictObject({ type: z12.enum(CTA_TYPES) }).optional(),
|
|
3800
|
+
format: z12.enum(["standard", "collection"]).default("standard"),
|
|
3801
|
+
/**
|
|
3802
|
+
* A collection over a photo experience (more than header → product set →
|
|
3803
|
+
* footer) shows four pictures under its hero, named by the experience's
|
|
3804
|
+
* elements: a photo element, or one product of a product-set element
|
|
3805
|
+
* (`element_child_index`). Meta refuses such a collection without four
|
|
3806
|
+
* (2131023). Only a live experience has element ids — read them with
|
|
3807
|
+
* `instant-experiences --page-id`.
|
|
3808
|
+
*/
|
|
3809
|
+
collection_thumbnails: z12.array(
|
|
3810
|
+
z12.strictObject({
|
|
3811
|
+
element_id: z12.string().regex(NUMERIC_ID_REGEX),
|
|
3812
|
+
element_child_index: z12.number().int().min(0).optional(),
|
|
3813
|
+
/**
|
|
3814
|
+
* The square of a photo element shown, in the photo's pixels:
|
|
3815
|
+
* `{"100x100": [[left, top], [right, bottom]]}` — Meta's only key.
|
|
3816
|
+
* Required on a photo element (live); staging fills a centered
|
|
3817
|
+
* square from the photo when it is left out.
|
|
3818
|
+
*/
|
|
3819
|
+
element_crops: z12.strictObject({
|
|
3820
|
+
"100x100": z12.tuple([
|
|
3821
|
+
z12.tuple([z12.number().int().min(0), z12.number().int().min(0)]),
|
|
3822
|
+
z12.tuple([z12.number().int().positive(), z12.number().int().positive()])
|
|
3823
|
+
])
|
|
3824
|
+
}).optional()
|
|
3825
|
+
})
|
|
3826
|
+
).length(4, "a collection shows exactly four pictures under its hero").optional(),
|
|
3827
|
+
...imageMediaFields,
|
|
3828
|
+
...videoMediaFields
|
|
3829
|
+
}).superRefine((p, ctx) => {
|
|
3830
|
+
if (p.collection_thumbnails && (p.format !== "collection" || !NUMERIC_ID_REGEX.test(p.canvas_id))) {
|
|
3831
|
+
ctx.addIssue({
|
|
3832
|
+
code: "custom",
|
|
3833
|
+
path: ["collection_thumbnails"],
|
|
3834
|
+
message: "collection thumbnails go on a --format collection ad over a live Instant Experience (its elements have ids only once it exists)"
|
|
3835
|
+
});
|
|
3836
|
+
}
|
|
3837
|
+
const images = countImageRefs(p);
|
|
3838
|
+
const videos = countVideoRefs(p);
|
|
3839
|
+
if (images + videos !== 1) {
|
|
3840
|
+
ctx.addIssue({
|
|
3841
|
+
code: "custom",
|
|
3842
|
+
path: ["imageHash"],
|
|
3843
|
+
message: "the ad shows one picture or one video before it opens the experience"
|
|
3844
|
+
});
|
|
3845
|
+
}
|
|
3846
|
+
});
|
|
3847
|
+
var creativeContentSchema = z12.discriminatedUnion("creativeType", [
|
|
3848
|
+
singleCreativeSchema,
|
|
3849
|
+
carouselCreativeSchema,
|
|
3850
|
+
dynamicCreativeSchema,
|
|
3851
|
+
placementCreativeSchema,
|
|
3852
|
+
existingPostCreativeSchema,
|
|
3853
|
+
specCreativeSchema,
|
|
3854
|
+
catalogCreativeSchema,
|
|
3855
|
+
instantExperienceCreativeSchema
|
|
3856
|
+
]);
|
|
3857
|
+
var adCreativeCreateSchema = creativeContentSchema;
|
|
3858
|
+
var adCreativeUpdateSchema = z12.strictObject({
|
|
3859
|
+
name: z12.string().max(META_LIMITS.creative.nameMax).optional(),
|
|
3860
|
+
status: z12.enum(CREATIVE_UPDATE_STATUSES).optional(),
|
|
3861
|
+
/** Content patch — only honored when the target is a staged meta_temp_* creative. */
|
|
3862
|
+
content: z12.record(z12.string(), z12.unknown()).optional(),
|
|
3863
|
+
...labelFields
|
|
3864
|
+
}).refine((p) => Object.values(p).some((val) => val !== void 0), "update needs at least one field");
|
|
3865
|
+
var adCreateSchema = z12.strictObject({
|
|
3866
|
+
name: z12.string().min(1).max(META_LIMITS.ad.nameMax),
|
|
3867
|
+
adset_id: parentRefSchema,
|
|
3868
|
+
status: stageableStatusSchema.default("PAUSED"),
|
|
3869
|
+
creative: z12.strictObject({ creative_id: parentRefSchema }),
|
|
3870
|
+
/** Conversion pixel / offline event set / view tags — structural passthrough. */
|
|
3871
|
+
tracking_specs: z12.array(z12.record(z12.string(), z12.unknown())).optional(),
|
|
3872
|
+
/**
|
|
3873
|
+
* The website domain the ad's conversions are counted on — the registrable
|
|
3874
|
+
* domain only (`example.com`, never `www.example.com` or a URL). Meta fills
|
|
3875
|
+
* it from the destination when left out; set it when the site and the
|
|
3876
|
+
* pixel's domain differ. Create-only on Meta's ad reference.
|
|
3877
|
+
*/
|
|
3878
|
+
conversion_domain: z12.string().trim().toLowerCase().regex(/^(?!www\.)[a-z0-9-]+(\.[a-z0-9-]+)+$/, "the domain alone, e.g. example.com (no www., no https://)").optional(),
|
|
3879
|
+
...createLabelFields
|
|
3880
|
+
});
|
|
3881
|
+
var adUpdateSchema = z12.strictObject({
|
|
3882
|
+
name: z12.string().min(1).max(META_LIMITS.ad.nameMax).optional(),
|
|
3883
|
+
status: updateStatusSchema.optional(),
|
|
3884
|
+
/** Swapping the creative is the Meta way to "edit" an ad's creative. */
|
|
3885
|
+
creative: z12.strictObject({ creative_id: parentRefSchema }).optional(),
|
|
3886
|
+
tracking_specs: z12.array(z12.record(z12.string(), z12.unknown())).optional(),
|
|
3887
|
+
...labelFields
|
|
3888
|
+
}).refine((p) => Object.values(p).some((val) => val !== void 0), "update needs at least one field");
|
|
3889
|
+
var lookalikeRatioSchema = z12.number().min(0.01).max(0.2).refine(
|
|
3890
|
+
(ratio) => Math.abs(Math.round(ratio * 100) - ratio * 100) < 1e-9,
|
|
3891
|
+
"Meta sizes a lookalike in 1% steps (0.01\u20130.20)"
|
|
3892
|
+
);
|
|
3893
|
+
var lookalikeSpecSchema = z12.strictObject({
|
|
3894
|
+
origin: z12.array(
|
|
3895
|
+
z12.strictObject({
|
|
3896
|
+
// An audience this chat creates holds nobody until it publishes and Meta
|
|
3897
|
+
// matches it, and Meta refuses a lookalike of an empty seed (1713008).
|
|
3898
|
+
id: z12.string().regex(
|
|
3899
|
+
NUMERIC_ID_REGEX,
|
|
3900
|
+
"a lookalike's seed must already hold people \u2014 an audience this chat creates is empty until it publishes and Meta matches it (up to a day); make the lookalike in a later chat"
|
|
3901
|
+
)
|
|
3902
|
+
})
|
|
3903
|
+
).min(1).max(1, "Meta builds a lookalike from one seed audience \u2014 make one lookalike per seed").optional(),
|
|
3904
|
+
conversions_of: z12.array(z12.string().regex(NUMERIC_ID_REGEX, "a live campaign or ad set id")).min(1).max(10).optional(),
|
|
3905
|
+
ratio: lookalikeRatioSchema,
|
|
3906
|
+
starting_ratio: z12.number().min(0).max(0.19).refine(
|
|
3907
|
+
(ratio) => Math.abs(Math.round(ratio * 100) - ratio * 100) < 1e-9,
|
|
3908
|
+
"a band starts on a whole percent (0.00, 0.01, \u2026 0.19)"
|
|
3909
|
+
).optional(),
|
|
3910
|
+
country: z12.string().length(2).optional(),
|
|
3911
|
+
countries: z12.array(z12.string().length(2)).min(1).max(50).optional(),
|
|
3912
|
+
allow_international_seeds: z12.boolean().optional()
|
|
3913
|
+
}).superRefine((spec, ctx) => {
|
|
3914
|
+
if (spec.origin === void 0 === (spec.conversions_of === void 0)) {
|
|
3915
|
+
ctx.addIssue({
|
|
3916
|
+
code: "custom",
|
|
3917
|
+
path: ["origin"],
|
|
3918
|
+
message: "a lookalike has one seed: an audience (origin) or the people who converted in live campaigns or ad sets (conversions_of)"
|
|
3919
|
+
});
|
|
3920
|
+
}
|
|
3921
|
+
if (!spec.country && !spec.countries) {
|
|
3922
|
+
ctx.addIssue({
|
|
3923
|
+
code: "custom",
|
|
3924
|
+
path: ["country"],
|
|
3925
|
+
message: "a lookalike is found in a country \u2014 name one (country) or several (countries)"
|
|
3926
|
+
});
|
|
3927
|
+
}
|
|
3928
|
+
if (spec.country && spec.countries) {
|
|
3929
|
+
ctx.addIssue({
|
|
3930
|
+
code: "custom",
|
|
3931
|
+
path: ["countries"],
|
|
3932
|
+
message: "name one country or a list of countries, not both"
|
|
3933
|
+
});
|
|
3934
|
+
}
|
|
3935
|
+
if (spec.starting_ratio !== void 0 && (spec.ratio === void 0 || spec.starting_ratio >= spec.ratio)) {
|
|
3936
|
+
ctx.addIssue({
|
|
3937
|
+
code: "custom",
|
|
3938
|
+
path: ["starting_ratio"],
|
|
3939
|
+
message: "starting_ratio begins a band that ends at ratio, so it needs a larger ratio (0.01 to 0.03 is the 1%\u20133% band)"
|
|
3940
|
+
});
|
|
3941
|
+
}
|
|
3942
|
+
});
|
|
3943
|
+
function valuesOfKey(node, key) {
|
|
3944
|
+
if (Array.isArray(node)) return node.flatMap((entry) => valuesOfKey(entry, key));
|
|
3945
|
+
if (!node || typeof node !== "object") return [];
|
|
3946
|
+
const record = node;
|
|
3947
|
+
return [
|
|
3948
|
+
...key in record ? [record[key]] : [],
|
|
3949
|
+
...Object.values(record).flatMap((value) => valuesOfKey(value, key))
|
|
3950
|
+
];
|
|
3951
|
+
}
|
|
3952
|
+
function mixedEventSourceTypes(rule) {
|
|
3953
|
+
const types = valuesOfKey(rule, "event_sources").flatMap((sources) => Array.isArray(sources) ? sources : []).map((source) => source?.type).filter((type) => typeof type === "string");
|
|
3954
|
+
const distinct = [...new Set(types)].sort();
|
|
3955
|
+
return distinct.length > 1 ? distinct : [];
|
|
3956
|
+
}
|
|
3957
|
+
var customAudienceMemberSchema = z12.strictObject({
|
|
3958
|
+
email: z12.string().min(1).optional(),
|
|
3959
|
+
phone: z12.string().min(1).optional(),
|
|
3960
|
+
/** Two-letter ISO country (US, GB) — Meta reads a phone without its country code against it. */
|
|
3961
|
+
country: z12.string().min(1).optional()
|
|
3962
|
+
}).refine((m) => m.email !== void 0 || m.phone !== void 0, "each person needs an email or a phone");
|
|
3963
|
+
var customAudienceCreateSchema = z12.strictObject({
|
|
3964
|
+
name: z12.string().min(1).max(META_LIMITS.audience.nameMax),
|
|
3965
|
+
subtype: z12.enum(CUSTOM_AUDIENCE_SUBTYPES),
|
|
3966
|
+
description: z12.string().max(500).optional(),
|
|
3967
|
+
customer_file_source: z12.enum(CUSTOMER_FILE_SOURCES).optional(),
|
|
3968
|
+
retention_days: z12.number().int().min(1).max(META_LIMITS.audience.retentionDaysMax).optional(),
|
|
3969
|
+
lookalike_spec: lookalikeSpecSchema.optional(),
|
|
3970
|
+
/** Website/engagement rule — structural passthrough validated by Meta. */
|
|
3971
|
+
rule: z12.record(z12.string(), z12.unknown()).optional(),
|
|
3972
|
+
/** The people of a customer list (subtype CUSTOM), uploaded right after the audience is created at publish. */
|
|
3973
|
+
members: z12.array(customAudienceMemberSchema).min(1).max(META_LIMITS.audience.membersMax).optional(),
|
|
3974
|
+
/**
|
|
3975
|
+
* A value-based customer list: each person carries what they are worth
|
|
3976
|
+
* (the LOOKALIKE_VALUE column), and a lookalike of it finds people like
|
|
3977
|
+
* the most valuable ones. Fixed at creation.
|
|
3978
|
+
*/
|
|
3979
|
+
is_value_based: z12.boolean().optional()
|
|
3980
|
+
}).superRefine((p, ctx) => {
|
|
3981
|
+
if (p.members && p.subtype !== "CUSTOM") {
|
|
3982
|
+
ctx.addIssue({
|
|
3983
|
+
code: "custom",
|
|
3984
|
+
path: ["members"],
|
|
3985
|
+
message: "only a customer list (subtype CUSTOM) is made of people you upload \u2014 drop members or use subtype CUSTOM"
|
|
3986
|
+
});
|
|
3987
|
+
}
|
|
3988
|
+
if (p.members && p.is_value_based) {
|
|
3989
|
+
ctx.addIssue({
|
|
3990
|
+
code: "custom",
|
|
3991
|
+
path: ["members"],
|
|
3992
|
+
message: "a value-based list needs a value for each person \u2014 create it without members, then use audiences members add with a LOOKALIKE_VALUE column"
|
|
3993
|
+
});
|
|
3994
|
+
}
|
|
3995
|
+
if (p.is_value_based && (p.subtype !== "CUSTOM" || !p.customer_file_source)) {
|
|
3996
|
+
ctx.addIssue({
|
|
3997
|
+
code: "custom",
|
|
3998
|
+
path: ["is_value_based"],
|
|
3999
|
+
message: "a value-based audience is a customer list \u2014 subtype CUSTOM with customer_file_source"
|
|
4000
|
+
});
|
|
4001
|
+
}
|
|
4002
|
+
if (p.subtype === "LOOKALIKE" && !p.lookalike_spec) {
|
|
4003
|
+
ctx.addIssue({ code: "custom", path: ["lookalike_spec"], message: "LOOKALIKE audiences need a lookalike_spec" });
|
|
4004
|
+
}
|
|
4005
|
+
if ((p.subtype === "WEBSITE" || p.subtype === "ENGAGEMENT") && !p.rule) {
|
|
4006
|
+
ctx.addIssue({ code: "custom", path: ["rule"], message: `${p.subtype} audiences need a rule (use --file)` });
|
|
4007
|
+
}
|
|
4008
|
+
if (p.rule && p.retention_days !== void 0) {
|
|
4009
|
+
ctx.addIssue({
|
|
4010
|
+
code: "custom",
|
|
4011
|
+
path: ["retention_days"],
|
|
4012
|
+
message: "a rule carries its own window in retention_seconds, so drop retention_days \u2014 Meta takes it only on a customer-file audience, and sending both leaves the review showing a window the audience will not have"
|
|
4013
|
+
});
|
|
4014
|
+
}
|
|
4015
|
+
const mixed = mixedEventSourceTypes(p.rule);
|
|
4016
|
+
if (mixed.length > 0) {
|
|
4017
|
+
ctx.addIssue({
|
|
4018
|
+
code: "custom",
|
|
4019
|
+
path: ["rule"],
|
|
4020
|
+
message: `one audience covers one kind of source, but this rule mixes ${mixed.join(" and ")} \u2014 split it into one audience per source`
|
|
4021
|
+
});
|
|
4022
|
+
}
|
|
4023
|
+
});
|
|
4024
|
+
var customAudienceUpdateSchema = z12.strictObject({
|
|
4025
|
+
name: z12.string().min(1).max(META_LIMITS.audience.nameMax).optional(),
|
|
4026
|
+
description: z12.string().max(500).optional(),
|
|
4027
|
+
customer_file_source: z12.enum(CUSTOMER_FILE_SOURCES).optional(),
|
|
4028
|
+
opt_out_link: httpsUrlSchema5.optional(),
|
|
4029
|
+
rule: z12.record(z12.string(), z12.unknown()).optional(),
|
|
4030
|
+
status: z12.literal("DELETED").optional()
|
|
4031
|
+
}).superRefine((p, ctx) => {
|
|
4032
|
+
if (!Object.values(p).some((val) => val !== void 0)) {
|
|
4033
|
+
ctx.addIssue({ code: "custom", message: "update needs at least one field" });
|
|
4034
|
+
}
|
|
4035
|
+
if (p.status === "DELETED" && Object.values(p).filter((val) => val !== void 0).length > 1) {
|
|
4036
|
+
ctx.addIssue({ code: "custom", path: ["status"], message: "a deleted audience takes no other change" });
|
|
4037
|
+
}
|
|
4038
|
+
const mixed = mixedEventSourceTypes(p.rule);
|
|
4039
|
+
if (mixed.length > 0) {
|
|
4040
|
+
ctx.addIssue({
|
|
4041
|
+
code: "custom",
|
|
4042
|
+
path: ["rule"],
|
|
4043
|
+
message: `one audience covers one kind of source, but this rule mixes ${mixed.join(" and ")} \u2014 split it into one audience per source`
|
|
4044
|
+
});
|
|
4045
|
+
}
|
|
4046
|
+
});
|
|
4047
|
+
var customConversionCreateSchema = z12.strictObject({
|
|
4048
|
+
name: z12.string().min(1).max(META_LIMITS.customConversion.nameMax),
|
|
4049
|
+
description: z12.string().max(META_LIMITS.customConversion.descriptionMax).optional(),
|
|
4050
|
+
/** The pixel the conversion counts from — `event_source_id` on the wire. Baker never creates pixels, so this is always a real id. */
|
|
4051
|
+
pixel_id: z12.string().regex(NUMERIC_ID_REGEX),
|
|
4052
|
+
custom_event_type: z12.enum(CUSTOM_EVENT_TYPES),
|
|
4053
|
+
/** Meta's filter JSON. Built by `buildCustomConversionRule` for the URL case. */
|
|
4054
|
+
rule: z12.record(z12.string(), z12.unknown()),
|
|
4055
|
+
/** What one conversion is worth, when the site cannot send a value with the event. */
|
|
4056
|
+
default_conversion_value: z12.number().min(0).optional()
|
|
4057
|
+
}).superRefine((p, ctx) => {
|
|
4058
|
+
const refusal = customConversionRuleRefusal(p.rule);
|
|
4059
|
+
if (refusal) {
|
|
4060
|
+
ctx.addIssue({ code: "custom", path: ["rule"], message: refusal });
|
|
4061
|
+
}
|
|
4062
|
+
});
|
|
4063
|
+
var customConversionUpdateSchema = z12.strictObject({
|
|
4064
|
+
name: z12.string().min(1).max(META_LIMITS.customConversion.nameMax).optional(),
|
|
4065
|
+
description: z12.string().max(META_LIMITS.customConversion.descriptionMax).optional(),
|
|
4066
|
+
default_conversion_value: z12.number().min(0).optional(),
|
|
4067
|
+
status: z12.literal("DELETED").optional()
|
|
4068
|
+
}).superRefine((p, ctx) => {
|
|
4069
|
+
const set = Object.values(p).filter((val) => val !== void 0);
|
|
4070
|
+
if (set.length === 0) {
|
|
4071
|
+
ctx.addIssue({ code: "custom", message: "update needs at least one field" });
|
|
4072
|
+
}
|
|
4073
|
+
if (p.status === "DELETED" && set.length > 1) {
|
|
4074
|
+
ctx.addIssue({ code: "custom", path: ["status"], message: "a deleted conversion takes no other change" });
|
|
4075
|
+
}
|
|
4076
|
+
});
|
|
4077
|
+
var adLabelCreateSchema = z12.strictObject({
|
|
4078
|
+
name: z12.string().trim().min(1).max(META_LIMITS.adLabel.nameMax)
|
|
4079
|
+
});
|
|
4080
|
+
var adLabelUpdateSchema = z12.strictObject({
|
|
4081
|
+
name: z12.string().trim().min(1).max(META_LIMITS.adLabel.nameMax).optional(),
|
|
4082
|
+
status: z12.literal("DELETED").optional()
|
|
4083
|
+
}).refine((p) => p.name === void 0 !== (p.status === void 0), "rename the label or delete it \u2014 one of them");
|
|
4084
|
+
var mediaDeleteSchema = z12.strictObject({
|
|
4085
|
+
imageHash: z12.string().regex(IMAGE_HASH_REGEX).optional(),
|
|
4086
|
+
/**
|
|
4087
|
+
* Or a video, taken out of the ad account's video library
|
|
4088
|
+
* (`DELETE /act_<id>/advideos?video_id=`). Live on v26.0, `DELETE /{video_id}`
|
|
4089
|
+
* was refused with the user token and the Page token alike (1363055 "You
|
|
4090
|
+
* might not have permission to delete this video"), with `pages_manage_posts`
|
|
4091
|
+
* granted; the library delete took it off the account.
|
|
4092
|
+
*/
|
|
4093
|
+
videoId: z12.string().regex(NUMERIC_ID_REGEX).optional()
|
|
4094
|
+
}).refine((p) => p.imageHash === void 0 !== (p.videoId === void 0), "name imageHash OR videoId");
|
|
4095
|
+
var mediaUploadSchema = z12.strictObject({
|
|
4096
|
+
kind: z12.enum(MEDIA_KINDS),
|
|
4097
|
+
bakerImageId: bakerMediaIdSchema.optional(),
|
|
4098
|
+
bakerVideoId: bakerMediaIdSchema.optional(),
|
|
4099
|
+
/** Optional display name / filename hint. */
|
|
4100
|
+
name: z12.string().max(255).optional()
|
|
4101
|
+
}).superRefine((p, ctx) => {
|
|
4102
|
+
if (p.kind === "image" && !p.bakerImageId) {
|
|
4103
|
+
ctx.addIssue({ code: "custom", path: ["bakerImageId"], message: "image uploads need a bakerImageId" });
|
|
4104
|
+
}
|
|
4105
|
+
if (p.kind === "video" && !p.bakerVideoId) {
|
|
4106
|
+
ctx.addIssue({ code: "custom", path: ["bakerVideoId"], message: "video uploads need a bakerVideoId" });
|
|
4107
|
+
}
|
|
4108
|
+
});
|
|
4109
|
+
var adAccountUpdateSchema = z12.strictObject({
|
|
4110
|
+
name: z12.string().trim().min(1).max(100).optional(),
|
|
4111
|
+
/** The account spending limit — spending stops when the account has spent this since it was set. */
|
|
4112
|
+
spendCap: moneySchema.optional(),
|
|
4113
|
+
/** `remove`: no limit at all. `reset`: start counting toward the limit again from zero. */
|
|
4114
|
+
spendCapAction: z12.enum(["remove", "reset"]).optional(),
|
|
4115
|
+
/**
|
|
4116
|
+
* The EU (DSA) advertiser and payer every new ad set on the account starts
|
|
4117
|
+
* with (`default_dsa_beneficiary` / `default_dsa_payor`). Meta keeps both or
|
|
4118
|
+
* neither, and clears them with two empty strings (`clearDefaultDsa`).
|
|
4119
|
+
* Written on the account node, which ignores `validate_only`.
|
|
4120
|
+
*/
|
|
4121
|
+
defaultDsaBeneficiary: z12.string().trim().min(1).max(512).optional(),
|
|
4122
|
+
defaultDsaPayor: z12.string().trim().min(1).max(512).optional(),
|
|
4123
|
+
clearDefaultDsa: z12.literal(true).optional()
|
|
4124
|
+
}).superRefine((p, ctx) => {
|
|
4125
|
+
const dsa = [p.defaultDsaBeneficiary, p.defaultDsaPayor].filter((value) => value !== void 0).length;
|
|
4126
|
+
if (p.name === void 0 && p.spendCap === void 0 && p.spendCapAction === void 0 && dsa === 0 && !p.clearDefaultDsa) {
|
|
4127
|
+
ctx.addIssue({
|
|
4128
|
+
code: "custom",
|
|
4129
|
+
message: "name, spendCap, spendCapAction, the default EU advertiser and payer, or clearDefaultDsa is required"
|
|
4130
|
+
});
|
|
4131
|
+
}
|
|
4132
|
+
if (dsa === 1) {
|
|
4133
|
+
ctx.addIssue({
|
|
4134
|
+
code: "custom",
|
|
4135
|
+
path: ["defaultDsaPayor"],
|
|
4136
|
+
message: "Meta keeps the default EU advertiser and payer together \u2014 pass both (the same name when one business does both)"
|
|
4137
|
+
});
|
|
4138
|
+
}
|
|
4139
|
+
if (dsa > 0 && p.clearDefaultDsa) {
|
|
4140
|
+
ctx.addIssue({ code: "custom", path: ["clearDefaultDsa"], message: "set the defaults OR clear them, not both" });
|
|
4141
|
+
}
|
|
4142
|
+
if (p.spendCap !== void 0 && p.spendCapAction === "remove") {
|
|
4143
|
+
ctx.addIssue({
|
|
4144
|
+
code: "custom",
|
|
4145
|
+
path: ["spendCapAction"],
|
|
4146
|
+
message: "set a spending limit OR remove it, not both"
|
|
4147
|
+
});
|
|
4148
|
+
}
|
|
4149
|
+
});
|
|
4150
|
+
var META_DRAFT_OP_KINDS = [
|
|
4151
|
+
"campaign.create",
|
|
4152
|
+
"campaign.update",
|
|
4153
|
+
"adSet.create",
|
|
4154
|
+
"adSet.update",
|
|
4155
|
+
"adCreative.create",
|
|
4156
|
+
"adCreative.update",
|
|
4157
|
+
"ad.create",
|
|
4158
|
+
"ad.update",
|
|
4159
|
+
"customAudience.create",
|
|
4160
|
+
"customAudience.update",
|
|
4161
|
+
"customAudience.members",
|
|
4162
|
+
"customConversion.create",
|
|
4163
|
+
"customConversion.update",
|
|
4164
|
+
"leadForm.create",
|
|
4165
|
+
"leadForm.update",
|
|
4166
|
+
"media.upload",
|
|
4167
|
+
"media.delete",
|
|
4168
|
+
"adLabel.create",
|
|
4169
|
+
"adLabel.update",
|
|
4170
|
+
"adRule.create",
|
|
4171
|
+
"adRule.update",
|
|
4172
|
+
"adStudy.create",
|
|
4173
|
+
"adStudy.update",
|
|
4174
|
+
"campaign.copy",
|
|
4175
|
+
"adSet.copy",
|
|
4176
|
+
"ad.copy",
|
|
4177
|
+
"canvas.create",
|
|
4178
|
+
"canvas.update",
|
|
4179
|
+
"productCatalog.create",
|
|
4180
|
+
"productCatalog.update",
|
|
4181
|
+
"product.create",
|
|
4182
|
+
"product.update",
|
|
4183
|
+
"productSet.create",
|
|
4184
|
+
"productSet.update",
|
|
4185
|
+
"productFeed.create",
|
|
4186
|
+
"productFeed.update",
|
|
4187
|
+
"pagePhoto.create",
|
|
4188
|
+
"pagePhoto.update",
|
|
4189
|
+
"adAccount.update",
|
|
4190
|
+
"publisherBlockList.create",
|
|
4191
|
+
"publisherBlockList.update",
|
|
4192
|
+
"reachFrequencyReservation.create",
|
|
4193
|
+
"reachFrequencyReservation.update"
|
|
4194
|
+
];
|
|
4195
|
+
var metaDraftOpKindSchema = z12.enum(META_DRAFT_OP_KINDS);
|
|
4196
|
+
var accountIdSchema = z12.string().regex(NUMERIC_ID_REGEX, "accountId must be the bare numeric ad account id");
|
|
4197
|
+
var updateTargetSchema = z12.union([z12.string().regex(NUMERIC_ID_REGEX), tempRefSchema3]);
|
|
4198
|
+
function createOp(kind, payload) {
|
|
4199
|
+
return z12.object({ kind: z12.literal(kind), accountId: accountIdSchema, payload });
|
|
4200
|
+
}
|
|
4201
|
+
function updateOp(kind, payload) {
|
|
4202
|
+
return z12.object({ kind: z12.literal(kind), accountId: accountIdSchema, target: updateTargetSchema, payload });
|
|
4203
|
+
}
|
|
4204
|
+
function budgetUpdateOp(kind, payload) {
|
|
4205
|
+
return updateOp(kind, payload).extend({ confirmBudgetIncrease: z12.boolean().optional() });
|
|
4206
|
+
}
|
|
4207
|
+
var metaDraftOpInputSchema = z12.discriminatedUnion("kind", [
|
|
4208
|
+
createOp("campaign.create", campaignCreateSchema),
|
|
4209
|
+
budgetUpdateOp("campaign.update", campaignUpdateSchema),
|
|
4210
|
+
createOp("adSet.create", adSetCreateSchema),
|
|
4211
|
+
budgetUpdateOp("adSet.update", adSetUpdateSchema),
|
|
4212
|
+
createOp("adCreative.create", adCreativeCreateSchema),
|
|
4213
|
+
updateOp("adCreative.update", adCreativeUpdateSchema),
|
|
4214
|
+
createOp("ad.create", adCreateSchema),
|
|
4215
|
+
updateOp("ad.update", adUpdateSchema),
|
|
4216
|
+
createOp("customAudience.create", customAudienceCreateSchema),
|
|
4217
|
+
updateOp("customAudience.update", customAudienceUpdateSchema),
|
|
4218
|
+
createOp("customAudience.members", customAudienceMembersSchema),
|
|
4219
|
+
createOp("customConversion.create", customConversionCreateSchema),
|
|
4220
|
+
updateOp("customConversion.update", customConversionUpdateSchema),
|
|
4221
|
+
createOp("leadForm.create", leadFormCreateSchema),
|
|
4222
|
+
updateOp("leadForm.update", leadFormUpdateSchema),
|
|
4223
|
+
createOp("media.upload", mediaUploadSchema),
|
|
4224
|
+
createOp("media.delete", mediaDeleteSchema),
|
|
4225
|
+
createOp("adLabel.create", adLabelCreateSchema),
|
|
4226
|
+
updateOp("adLabel.update", adLabelUpdateSchema),
|
|
4227
|
+
createOp("adRule.create", adRuleCreateSchema),
|
|
4228
|
+
updateOp("adRule.update", adRuleUpdateSchema),
|
|
4229
|
+
createOp("adStudy.create", adStudyCreateSchema),
|
|
4230
|
+
updateOp("adStudy.update", adStudyUpdateSchema),
|
|
4231
|
+
createOp("campaign.copy", campaignCopySchema),
|
|
4232
|
+
createOp("adSet.copy", adSetCopySchema),
|
|
4233
|
+
createOp("ad.copy", adCopySchema),
|
|
4234
|
+
createOp("canvas.create", canvasCreateSchema),
|
|
4235
|
+
updateOp("canvas.update", canvasUpdateSchema),
|
|
4236
|
+
createOp("productCatalog.create", productCatalogCreateSchema),
|
|
4237
|
+
updateOp("productCatalog.update", productCatalogUpdateSchema),
|
|
4238
|
+
createOp("product.create", productCreateSchema),
|
|
4239
|
+
updateOp("product.update", productUpdateSchema),
|
|
4240
|
+
createOp("productSet.create", productSetCreateSchema),
|
|
4241
|
+
updateOp("productSet.update", productSetUpdateSchema),
|
|
4242
|
+
createOp("productFeed.create", productFeedCreateSchema),
|
|
4243
|
+
updateOp("productFeed.update", productFeedUpdateSchema),
|
|
4244
|
+
createOp("pagePhoto.create", pagePhotoCreateSchema),
|
|
4245
|
+
updateOp("pagePhoto.update", pagePhotoUpdateSchema),
|
|
4246
|
+
updateOp("adAccount.update", adAccountUpdateSchema),
|
|
4247
|
+
createOp("publisherBlockList.create", publisherBlockListCreateSchema),
|
|
4248
|
+
updateOp("publisherBlockList.update", publisherBlockListUpdateSchema),
|
|
4249
|
+
createOp("reachFrequencyReservation.create", reachFrequencyReservationCreateSchema),
|
|
4250
|
+
updateOp("reachFrequencyReservation.update", reachFrequencyReservationUpdateSchema)
|
|
4251
|
+
]);
|
|
4252
|
+
|
|
4253
|
+
export {
|
|
4254
|
+
buildCustomConversionRule,
|
|
4255
|
+
isMetaLeadAdsRefusal,
|
|
4256
|
+
FACEBOOK_PREVIEW_FORMATS,
|
|
4257
|
+
INSTAGRAM_PREVIEW_FORMATS,
|
|
4258
|
+
PLATFORM_SCOPED_POSITIONS,
|
|
4259
|
+
previewFormatForPosition,
|
|
4260
|
+
placementFormatNote,
|
|
4261
|
+
brandSafetyTargetingFields,
|
|
4262
|
+
retailerIdFilter,
|
|
4263
|
+
CUSTOMER_LIST_KEYS,
|
|
4264
|
+
UNHASHED_CUSTOMER_LIST_KEYS,
|
|
4265
|
+
IDENTIFYING_CUSTOMER_LIST_KEYS,
|
|
4266
|
+
CUSTOMER_LIST_BATCH_ROWS,
|
|
4267
|
+
CUSTOMER_LIST_MAX_BATCHES,
|
|
4268
|
+
customerListPreviewCell,
|
|
4269
|
+
customerListBatchSchema,
|
|
4270
|
+
customAudienceMembersSchema,
|
|
4271
|
+
metaTargetingSchema,
|
|
4272
|
+
metaDraftOpInputSchema
|
|
4273
|
+
};
|
|
4274
|
+
//# sourceMappingURL=chunk-Q3GSVLE5.js.map
|