@rebuy/rebuy 3.6.0 → 3.8.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.
@@ -301,7 +301,29 @@ var Metadata = import_zod2.z.object({
301
301
  unmatchedRules: import_zod2.z.array(MatchedRule).optional(),
302
302
  widget: import_zod2.z.object({
303
303
  discount: import_zod2.z.object({ amount: import_zod2.z.string(), type: import_zod2.z.string() }).optional(),
304
- language: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.string()).optional()
304
+ /**
305
+ * Data-source-authored text content, dual-purpose in the CAB renderer (rebuy-shopify-extensions'
306
+ * `checkout-and-beyond`): every key becomes a `{{token}}` for the interpolation pass
307
+ * (`extractDataVariables`), e.g. `{ superTitle: 'Limited drop' }` resolves `{{superTitle}}`
308
+ * anywhere in the widget's copy, and the legacy add-to-order state keys (`extractLabelOverrides`)
309
+ * plus `variantOption`/`variantOptions` (`extractVariantLabels`) additionally keep their
310
+ * whole-label override behavior. The serve path passes metadata through verbatim
311
+ * (`fetchDataSourceResults` only camelCases keys, which lands snake_case names in the canonical
312
+ * token grammar); this schema is the contract of record. Parsing mirrors the renderer's
313
+ * fail-safe: an entry whose value is not a string or finite number — a data-source author's
314
+ * `null`, boolean, object, or a computed `NaN`/`Infinity` — is dropped at parse time rather
315
+ * than failing the whole `Metadata` block or rendering `[object Object]` (or a literal `NaN`)
316
+ * into buyer copy. Booleans stay excluded on purpose (unlike
317
+ * `Output.value` above, this is buyer-facing copy); an empty string is kept — it deliberately
318
+ * blanks a token.
319
+ */
320
+ language: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.unknown()).transform(
321
+ (raw) => Object.fromEntries(
322
+ Object.entries(raw).filter(
323
+ (entry) => typeof entry[1] === "string" || Number.isFinite(entry[1])
324
+ )
325
+ )
326
+ ).optional()
305
327
  }).optional()
306
328
  });
307
329
 
@@ -1456,7 +1478,13 @@ var import_zod20 = require("zod");
1456
1478
  var CABMonetizeSection = import_zod20.z.object({
1457
1479
  ...CABVariableFields,
1458
1480
  name: import_zod20.z.string().optional(),
1459
- /** Privacy Policy footer href (the widget's `privacy_policy_url` setting); the client relays it to `/cab/monetize`. */
1481
+ /**
1482
+ * Server-hydrated offer cards (inline hydration): when `/cab/sections` was asked with a `monetize`
1483
+ * context block, the composed cards ride the marker itself — `[]` means "hydrated, no fill" (render
1484
+ * nothing, skip the fetch); ABSENT means "not hydrated" (the client fetches via `/cab/monetize`).
1485
+ */
1486
+ offers: import_zod20.z.array(import_zod20.z.lazy(() => CABSection)).optional(),
1487
+ /** Privacy Policy footer href (the widget's `privacy_policy_url` setting); read server-side for hydration, relayed by the client only on the legacy `/cab/monetize` fallback. */
1460
1488
  privacyPolicyUrl: import_zod20.z.string().optional(),
1461
1489
  sectionId: import_zod20.z.uuid().default(() => (0, import_uuid8.v7)()),
1462
1490
  sectionType: import_zod20.z.literal(SectionType.monetize).default(SectionType.monetize),
@@ -3821,184 +3849,6 @@ var fetchDataSourceResults = async (input, ctx) => {
3821
3849
  };
3822
3850
  };
3823
3851
 
3824
- // src/server/salvageSections.ts
3825
- var import_uuid14 = require("uuid");
3826
- var STUB_SECTION_TYPE = "unknown";
3827
- var knownSectionTypes = new Set(sectionTypes);
3828
- var hasSectionsArray = (raw) => typeof raw === "object" && raw !== null && Array.isArray(raw.sections);
3829
- var stubSection = (raw, report) => {
3830
- const record = typeof raw === "object" && raw !== null ? raw : {};
3831
- const original = record.sectionType;
3832
- const rawId = record.sectionId;
3833
- report?.({
3834
- detail: `invalid section (${typeof original === "string" ? original : "no sectionType"})`,
3835
- kind: "section"
3836
- });
3837
- return {
3838
- sectionId: typeof rawId === "string" && rawId ? rawId : (0, import_uuid14.v7)(),
3839
- sectionType: typeof original === "string" && !knownSectionTypes.has(original) ? original : STUB_SECTION_TYPE
3840
- };
3841
- };
3842
- var salvageSection = (raw, report) => {
3843
- const parsed = CABSection.safeParse(raw);
3844
- if (parsed.success) return parsed.data;
3845
- if (hasSectionsArray(raw)) {
3846
- const shell = CABSection.safeParse({ ...raw, sections: [] });
3847
- if (shell.success)
3848
- return { ...shell.data, sections: raw.sections.map((child) => salvageSection(child, report)) };
3849
- }
3850
- return stubSection(raw, report);
3851
- };
3852
- var salvageRootSection = (raw, report) => {
3853
- const shell = CABRootSection.safeParse(hasSectionsArray(raw) ? { ...raw, sections: [] } : raw);
3854
- if (!shell.success) return CABRootSection.parse(raw);
3855
- const sections = hasSectionsArray(raw) ? raw.sections.map((child) => salvageSection(child, report)) : [];
3856
- return { ...shell.data, sections };
3857
- };
3858
-
3859
- // src/server/widgetSettings.ts
3860
- var isMissingUpstreamPayload = isEmptyObject;
3861
- var dispatchWidgetTransform = (id, normalized, report) => {
3862
- if (isCABRootSection(normalized)) return salvageRootSection(normalizeTextContent(normalized), report);
3863
- if (isContentBlockV1(normalized)) return convertContentBlockToV2({ id, settings: normalized });
3864
- if (isLineItemEditorV1(normalized)) return convertLineItemEditorToV2({ id, settings: normalized });
3865
- if (isMonetizeV1(normalized)) return convertMonetizeToV2({ id, settings: normalized });
3866
- if (isOfferV1(normalized)) return convertOfferToV2({ id, name: normalized.name, settings: normalized });
3867
- return normalized;
3868
- };
3869
- var fetchProgressBarContext = async (shop, ctx) => {
3870
- const config = await fetchUserConfig({ shop }, ctx).catch(() => void 0);
3871
- if (!config) return {};
3872
- if (config.shop.billingVersion === "v6" && config.shop.activePackages.some((pkg) => pkg.shortName === "")) {
3873
- ctx.reportDrift?.({
3874
- detail: "shop config active package entry inerted (drifted shape) \u2014 smart-cart entitlement may misgate",
3875
- kind: "shopConfig"
3876
- });
3877
- }
3878
- const entitled = config.shop.billingVersion !== "v6" || config.shop.activePackages.some((pkg) => pkg.shortName === "cart-merchandising" && pkg.isActive);
3879
- return {
3880
- bars: entitled ? extractSmartCartBars(config.smartCart) : [],
3881
- progressColor: await fetchThemeColor(config.shop.shopId, ctx)
3882
- };
3883
- };
3884
- var fetchThemeColor = async (shopId, ctx) => {
3885
- if (!shopId) return void 0;
3886
- try {
3887
- const theme = await ctx.fetchUpstream(`/admin/api/v1/shop/theme/${shopId}`, {}, { host: ctx.host });
3888
- const color = theme.theme?.settings?.buttonBackground;
3889
- return typeof color === "string" && color ? color : void 0;
3890
- } catch {
3891
- return void 0;
3892
- }
3893
- };
3894
- var colorlessProgressBars = (section) => {
3895
- const own = section.sectionType === SectionType.progressBar && section.progressColor === void 0;
3896
- const nested = (section.sections ?? []).flatMap((child) => colorlessProgressBars(child));
3897
- return own ? [section, ...nested] : nested;
3898
- };
3899
- var fetchAndConvert = async (id, input, ctx) => {
3900
- const params = { id: String(id), shop: input.shop };
3901
- if (input.cacheKey != null) params.cache_key = String(input.cacheKey);
3902
- if (input.key) params.key = input.key;
3903
- let upstream;
3904
- try {
3905
- upstream = await ctx.fetchUpstream("/api/v1/widgets/settings", params, { host: ctx.host });
3906
- } catch (err) {
3907
- if (err instanceof UpstreamError && [400, 404, 410].includes(err.status)) {
3908
- throw new NotFoundError(`Widget not found: ${id}`);
3909
- }
3910
- throw err;
3911
- }
3912
- const data = unwrapData(upstream);
3913
- if (isMissingUpstreamPayload(data)) throw new NotFoundError(`Widget not found: ${id}`);
3914
- const normalized = convertNumericObjects(deepCamelCase(data));
3915
- if (isProgressBarV1(normalized) && normalized.barSettings.barType === "smart-cart") {
3916
- const { bars, progressColor } = await fetchProgressBarContext(input.shop, ctx);
3917
- return convertSmartCartProgressBarToV2({ bars: bars ?? [], id, progressColor, settings: normalized });
3918
- }
3919
- if (isConvertibleProgressBarV1(normalized)) {
3920
- const { progressColor } = await fetchProgressBarContext(input.shop, ctx);
3921
- return convertProgressBarToV2({ id, progressColor, settings: normalized });
3922
- }
3923
- const converted = dispatchWidgetTransform(id, normalized, ctx.reportDrift);
3924
- const colorless = isCABRootSection(normalized) ? colorlessProgressBars(converted) : [];
3925
- if (colorless.length > 0) {
3926
- const { progressColor: color } = await fetchProgressBarContext(input.shop, ctx);
3927
- if (color) for (const bar of colorless) bar.progressColor = color;
3928
- }
3929
- return converted;
3930
- };
3931
- var resolveWidgetExperiment = async (input, ctx) => {
3932
- if (!input.visitorId) return void 0;
3933
- try {
3934
- const { shop } = await fetchUserConfig({ shop: input.shop }, ctx);
3935
- return resolveExperiment(shop.activeExperiments, input.id, input.visitorId);
3936
- } catch {
3937
- return void 0;
3938
- }
3939
- };
3940
- var isCABTree = (result) => result.version === 2;
3941
- var convertWidgetSettings = async (input, ctx) => {
3942
- const experiment = await resolveWidgetExperiment(input, ctx);
3943
- const result = await fetchAndConvert(experiment?.elementId ?? input.id, input, ctx);
3944
- return experiment && isCABTree(result) ? { ...result, experiment } : result;
3945
- };
3946
-
3947
- // src/server/giftValidation.ts
3948
- var resolveDataSourcePath = async (widgetId, shop, ctx) => {
3949
- const raw = await ctx.fetchUpstream("/api/v1/widgets/settings", { id: String(widgetId), shop }, { host: ctx.host });
3950
- const data = raw && typeof raw === "object" && "data" in raw ? raw.data : raw;
3951
- const endpoint = deepCamelCase(data)?.endpoint;
3952
- return typeof endpoint === "string" && endpoint ? endpoint : void 0;
3953
- };
3954
- var groupByWidget = (gifts) => {
3955
- const byWidget = /* @__PURE__ */ new Map();
3956
- for (const gift of gifts) {
3957
- byWidget.set(gift.widgetId, [...byWidget.get(gift.widgetId) ?? [], gift]);
3958
- }
3959
- return byWidget;
3960
- };
3961
- var validateGifts = async (input, ctx) => {
3962
- const perWidget = await Promise.all(
3963
- [...groupByWidget(input.gifts)].map(async ([widgetId, gifts]) => {
3964
- const discountLost = gifts.filter(hasLostDiscount).map((gift) => gift.lineId);
3965
- try {
3966
- const dataSourcePath = await resolveDataSourcePath(widgetId, input.shop, ctx);
3967
- if (!dataSourcePath) return discountLost;
3968
- const { metadata } = await fetchDataSourceResults(
3969
- {
3970
- cart: input.cart,
3971
- /** Market context threads through so a market-gated gift rule answers for the buyer's market — as engine params, inheriting the shop's markets-toggle gate (REB-21688). */
3972
- country: input.country,
3973
- customerId: input.customerId,
3974
- dataSourcePath,
3975
- /** The gift being validated is in the cart — input filtering would erase it from the answer. */
3976
- filterInputs: "no",
3977
- language: input.language,
3978
- /**
3979
- * `limit: 0` fetches NO products — only the rule verdict is needed. Safe because the engine
3980
- * derives `matchedRules` from rule-config evaluation, not from fetched results (traced through
3981
- * `api.php`), so the metadata is complete at limit 0. Don't raise it to "fix" a non-bug.
3982
- */
3983
- limit: 0,
3984
- shop: input.shop,
3985
- visitorId: input.visitorId
3986
- },
3987
- ctx
3988
- );
3989
- const meta = metadata ?? void 0;
3990
- if (!isDefinitiveRuleAnswer(meta) || hasUnsupportedRuleTypes(meta)) return discountLost;
3991
- const validIds = getValidGwpProductIds(meta.matchedRules);
3992
- if (validIds.size === 0 && !isConfirmedRuleMiss(meta)) return discountLost;
3993
- return gifts.filter((gift) => shouldRemoveGift(gift, validIds)).map((gift) => gift.lineId);
3994
- } catch {
3995
- return discountLost;
3996
- }
3997
- })
3998
- );
3999
- return { remove: perWidget.flat() };
4000
- };
4001
-
4002
3852
  // src/server/adsEngine.ts
4003
3853
  var import_zod28 = require("zod");
4004
3854
  var SUPPLY_KEY = { production: "AQF5RKLR", staging: "Z56LQ7PS" };
@@ -4045,6 +3895,7 @@ var buildAdsParams = (input, publisherKey, supplyKey, testing, userAgent, ipAddr
4045
3895
  testing: String(testing),
4046
3896
  transaction_total: String(input.transactionTotal ?? input.cart.subtotal),
4047
3897
  ...items.length > 0 && { transaction_items: JSON.stringify(items) },
3898
+ ...input.clientRequestId && { client_request_id: input.clientRequestId },
4048
3899
  ...input.country && { country_code: input.country },
4049
3900
  ...input.currency && { currency_code: input.currency },
4050
3901
  ...input.customerEmail && { customer_email: input.customerEmail },
@@ -4228,6 +4079,216 @@ var fetchMonetizeOffers = async (input, ctx) => {
4228
4079
  return { offers };
4229
4080
  };
4230
4081
 
4082
+ // src/server/salvageSections.ts
4083
+ var import_uuid14 = require("uuid");
4084
+ var STUB_SECTION_TYPE = "unknown";
4085
+ var knownSectionTypes = new Set(sectionTypes);
4086
+ var hasSectionsArray = (raw) => typeof raw === "object" && raw !== null && Array.isArray(raw.sections);
4087
+ var stubSection = (raw, report) => {
4088
+ const record = typeof raw === "object" && raw !== null ? raw : {};
4089
+ const original = record.sectionType;
4090
+ const rawId = record.sectionId;
4091
+ report?.({
4092
+ detail: `invalid section (${typeof original === "string" ? original : "no sectionType"})`,
4093
+ kind: "section"
4094
+ });
4095
+ return {
4096
+ sectionId: typeof rawId === "string" && rawId ? rawId : (0, import_uuid14.v7)(),
4097
+ sectionType: typeof original === "string" && !knownSectionTypes.has(original) ? original : STUB_SECTION_TYPE
4098
+ };
4099
+ };
4100
+ var salvageSection = (raw, report) => {
4101
+ const parsed = CABSection.safeParse(raw);
4102
+ if (parsed.success) return parsed.data;
4103
+ if (hasSectionsArray(raw)) {
4104
+ const shell = CABSection.safeParse({ ...raw, sections: [] });
4105
+ if (shell.success)
4106
+ return { ...shell.data, sections: raw.sections.map((child) => salvageSection(child, report)) };
4107
+ }
4108
+ return stubSection(raw, report);
4109
+ };
4110
+ var salvageRootSection = (raw, report) => {
4111
+ const shell = CABRootSection.safeParse(hasSectionsArray(raw) ? { ...raw, sections: [] } : raw);
4112
+ if (!shell.success) return CABRootSection.parse(raw);
4113
+ const sections = hasSectionsArray(raw) ? raw.sections.map((child) => salvageSection(child, report)) : [];
4114
+ return { ...shell.data, sections };
4115
+ };
4116
+
4117
+ // src/server/widgetSettings.ts
4118
+ var isMissingUpstreamPayload = isEmptyObject;
4119
+ var dispatchWidgetTransform = (id, normalized, report) => {
4120
+ if (isCABRootSection(normalized)) return salvageRootSection(normalizeTextContent(normalized), report);
4121
+ if (isContentBlockV1(normalized)) return convertContentBlockToV2({ id, settings: normalized });
4122
+ if (isLineItemEditorV1(normalized)) return convertLineItemEditorToV2({ id, settings: normalized });
4123
+ if (isMonetizeV1(normalized)) return convertMonetizeToV2({ id, settings: normalized });
4124
+ if (isOfferV1(normalized)) return convertOfferToV2({ id, name: normalized.name, settings: normalized });
4125
+ return normalized;
4126
+ };
4127
+ var fetchProgressBarContext = async (shop, ctx) => {
4128
+ const config = await fetchUserConfig({ shop }, ctx).catch(() => void 0);
4129
+ if (!config) return {};
4130
+ if (config.shop.billingVersion === "v6" && config.shop.activePackages.some((pkg) => pkg.shortName === "")) {
4131
+ ctx.reportDrift?.({
4132
+ detail: "shop config active package entry inerted (drifted shape) \u2014 smart-cart entitlement may misgate",
4133
+ kind: "shopConfig"
4134
+ });
4135
+ }
4136
+ const entitled = config.shop.billingVersion !== "v6" || config.shop.activePackages.some((pkg) => pkg.shortName === "cart-merchandising" && pkg.isActive);
4137
+ return {
4138
+ bars: entitled ? extractSmartCartBars(config.smartCart) : [],
4139
+ progressColor: await fetchThemeColor(config.shop.shopId, ctx)
4140
+ };
4141
+ };
4142
+ var fetchThemeColor = async (shopId, ctx) => {
4143
+ if (!shopId) return void 0;
4144
+ try {
4145
+ const theme = await ctx.fetchUpstream(`/admin/api/v1/shop/theme/${shopId}`, {}, { host: ctx.host });
4146
+ const color = theme.theme?.settings?.buttonBackground;
4147
+ return typeof color === "string" && color ? color : void 0;
4148
+ } catch {
4149
+ return void 0;
4150
+ }
4151
+ };
4152
+ var colorlessProgressBars = (section) => {
4153
+ const own = section.sectionType === SectionType.progressBar && section.progressColor === void 0;
4154
+ const nested = (section.sections ?? []).flatMap((child) => colorlessProgressBars(child));
4155
+ return own ? [section, ...nested] : nested;
4156
+ };
4157
+ var isCABTree = (result) => result.version === 2;
4158
+ var findMonetizeMarker = (section) => {
4159
+ if (section.sectionType === SectionType.monetize && section.offers === void 0) return section;
4160
+ if (!Array.isArray(section.sections)) return void 0;
4161
+ for (const child of section.sections) {
4162
+ const found = findMonetizeMarker(child);
4163
+ if (found) return found;
4164
+ }
4165
+ return void 0;
4166
+ };
4167
+ var hydrateMonetizeMarkers = async (converted, input, ctx) => {
4168
+ if (!input.monetize || !isCABTree(converted)) return;
4169
+ const first = findMonetizeMarker(converted);
4170
+ if (!first) return;
4171
+ try {
4172
+ const { offers } = await fetchMonetizeOffers(
4173
+ {
4174
+ ...input.monetize,
4175
+ privacyPolicyUrl: first.privacyPolicyUrl,
4176
+ shop: input.shop,
4177
+ visitorId: input.monetize.visitorId || input.visitorId
4178
+ },
4179
+ ctx
4180
+ );
4181
+ first.offers = offers;
4182
+ } catch (err) {
4183
+ ctx.reportDrift?.({
4184
+ detail: `inline hydration failed: ${err instanceof Error ? err.message : String(err)}`,
4185
+ kind: "monetizeOffer"
4186
+ });
4187
+ }
4188
+ };
4189
+ var fetchAndConvert = async (id, input, ctx) => {
4190
+ const params = { id: String(id), shop: input.shop };
4191
+ if (input.cacheKey != null) params.cache_key = String(input.cacheKey);
4192
+ if (input.key) params.key = input.key;
4193
+ let upstream;
4194
+ try {
4195
+ upstream = await ctx.fetchUpstream("/api/v1/widgets/settings", params, { host: ctx.host });
4196
+ } catch (err) {
4197
+ if (err instanceof UpstreamError && [400, 404, 410].includes(err.status)) {
4198
+ throw new NotFoundError(`Widget not found: ${id}`);
4199
+ }
4200
+ throw err;
4201
+ }
4202
+ const data = unwrapData(upstream);
4203
+ if (isMissingUpstreamPayload(data)) throw new NotFoundError(`Widget not found: ${id}`);
4204
+ const normalized = convertNumericObjects(deepCamelCase(data));
4205
+ if (isProgressBarV1(normalized) && normalized.barSettings.barType === "smart-cart") {
4206
+ const { bars, progressColor } = await fetchProgressBarContext(input.shop, ctx);
4207
+ return convertSmartCartProgressBarToV2({ bars: bars ?? [], id, progressColor, settings: normalized });
4208
+ }
4209
+ if (isConvertibleProgressBarV1(normalized)) {
4210
+ const { progressColor } = await fetchProgressBarContext(input.shop, ctx);
4211
+ return convertProgressBarToV2({ id, progressColor, settings: normalized });
4212
+ }
4213
+ const converted = dispatchWidgetTransform(id, normalized, ctx.reportDrift);
4214
+ const colorless = isCABRootSection(normalized) ? colorlessProgressBars(converted) : [];
4215
+ if (colorless.length > 0) {
4216
+ const { progressColor: color } = await fetchProgressBarContext(input.shop, ctx);
4217
+ if (color) for (const bar of colorless) bar.progressColor = color;
4218
+ }
4219
+ return converted;
4220
+ };
4221
+ var resolveWidgetExperiment = async (input, ctx) => {
4222
+ if (!input.visitorId) return void 0;
4223
+ try {
4224
+ const { shop } = await fetchUserConfig({ shop: input.shop }, ctx);
4225
+ return resolveExperiment(shop.activeExperiments, input.id, input.visitorId);
4226
+ } catch {
4227
+ return void 0;
4228
+ }
4229
+ };
4230
+ var convertWidgetSettings = async (input, ctx) => {
4231
+ const experiment = await resolveWidgetExperiment(input, ctx);
4232
+ const result = await fetchAndConvert(experiment?.elementId ?? input.id, input, ctx);
4233
+ await hydrateMonetizeMarkers(result, input, ctx);
4234
+ return experiment && isCABTree(result) ? { ...result, experiment } : result;
4235
+ };
4236
+
4237
+ // src/server/giftValidation.ts
4238
+ var resolveDataSourcePath = async (widgetId, shop, ctx) => {
4239
+ const raw = await ctx.fetchUpstream("/api/v1/widgets/settings", { id: String(widgetId), shop }, { host: ctx.host });
4240
+ const data = raw && typeof raw === "object" && "data" in raw ? raw.data : raw;
4241
+ const endpoint = deepCamelCase(data)?.endpoint;
4242
+ return typeof endpoint === "string" && endpoint ? endpoint : void 0;
4243
+ };
4244
+ var groupByWidget = (gifts) => {
4245
+ const byWidget = /* @__PURE__ */ new Map();
4246
+ for (const gift of gifts) {
4247
+ byWidget.set(gift.widgetId, [...byWidget.get(gift.widgetId) ?? [], gift]);
4248
+ }
4249
+ return byWidget;
4250
+ };
4251
+ var validateGifts = async (input, ctx) => {
4252
+ const perWidget = await Promise.all(
4253
+ [...groupByWidget(input.gifts)].map(async ([widgetId, gifts]) => {
4254
+ const discountLost = gifts.filter(hasLostDiscount).map((gift) => gift.lineId);
4255
+ try {
4256
+ const dataSourcePath = await resolveDataSourcePath(widgetId, input.shop, ctx);
4257
+ if (!dataSourcePath) return discountLost;
4258
+ const { metadata } = await fetchDataSourceResults(
4259
+ {
4260
+ cart: input.cart,
4261
+ /** Market context threads through so a market-gated gift rule answers for the buyer's market — as engine params, inheriting the shop's markets-toggle gate (REB-21688). */
4262
+ country: input.country,
4263
+ customerId: input.customerId,
4264
+ dataSourcePath,
4265
+ /** The gift being validated is in the cart — input filtering would erase it from the answer. */
4266
+ filterInputs: "no",
4267
+ language: input.language,
4268
+ /**
4269
+ * `limit: 0` fetches NO products — only the rule verdict is needed. Safe because the engine
4270
+ * derives `matchedRules` from rule-config evaluation, not from fetched results (traced through
4271
+ * `api.php`), so the metadata is complete at limit 0. Don't raise it to "fix" a non-bug.
4272
+ */
4273
+ limit: 0,
4274
+ shop: input.shop,
4275
+ visitorId: input.visitorId
4276
+ },
4277
+ ctx
4278
+ );
4279
+ const meta = metadata ?? void 0;
4280
+ if (!isDefinitiveRuleAnswer(meta) || hasUnsupportedRuleTypes(meta)) return discountLost;
4281
+ const validIds = getValidGwpProductIds(meta.matchedRules);
4282
+ if (validIds.size === 0 && !isConfirmedRuleMiss(meta)) return discountLost;
4283
+ return gifts.filter((gift) => shouldRemoveGift(gift, validIds)).map((gift) => gift.lineId);
4284
+ } catch {
4285
+ return discountLost;
4286
+ }
4287
+ })
4288
+ );
4289
+ return { remove: perWidget.flat() };
4290
+ };
4291
+
4231
4292
  // src/server/analytics.ts
4232
4293
  var import_es_toolkit7 = require("es-toolkit");
4233
4294
  var EXPERIMENT_TYPE_MAP = {
@@ -4399,14 +4460,6 @@ var CartInputSchema = import_zod29.z.object({
4399
4460
  /** Presentment subtotal in cents. */
4400
4461
  subtotal: import_zod29.z.number()
4401
4462
  });
4402
- var WidgetSettingsInputSchema = import_zod29.z.object({
4403
- cacheKey: import_zod29.z.number().optional(),
4404
- id: import_zod29.z.number(),
4405
- key: import_zod29.z.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional(),
4406
- shop: import_zod29.z.string().max(REQUEST_BOUNDS.STRING_ID_MAX),
4407
- /** Stable per-buyer id; when present, enables sticky server-side A/B variant selection. */
4408
- visitorId: import_zod29.z.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional()
4409
- });
4410
4463
  var UserConfigInputSchema = import_zod29.z.object({ shop: import_zod29.z.string().max(REQUEST_BOUNDS.STRING_ID_MAX) });
4411
4464
  var DataSourceInputSchema = import_zod29.z.object({
4412
4465
  cart: CartInputSchema,
@@ -4489,6 +4542,8 @@ var MonetizeTransactionItemSchema = import_zod29.z.object({
4489
4542
  });
4490
4543
  var MonetizeInputSchema = import_zod29.z.object({
4491
4544
  cart: CartInputSchema,
4545
+ /** Client-minted render-beacon correlation id; forwarded to the ads engine as `client_request_id`. */
4546
+ clientRequestId: import_zod29.z.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional(),
4492
4547
  country: import_zod29.z.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional(),
4493
4548
  currency: import_zod29.z.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional(),
4494
4549
  /** Ads-targeting identity/geo/locale fields (React parity); folded into the engine query verbatim. */
@@ -4518,6 +4573,22 @@ var MonetizeInputSchema = import_zod29.z.object({
4518
4573
  visitorId: import_zod29.z.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional(),
4519
4574
  zipCode: import_zod29.z.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional()
4520
4575
  });
4576
+ var WidgetSettingsInputSchema = import_zod29.z.object({
4577
+ cacheKey: import_zod29.z.number().optional(),
4578
+ id: import_zod29.z.number(),
4579
+ key: import_zod29.z.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional(),
4580
+ /**
4581
+ * Monetize context for inline hydration: when present AND the widget converts to a Monetize marker,
4582
+ * the server fetches/composes the offer cards inside this same request (no second `/cab/monetize`
4583
+ * round trip). Presence doubles as the capability signal — an absent block keeps the legacy marker
4584
+ * flow, so old/new client×server pairs need no lockstep deploy. `shop` rides the outer input;
4585
+ * `privacyPolicyUrl` comes from the widget settings server-side (more authoritative than a relay).
4586
+ */
4587
+ monetize: MonetizeInputSchema.omit({ privacyPolicyUrl: true, shop: true }).optional(),
4588
+ shop: import_zod29.z.string().max(REQUEST_BOUNDS.STRING_ID_MAX),
4589
+ /** Stable per-buyer id; when present, enables sticky server-side A/B variant selection. */
4590
+ visitorId: import_zod29.z.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional()
4591
+ });
4521
4592
  var AnalyticsEventInputSchema = import_zod29.z.object({
4522
4593
  name: import_zod29.z.string().max(REQUEST_BOUNDS.STRING_KEY_MAX),
4523
4594
  properties: import_zod29.z.record(import_zod29.z.string().max(REQUEST_BOUNDS.STRING_KEY_MAX), import_zod29.z.unknown()).optional(),