@rebuy/rebuy 3.12.0 → 3.13.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/dist/index.cjs +16 -2
- package/dist/index.mjs +16 -2
- package/dist/schema/cabShopConfig.d.ts +2 -0
- package/dist/server/adsEngine.d.ts +4 -1
- package/dist/server/index.cjs +49 -27
- package/dist/server/index.mjs +49 -27
- package/dist/server/monetizeOffers.d.ts +4 -2
- package/dist/server/requestSchemas.d.ts +1 -1
- package/dist/transforms/index.cjs +2 -2
- package/dist/transforms/index.mjs +2 -2
- package/dist/transforms/offerV1/offerCard.d.ts +5 -2
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1162,6 +1162,20 @@ var CabShopConfig = import_zod2.z.object({
|
|
|
1162
1162
|
apiKey: import_zod2.z.string(),
|
|
1163
1163
|
/** The shop's billing generation; the smart-cart entitlement gate only fires for `'v6'`. Missing/drift ⇒ `null` (fail open, ungated). */
|
|
1164
1164
|
billingVersion: import_zod2.z.string().nullable().catch(null).default(null),
|
|
1165
|
+
/**
|
|
1166
|
+
* The shop's rotating cache-buster: `onsite_cache_key` internally — a unix timestamp the engine bumps,
|
|
1167
|
+
* and the edge copy of this config purges, on every widget/settings/theme edit — but serialized on the
|
|
1168
|
+
* wire as `cache_key`, which `deepCamelCase` maps to this field (renaming it `onsiteCacheKey` would
|
|
1169
|
+
* sever the mapping silently; the wire-level assertion in `userConfig.test.ts` is what fails loudly).
|
|
1170
|
+
* Threaded onto the widgets/settings read as `cache_key` so the engine marks the response
|
|
1171
|
+
* immutable-cacheable and the `cached.*` edge serves it fresh-by-URL. The engine sends a numeric
|
|
1172
|
+
* string — coerce — but admit only number/string inputs and constrain to a positive integer: a bare
|
|
1173
|
+
* coercion folds `null`/`''`/`false` to `0` and `true` to `1` — "valid" numbers that would go upstream
|
|
1174
|
+
* as a constant never-rotating buster and pin the edge copy permanently stale. A real key is always a
|
|
1175
|
+
* positive int sent as number or string, so everything else lands on `.catch()` and degrades that read
|
|
1176
|
+
* to un-busted (origin-served — slower, never stale), never a parse fail.
|
|
1177
|
+
*/
|
|
1178
|
+
cacheKey: import_zod2.z.union([import_zod2.z.number(), import_zod2.z.string()]).transform((value) => Number(value)).pipe(import_zod2.z.number().int().positive()).optional().catch(void 0),
|
|
1165
1179
|
/**
|
|
1166
1180
|
* The shop's base-currency ISO code (e.g. `'USD'`). CAB derives the shop minor-unit exponent from it
|
|
1167
1181
|
* (`currencyDecimals`) so a Functions-applied fixed discount — whose amount is in shop minor units —
|
|
@@ -4678,16 +4692,16 @@ var buildOfferCard = (settings, horizontal) => {
|
|
|
4678
4692
|
gridCardImage(image),
|
|
4679
4693
|
title,
|
|
4680
4694
|
...variantTitle,
|
|
4695
|
+
...reviews,
|
|
4681
4696
|
price,
|
|
4682
4697
|
...description,
|
|
4683
|
-
...reviews,
|
|
4684
4698
|
...variants,
|
|
4685
4699
|
...quantity,
|
|
4686
4700
|
...subscription,
|
|
4687
4701
|
addButton
|
|
4688
4702
|
];
|
|
4689
4703
|
}
|
|
4690
|
-
const info = rowsLayout([title, ...variantTitle, price, ...description, ...
|
|
4704
|
+
const info = rowsLayout([title, ...variantTitle, ...reviews, price, ...description, ...variants]);
|
|
4691
4705
|
const actions = [...subscription, addButton];
|
|
4692
4706
|
const actionsCell = actions.length === 1 ? actions[0] : rowsLayout(actions);
|
|
4693
4707
|
const card = {
|
package/dist/index.mjs
CHANGED
|
@@ -876,6 +876,20 @@ var CabShopConfig = z2.object({
|
|
|
876
876
|
apiKey: z2.string(),
|
|
877
877
|
/** The shop's billing generation; the smart-cart entitlement gate only fires for `'v6'`. Missing/drift ⇒ `null` (fail open, ungated). */
|
|
878
878
|
billingVersion: z2.string().nullable().catch(null).default(null),
|
|
879
|
+
/**
|
|
880
|
+
* The shop's rotating cache-buster: `onsite_cache_key` internally — a unix timestamp the engine bumps,
|
|
881
|
+
* and the edge copy of this config purges, on every widget/settings/theme edit — but serialized on the
|
|
882
|
+
* wire as `cache_key`, which `deepCamelCase` maps to this field (renaming it `onsiteCacheKey` would
|
|
883
|
+
* sever the mapping silently; the wire-level assertion in `userConfig.test.ts` is what fails loudly).
|
|
884
|
+
* Threaded onto the widgets/settings read as `cache_key` so the engine marks the response
|
|
885
|
+
* immutable-cacheable and the `cached.*` edge serves it fresh-by-URL. The engine sends a numeric
|
|
886
|
+
* string — coerce — but admit only number/string inputs and constrain to a positive integer: a bare
|
|
887
|
+
* coercion folds `null`/`''`/`false` to `0` and `true` to `1` — "valid" numbers that would go upstream
|
|
888
|
+
* as a constant never-rotating buster and pin the edge copy permanently stale. A real key is always a
|
|
889
|
+
* positive int sent as number or string, so everything else lands on `.catch()` and degrades that read
|
|
890
|
+
* to un-busted (origin-served — slower, never stale), never a parse fail.
|
|
891
|
+
*/
|
|
892
|
+
cacheKey: z2.union([z2.number(), z2.string()]).transform((value) => Number(value)).pipe(z2.number().int().positive()).optional().catch(void 0),
|
|
879
893
|
/**
|
|
880
894
|
* The shop's base-currency ISO code (e.g. `'USD'`). CAB derives the shop minor-unit exponent from it
|
|
881
895
|
* (`currencyDecimals`) so a Functions-applied fixed discount — whose amount is in shop minor units —
|
|
@@ -4392,16 +4406,16 @@ var buildOfferCard = (settings, horizontal) => {
|
|
|
4392
4406
|
gridCardImage(image),
|
|
4393
4407
|
title,
|
|
4394
4408
|
...variantTitle,
|
|
4409
|
+
...reviews,
|
|
4395
4410
|
price,
|
|
4396
4411
|
...description,
|
|
4397
|
-
...reviews,
|
|
4398
4412
|
...variants,
|
|
4399
4413
|
...quantity,
|
|
4400
4414
|
...subscription,
|
|
4401
4415
|
addButton
|
|
4402
4416
|
];
|
|
4403
4417
|
}
|
|
4404
|
-
const info = rowsLayout([title, ...variantTitle, price, ...description, ...
|
|
4418
|
+
const info = rowsLayout([title, ...variantTitle, ...reviews, price, ...description, ...variants]);
|
|
4405
4419
|
const actions = [...subscription, addButton];
|
|
4406
4420
|
const actionsCell = actions.length === 1 ? actions[0] : rowsLayout(actions);
|
|
4407
4421
|
const card = {
|
|
@@ -39,6 +39,7 @@ export declare const CabShopConfig: z.ZodObject<{
|
|
|
39
39
|
}, z.core.$strip>>>>>;
|
|
40
40
|
apiKey: z.ZodString;
|
|
41
41
|
billingVersion: z.ZodDefault<z.ZodCatch<z.ZodNullable<z.ZodString>>>;
|
|
42
|
+
cacheKey: z.ZodCatch<z.ZodOptional<z.ZodPipe<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>, z.ZodNumber>>>;
|
|
42
43
|
currency: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
43
44
|
markets: z.ZodPipe<z.ZodDefault<z.ZodCatch<z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
44
45
|
enabled: z.ZodBoolean;
|
|
@@ -76,6 +77,7 @@ export declare const CabUserConfig: z.ZodObject<{
|
|
|
76
77
|
}, z.core.$strip>>>>>;
|
|
77
78
|
apiKey: z.ZodString;
|
|
78
79
|
billingVersion: z.ZodDefault<z.ZodCatch<z.ZodNullable<z.ZodString>>>;
|
|
80
|
+
cacheKey: z.ZodCatch<z.ZodOptional<z.ZodPipe<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>, z.ZodNumber>>>;
|
|
79
81
|
currency: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
80
82
|
markets: z.ZodPipe<z.ZodDefault<z.ZodCatch<z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
81
83
|
enabled: z.ZodBoolean;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { type CabUserConfig } from '../schema/cabShopConfig';
|
|
2
3
|
import { type MonetizeInput, type ServerContext } from '../server/shared';
|
|
3
4
|
/**
|
|
4
5
|
* A single ads-engine offer. The `*_url` fields are tracking endpoints the engine returns fully
|
|
@@ -34,5 +35,7 @@ export declare const buildAdsParams: (input: MonetizeInput, publisherKey: string
|
|
|
34
35
|
* Fetch live ad offers from the Rebuy ads engine. Resolves the shop's publisher key server-side (and
|
|
35
36
|
* returns `[]` when the shop isn't enrolled — no ads, not an error). A non-prod upstream sends the
|
|
36
37
|
* staging supply key; a non-prod/editor/QA-email request is flagged `testing` (excluded from billing).
|
|
38
|
+
* `prefetched` is a caller's already-resolved shop config (inline hydration threads it) — consulted only
|
|
39
|
+
* AFTER the gates below, so a gated-out request still costs zero upstream calls either way.
|
|
37
40
|
*/
|
|
38
|
-
export declare const fetchAdsEngineOffers: (input: MonetizeInput, ctx: ServerContext) => Promise<AdsEngineOffer[]>;
|
|
41
|
+
export declare const fetchAdsEngineOffers: (input: MonetizeInput, ctx: ServerContext, prefetched?: CabUserConfig) => Promise<AdsEngineOffer[]>;
|
package/dist/server/index.cjs
CHANGED
|
@@ -582,6 +582,20 @@ var CabShopConfig = import_zod6.z.object({
|
|
|
582
582
|
apiKey: import_zod6.z.string(),
|
|
583
583
|
/** The shop's billing generation; the smart-cart entitlement gate only fires for `'v6'`. Missing/drift ⇒ `null` (fail open, ungated). */
|
|
584
584
|
billingVersion: import_zod6.z.string().nullable().catch(null).default(null),
|
|
585
|
+
/**
|
|
586
|
+
* The shop's rotating cache-buster: `onsite_cache_key` internally — a unix timestamp the engine bumps,
|
|
587
|
+
* and the edge copy of this config purges, on every widget/settings/theme edit — but serialized on the
|
|
588
|
+
* wire as `cache_key`, which `deepCamelCase` maps to this field (renaming it `onsiteCacheKey` would
|
|
589
|
+
* sever the mapping silently; the wire-level assertion in `userConfig.test.ts` is what fails loudly).
|
|
590
|
+
* Threaded onto the widgets/settings read as `cache_key` so the engine marks the response
|
|
591
|
+
* immutable-cacheable and the `cached.*` edge serves it fresh-by-URL. The engine sends a numeric
|
|
592
|
+
* string — coerce — but admit only number/string inputs and constrain to a positive integer: a bare
|
|
593
|
+
* coercion folds `null`/`''`/`false` to `0` and `true` to `1` — "valid" numbers that would go upstream
|
|
594
|
+
* as a constant never-rotating buster and pin the edge copy permanently stale. A real key is always a
|
|
595
|
+
* positive int sent as number or string, so everything else lands on `.catch()` and degrades that read
|
|
596
|
+
* to un-busted (origin-served — slower, never stale), never a parse fail.
|
|
597
|
+
*/
|
|
598
|
+
cacheKey: import_zod6.z.union([import_zod6.z.number(), import_zod6.z.string()]).transform((value) => Number(value)).pipe(import_zod6.z.number().int().positive()).optional().catch(void 0),
|
|
585
599
|
/**
|
|
586
600
|
* The shop's base-currency ISO code (e.g. `'USD'`). CAB derives the shop minor-unit exponent from it
|
|
587
601
|
* (`currencyDecimals`) so a Functions-applied fixed discount — whose amount is in shop minor units —
|
|
@@ -3215,16 +3229,16 @@ var buildOfferCard = (settings, horizontal) => {
|
|
|
3215
3229
|
gridCardImage(image),
|
|
3216
3230
|
title,
|
|
3217
3231
|
...variantTitle,
|
|
3232
|
+
...reviews,
|
|
3218
3233
|
price,
|
|
3219
3234
|
...description,
|
|
3220
|
-
...reviews,
|
|
3221
3235
|
...variants,
|
|
3222
3236
|
...quantity,
|
|
3223
3237
|
...subscription,
|
|
3224
3238
|
addButton
|
|
3225
3239
|
];
|
|
3226
3240
|
}
|
|
3227
|
-
const info = rowsLayout([title, ...variantTitle, price, ...description, ...
|
|
3241
|
+
const info = rowsLayout([title, ...variantTitle, ...reviews, price, ...description, ...variants]);
|
|
3228
3242
|
const actions = [...subscription, addButton];
|
|
3229
3243
|
const actionsCell = actions.length === 1 ? actions[0] : rowsLayout(actions);
|
|
3230
3244
|
const card = {
|
|
@@ -4008,10 +4022,10 @@ var buildAdsParams = (input, publisherKey, supplyKey, testing, userAgent, ipAddr
|
|
|
4008
4022
|
...userAgent && { user_agent: userAgent }
|
|
4009
4023
|
};
|
|
4010
4024
|
};
|
|
4011
|
-
var fetchAdsEngineOffers = async (input, ctx) => {
|
|
4025
|
+
var fetchAdsEngineOffers = async (input, ctx, prefetched) => {
|
|
4012
4026
|
if (!input.country || !SUPPORTED_COUNTRIES.has(input.country.toUpperCase())) return [];
|
|
4013
4027
|
if (!(input.sessionId || input.visitorId)) return [];
|
|
4014
|
-
const { shop } = await fetchUserConfig({ shop: input.shop }, ctx);
|
|
4028
|
+
const { shop } = prefetched ?? await fetchUserConfig({ shop: input.shop }, ctx);
|
|
4015
4029
|
const publisherKey = shop.monetize?.publisherKey;
|
|
4016
4030
|
if (!publisherKey) return [];
|
|
4017
4031
|
const isProduction = ctx.host === REBUY_ENGINE_HOST;
|
|
@@ -4169,8 +4183,10 @@ var composeOfferCard = (offer, input) => {
|
|
|
4169
4183
|
};
|
|
4170
4184
|
|
|
4171
4185
|
// src/server/monetizeOffers.ts
|
|
4172
|
-
var fetchMonetizeOffers = async (input, ctx) => {
|
|
4173
|
-
const composed = (await fetchAdsEngineOffers(input, ctx)).map(
|
|
4186
|
+
var fetchMonetizeOffers = async (input, ctx, prefetched) => {
|
|
4187
|
+
const composed = (await fetchAdsEngineOffers(input, ctx, prefetched)).map(
|
|
4188
|
+
(offer) => composeOfferCard(offer, input)
|
|
4189
|
+
);
|
|
4174
4190
|
const offers = composed.filter((card) => card !== void 0);
|
|
4175
4191
|
if (offers.length < composed.length)
|
|
4176
4192
|
ctx.reportDrift?.({
|
|
@@ -4225,8 +4241,8 @@ var dispatchWidgetTransform = (id, normalized, report) => {
|
|
|
4225
4241
|
if (isOfferV1(normalized)) return convertOfferToV2({ id, name: normalized.name, settings: normalized });
|
|
4226
4242
|
return normalized;
|
|
4227
4243
|
};
|
|
4228
|
-
var fetchProgressBarContext = async (shop, ctx) => {
|
|
4229
|
-
const config = await fetchUserConfig({ shop }, ctx).catch(() => void 0);
|
|
4244
|
+
var fetchProgressBarContext = async (shop, ctx, prefetched) => {
|
|
4245
|
+
const config = prefetched === void 0 ? await fetchUserConfig({ shop }, ctx).catch(() => void 0) : prefetched;
|
|
4230
4246
|
if (!config) return {};
|
|
4231
4247
|
if (config.shop.billingVersion === "v6" && config.shop.activePackages.some((pkg) => pkg.shortName === "")) {
|
|
4232
4248
|
ctx.reportDrift?.({
|
|
@@ -4265,7 +4281,7 @@ var findMonetizeMarker = (section) => {
|
|
|
4265
4281
|
}
|
|
4266
4282
|
return void 0;
|
|
4267
4283
|
};
|
|
4268
|
-
var hydrateMonetizeMarkers = async (converted, input, ctx) => {
|
|
4284
|
+
var hydrateMonetizeMarkers = async (converted, input, ctx, config) => {
|
|
4269
4285
|
if (!input.monetize || !isCABTree(converted)) return;
|
|
4270
4286
|
const first = findMonetizeMarker(converted);
|
|
4271
4287
|
if (!first) return;
|
|
@@ -4277,7 +4293,8 @@ var hydrateMonetizeMarkers = async (converted, input, ctx) => {
|
|
|
4277
4293
|
shop: input.shop,
|
|
4278
4294
|
visitorId: input.monetize.visitorId || input.visitorId
|
|
4279
4295
|
},
|
|
4280
|
-
ctx
|
|
4296
|
+
ctx,
|
|
4297
|
+
config ?? void 0
|
|
4281
4298
|
);
|
|
4282
4299
|
first.offers = offers;
|
|
4283
4300
|
} catch (err) {
|
|
@@ -4287,7 +4304,7 @@ var hydrateMonetizeMarkers = async (converted, input, ctx) => {
|
|
|
4287
4304
|
});
|
|
4288
4305
|
}
|
|
4289
4306
|
};
|
|
4290
|
-
var fetchAndConvert = async (id, input, ctx) => {
|
|
4307
|
+
var fetchAndConvert = async (id, input, ctx, config) => {
|
|
4291
4308
|
const params = { id: String(id), shop: input.shop };
|
|
4292
4309
|
if (input.cacheKey != null) params.cache_key = String(input.cacheKey);
|
|
4293
4310
|
if (input.key) params.key = input.key;
|
|
@@ -4304,34 +4321,32 @@ var fetchAndConvert = async (id, input, ctx) => {
|
|
|
4304
4321
|
if (isMissingUpstreamPayload(data)) throw new NotFoundError(`Widget not found: ${id}`);
|
|
4305
4322
|
const normalized = convertNumericObjects(deepCamelCase(data));
|
|
4306
4323
|
if (isProgressBarV1(normalized) && normalized.barSettings.barType === "smart-cart") {
|
|
4307
|
-
const { bars, progressColor } = await fetchProgressBarContext(input.shop, ctx);
|
|
4324
|
+
const { bars, progressColor } = await fetchProgressBarContext(input.shop, ctx, config);
|
|
4308
4325
|
return convertSmartCartProgressBarToV2({ bars: bars ?? [], id, progressColor, settings: normalized });
|
|
4309
4326
|
}
|
|
4310
4327
|
if (isConvertibleProgressBarV1(normalized)) {
|
|
4311
|
-
const { progressColor } = await fetchProgressBarContext(input.shop, ctx);
|
|
4328
|
+
const { progressColor } = await fetchProgressBarContext(input.shop, ctx, config);
|
|
4312
4329
|
return convertProgressBarToV2({ id, progressColor, settings: normalized });
|
|
4313
4330
|
}
|
|
4314
4331
|
const converted = dispatchWidgetTransform(id, normalized, ctx.reportDrift);
|
|
4315
4332
|
const colorless = isCABRootSection(normalized) ? colorlessProgressBars(converted) : [];
|
|
4316
4333
|
if (colorless.length > 0) {
|
|
4317
|
-
const { progressColor: color } = await fetchProgressBarContext(input.shop, ctx);
|
|
4334
|
+
const { progressColor: color } = await fetchProgressBarContext(input.shop, ctx, config);
|
|
4318
4335
|
if (color) for (const bar of colorless) bar.progressColor = color;
|
|
4319
4336
|
}
|
|
4320
4337
|
return converted;
|
|
4321
4338
|
};
|
|
4322
|
-
var resolveWidgetExperiment =
|
|
4323
|
-
if (!input.visitorId) return void 0;
|
|
4324
|
-
try {
|
|
4325
|
-
const { shop } = await fetchUserConfig({ shop: input.shop }, ctx);
|
|
4326
|
-
return resolveExperiment(shop.activeExperiments, input.id, input.visitorId);
|
|
4327
|
-
} catch {
|
|
4328
|
-
return void 0;
|
|
4329
|
-
}
|
|
4330
|
-
};
|
|
4339
|
+
var resolveWidgetExperiment = (input, config) => input.visitorId && config ? resolveExperiment(config.shop.activeExperiments, input.id, input.visitorId) : void 0;
|
|
4331
4340
|
var convertWidgetSettings = async (input, ctx) => {
|
|
4332
|
-
const
|
|
4333
|
-
const
|
|
4334
|
-
|
|
4341
|
+
const config = input.cacheKey != null && !input.visitorId ? void 0 : await fetchUserConfig({ shop: input.shop }, ctx).catch(() => null);
|
|
4342
|
+
const experiment = resolveWidgetExperiment(input, config);
|
|
4343
|
+
const result = await fetchAndConvert(
|
|
4344
|
+
experiment?.elementId ?? input.id,
|
|
4345
|
+
{ ...input, cacheKey: input.cacheKey ?? config?.shop.cacheKey },
|
|
4346
|
+
ctx,
|
|
4347
|
+
config
|
|
4348
|
+
);
|
|
4349
|
+
await hydrateMonetizeMarkers(result, input, ctx, config);
|
|
4335
4350
|
return experiment && isCABTree(result) ? { ...result, experiment } : result;
|
|
4336
4351
|
};
|
|
4337
4352
|
|
|
@@ -4675,7 +4690,14 @@ var MonetizeInputSchema = import_zod29.z.object({
|
|
|
4675
4690
|
zipCode: import_zod29.z.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional()
|
|
4676
4691
|
});
|
|
4677
4692
|
var WidgetSettingsInputSchema = import_zod29.z.object({
|
|
4678
|
-
|
|
4693
|
+
/**
|
|
4694
|
+
* Same domain as `CabShopConfig.cacheKey`, for the same reason: this side WINS the orchestrator's
|
|
4695
|
+
* `input.cacheKey ?? config.shop.cacheKey` (and an explicit value skips the config read entirely), so
|
|
4696
|
+
* an unconstrained `0`/negative/float here would go upstream as a constant never-rotating `cache_key`
|
|
4697
|
+
* and pin the edge copy permanently stale. `.catch(undefined)` because a bogus buster should degrade
|
|
4698
|
+
* the read to un-busted, never 400 a widget render.
|
|
4699
|
+
*/
|
|
4700
|
+
cacheKey: import_zod29.z.number().int().positive().optional().catch(void 0),
|
|
4679
4701
|
id: import_zod29.z.number(),
|
|
4680
4702
|
key: import_zod29.z.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional(),
|
|
4681
4703
|
/**
|
package/dist/server/index.mjs
CHANGED
|
@@ -534,6 +534,20 @@ var CabShopConfig = z6.object({
|
|
|
534
534
|
apiKey: z6.string(),
|
|
535
535
|
/** The shop's billing generation; the smart-cart entitlement gate only fires for `'v6'`. Missing/drift ⇒ `null` (fail open, ungated). */
|
|
536
536
|
billingVersion: z6.string().nullable().catch(null).default(null),
|
|
537
|
+
/**
|
|
538
|
+
* The shop's rotating cache-buster: `onsite_cache_key` internally — a unix timestamp the engine bumps,
|
|
539
|
+
* and the edge copy of this config purges, on every widget/settings/theme edit — but serialized on the
|
|
540
|
+
* wire as `cache_key`, which `deepCamelCase` maps to this field (renaming it `onsiteCacheKey` would
|
|
541
|
+
* sever the mapping silently; the wire-level assertion in `userConfig.test.ts` is what fails loudly).
|
|
542
|
+
* Threaded onto the widgets/settings read as `cache_key` so the engine marks the response
|
|
543
|
+
* immutable-cacheable and the `cached.*` edge serves it fresh-by-URL. The engine sends a numeric
|
|
544
|
+
* string — coerce — but admit only number/string inputs and constrain to a positive integer: a bare
|
|
545
|
+
* coercion folds `null`/`''`/`false` to `0` and `true` to `1` — "valid" numbers that would go upstream
|
|
546
|
+
* as a constant never-rotating buster and pin the edge copy permanently stale. A real key is always a
|
|
547
|
+
* positive int sent as number or string, so everything else lands on `.catch()` and degrades that read
|
|
548
|
+
* to un-busted (origin-served — slower, never stale), never a parse fail.
|
|
549
|
+
*/
|
|
550
|
+
cacheKey: z6.union([z6.number(), z6.string()]).transform((value) => Number(value)).pipe(z6.number().int().positive()).optional().catch(void 0),
|
|
537
551
|
/**
|
|
538
552
|
* The shop's base-currency ISO code (e.g. `'USD'`). CAB derives the shop minor-unit exponent from it
|
|
539
553
|
* (`currencyDecimals`) so a Functions-applied fixed discount — whose amount is in shop minor units —
|
|
@@ -3167,16 +3181,16 @@ var buildOfferCard = (settings, horizontal) => {
|
|
|
3167
3181
|
gridCardImage(image),
|
|
3168
3182
|
title,
|
|
3169
3183
|
...variantTitle,
|
|
3184
|
+
...reviews,
|
|
3170
3185
|
price,
|
|
3171
3186
|
...description,
|
|
3172
|
-
...reviews,
|
|
3173
3187
|
...variants,
|
|
3174
3188
|
...quantity,
|
|
3175
3189
|
...subscription,
|
|
3176
3190
|
addButton
|
|
3177
3191
|
];
|
|
3178
3192
|
}
|
|
3179
|
-
const info = rowsLayout([title, ...variantTitle, price, ...description, ...
|
|
3193
|
+
const info = rowsLayout([title, ...variantTitle, ...reviews, price, ...description, ...variants]);
|
|
3180
3194
|
const actions = [...subscription, addButton];
|
|
3181
3195
|
const actionsCell = actions.length === 1 ? actions[0] : rowsLayout(actions);
|
|
3182
3196
|
const card = {
|
|
@@ -3960,10 +3974,10 @@ var buildAdsParams = (input, publisherKey, supplyKey, testing, userAgent, ipAddr
|
|
|
3960
3974
|
...userAgent && { user_agent: userAgent }
|
|
3961
3975
|
};
|
|
3962
3976
|
};
|
|
3963
|
-
var fetchAdsEngineOffers = async (input, ctx) => {
|
|
3977
|
+
var fetchAdsEngineOffers = async (input, ctx, prefetched) => {
|
|
3964
3978
|
if (!input.country || !SUPPORTED_COUNTRIES.has(input.country.toUpperCase())) return [];
|
|
3965
3979
|
if (!(input.sessionId || input.visitorId)) return [];
|
|
3966
|
-
const { shop } = await fetchUserConfig({ shop: input.shop }, ctx);
|
|
3980
|
+
const { shop } = prefetched ?? await fetchUserConfig({ shop: input.shop }, ctx);
|
|
3967
3981
|
const publisherKey = shop.monetize?.publisherKey;
|
|
3968
3982
|
if (!publisherKey) return [];
|
|
3969
3983
|
const isProduction = ctx.host === REBUY_ENGINE_HOST;
|
|
@@ -4121,8 +4135,10 @@ var composeOfferCard = (offer, input) => {
|
|
|
4121
4135
|
};
|
|
4122
4136
|
|
|
4123
4137
|
// src/server/monetizeOffers.ts
|
|
4124
|
-
var fetchMonetizeOffers = async (input, ctx) => {
|
|
4125
|
-
const composed = (await fetchAdsEngineOffers(input, ctx)).map(
|
|
4138
|
+
var fetchMonetizeOffers = async (input, ctx, prefetched) => {
|
|
4139
|
+
const composed = (await fetchAdsEngineOffers(input, ctx, prefetched)).map(
|
|
4140
|
+
(offer) => composeOfferCard(offer, input)
|
|
4141
|
+
);
|
|
4126
4142
|
const offers = composed.filter((card) => card !== void 0);
|
|
4127
4143
|
if (offers.length < composed.length)
|
|
4128
4144
|
ctx.reportDrift?.({
|
|
@@ -4177,8 +4193,8 @@ var dispatchWidgetTransform = (id, normalized, report) => {
|
|
|
4177
4193
|
if (isOfferV1(normalized)) return convertOfferToV2({ id, name: normalized.name, settings: normalized });
|
|
4178
4194
|
return normalized;
|
|
4179
4195
|
};
|
|
4180
|
-
var fetchProgressBarContext = async (shop, ctx) => {
|
|
4181
|
-
const config = await fetchUserConfig({ shop }, ctx).catch(() => void 0);
|
|
4196
|
+
var fetchProgressBarContext = async (shop, ctx, prefetched) => {
|
|
4197
|
+
const config = prefetched === void 0 ? await fetchUserConfig({ shop }, ctx).catch(() => void 0) : prefetched;
|
|
4182
4198
|
if (!config) return {};
|
|
4183
4199
|
if (config.shop.billingVersion === "v6" && config.shop.activePackages.some((pkg) => pkg.shortName === "")) {
|
|
4184
4200
|
ctx.reportDrift?.({
|
|
@@ -4217,7 +4233,7 @@ var findMonetizeMarker = (section) => {
|
|
|
4217
4233
|
}
|
|
4218
4234
|
return void 0;
|
|
4219
4235
|
};
|
|
4220
|
-
var hydrateMonetizeMarkers = async (converted, input, ctx) => {
|
|
4236
|
+
var hydrateMonetizeMarkers = async (converted, input, ctx, config) => {
|
|
4221
4237
|
if (!input.monetize || !isCABTree(converted)) return;
|
|
4222
4238
|
const first = findMonetizeMarker(converted);
|
|
4223
4239
|
if (!first) return;
|
|
@@ -4229,7 +4245,8 @@ var hydrateMonetizeMarkers = async (converted, input, ctx) => {
|
|
|
4229
4245
|
shop: input.shop,
|
|
4230
4246
|
visitorId: input.monetize.visitorId || input.visitorId
|
|
4231
4247
|
},
|
|
4232
|
-
ctx
|
|
4248
|
+
ctx,
|
|
4249
|
+
config ?? void 0
|
|
4233
4250
|
);
|
|
4234
4251
|
first.offers = offers;
|
|
4235
4252
|
} catch (err) {
|
|
@@ -4239,7 +4256,7 @@ var hydrateMonetizeMarkers = async (converted, input, ctx) => {
|
|
|
4239
4256
|
});
|
|
4240
4257
|
}
|
|
4241
4258
|
};
|
|
4242
|
-
var fetchAndConvert = async (id, input, ctx) => {
|
|
4259
|
+
var fetchAndConvert = async (id, input, ctx, config) => {
|
|
4243
4260
|
const params = { id: String(id), shop: input.shop };
|
|
4244
4261
|
if (input.cacheKey != null) params.cache_key = String(input.cacheKey);
|
|
4245
4262
|
if (input.key) params.key = input.key;
|
|
@@ -4256,34 +4273,32 @@ var fetchAndConvert = async (id, input, ctx) => {
|
|
|
4256
4273
|
if (isMissingUpstreamPayload(data)) throw new NotFoundError(`Widget not found: ${id}`);
|
|
4257
4274
|
const normalized = convertNumericObjects(deepCamelCase(data));
|
|
4258
4275
|
if (isProgressBarV1(normalized) && normalized.barSettings.barType === "smart-cart") {
|
|
4259
|
-
const { bars, progressColor } = await fetchProgressBarContext(input.shop, ctx);
|
|
4276
|
+
const { bars, progressColor } = await fetchProgressBarContext(input.shop, ctx, config);
|
|
4260
4277
|
return convertSmartCartProgressBarToV2({ bars: bars ?? [], id, progressColor, settings: normalized });
|
|
4261
4278
|
}
|
|
4262
4279
|
if (isConvertibleProgressBarV1(normalized)) {
|
|
4263
|
-
const { progressColor } = await fetchProgressBarContext(input.shop, ctx);
|
|
4280
|
+
const { progressColor } = await fetchProgressBarContext(input.shop, ctx, config);
|
|
4264
4281
|
return convertProgressBarToV2({ id, progressColor, settings: normalized });
|
|
4265
4282
|
}
|
|
4266
4283
|
const converted = dispatchWidgetTransform(id, normalized, ctx.reportDrift);
|
|
4267
4284
|
const colorless = isCABRootSection(normalized) ? colorlessProgressBars(converted) : [];
|
|
4268
4285
|
if (colorless.length > 0) {
|
|
4269
|
-
const { progressColor: color } = await fetchProgressBarContext(input.shop, ctx);
|
|
4286
|
+
const { progressColor: color } = await fetchProgressBarContext(input.shop, ctx, config);
|
|
4270
4287
|
if (color) for (const bar of colorless) bar.progressColor = color;
|
|
4271
4288
|
}
|
|
4272
4289
|
return converted;
|
|
4273
4290
|
};
|
|
4274
|
-
var resolveWidgetExperiment =
|
|
4275
|
-
if (!input.visitorId) return void 0;
|
|
4276
|
-
try {
|
|
4277
|
-
const { shop } = await fetchUserConfig({ shop: input.shop }, ctx);
|
|
4278
|
-
return resolveExperiment(shop.activeExperiments, input.id, input.visitorId);
|
|
4279
|
-
} catch {
|
|
4280
|
-
return void 0;
|
|
4281
|
-
}
|
|
4282
|
-
};
|
|
4291
|
+
var resolveWidgetExperiment = (input, config) => input.visitorId && config ? resolveExperiment(config.shop.activeExperiments, input.id, input.visitorId) : void 0;
|
|
4283
4292
|
var convertWidgetSettings = async (input, ctx) => {
|
|
4284
|
-
const
|
|
4285
|
-
const
|
|
4286
|
-
|
|
4293
|
+
const config = input.cacheKey != null && !input.visitorId ? void 0 : await fetchUserConfig({ shop: input.shop }, ctx).catch(() => null);
|
|
4294
|
+
const experiment = resolveWidgetExperiment(input, config);
|
|
4295
|
+
const result = await fetchAndConvert(
|
|
4296
|
+
experiment?.elementId ?? input.id,
|
|
4297
|
+
{ ...input, cacheKey: input.cacheKey ?? config?.shop.cacheKey },
|
|
4298
|
+
ctx,
|
|
4299
|
+
config
|
|
4300
|
+
);
|
|
4301
|
+
await hydrateMonetizeMarkers(result, input, ctx, config);
|
|
4287
4302
|
return experiment && isCABTree(result) ? { ...result, experiment } : result;
|
|
4288
4303
|
};
|
|
4289
4304
|
|
|
@@ -4627,7 +4642,14 @@ var MonetizeInputSchema = z29.object({
|
|
|
4627
4642
|
zipCode: z29.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional()
|
|
4628
4643
|
});
|
|
4629
4644
|
var WidgetSettingsInputSchema = z29.object({
|
|
4630
|
-
|
|
4645
|
+
/**
|
|
4646
|
+
* Same domain as `CabShopConfig.cacheKey`, for the same reason: this side WINS the orchestrator's
|
|
4647
|
+
* `input.cacheKey ?? config.shop.cacheKey` (and an explicit value skips the config read entirely), so
|
|
4648
|
+
* an unconstrained `0`/negative/float here would go upstream as a constant never-rotating `cache_key`
|
|
4649
|
+
* and pin the edge copy permanently stale. `.catch(undefined)` because a bogus buster should degrade
|
|
4650
|
+
* the read to un-busted, never 400 a widget render.
|
|
4651
|
+
*/
|
|
4652
|
+
cacheKey: z29.number().int().positive().optional().catch(void 0),
|
|
4631
4653
|
id: z29.number(),
|
|
4632
4654
|
key: z29.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional(),
|
|
4633
4655
|
/**
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type CabUserConfig } from '../schema/cabShopConfig';
|
|
1
2
|
import { type CABSection } from '../schema/widgets/checkout-and-beyond';
|
|
2
3
|
import { type MonetizeInput, type ServerContext } from '../server/shared';
|
|
3
4
|
export type MonetizeOffers = {
|
|
@@ -8,6 +9,7 @@ export type MonetizeOffers = {
|
|
|
8
9
|
* offer card the extension renders directly. The header chrome (experiment copy + order-confirmed
|
|
9
10
|
* line) is baked inside every card — inside its border, as React draws it — so the response no longer
|
|
10
11
|
* carries a separate `header` (older extension builds read it as optional and render nothing). Empty
|
|
11
|
-
* when the shop isn't ad-enrolled.
|
|
12
|
+
* when the shop isn't ad-enrolled. `prefetched` is a caller's already-resolved shop config, passed
|
|
13
|
+
* through to the ads layer's publisher-key lookup so inline hydration doesn't refetch it.
|
|
12
14
|
*/
|
|
13
|
-
export declare const fetchMonetizeOffers: (input: MonetizeInput, ctx: ServerContext) => Promise<MonetizeOffers>;
|
|
15
|
+
export declare const fetchMonetizeOffers: (input: MonetizeInput, ctx: ServerContext, prefetched?: CabUserConfig) => Promise<MonetizeOffers>;
|
|
@@ -233,7 +233,7 @@ export declare const MonetizeInputSchema: z.ZodObject<{
|
|
|
233
233
|
}, z.core.$strip>;
|
|
234
234
|
/** Input for `convertWidgetSettings`. */
|
|
235
235
|
export declare const WidgetSettingsInputSchema: z.ZodObject<{
|
|
236
|
-
cacheKey: z.ZodOptional<z.ZodNumber
|
|
236
|
+
cacheKey: z.ZodCatch<z.ZodOptional<z.ZodNumber>>;
|
|
237
237
|
id: z.ZodNumber;
|
|
238
238
|
key: z.ZodOptional<z.ZodString>;
|
|
239
239
|
monetize: z.ZodOptional<z.ZodObject<{
|
|
@@ -3083,16 +3083,16 @@ var buildOfferCard = (settings, horizontal) => {
|
|
|
3083
3083
|
gridCardImage(image),
|
|
3084
3084
|
title,
|
|
3085
3085
|
...variantTitle,
|
|
3086
|
+
...reviews,
|
|
3086
3087
|
price,
|
|
3087
3088
|
...description,
|
|
3088
|
-
...reviews,
|
|
3089
3089
|
...variants,
|
|
3090
3090
|
...quantity,
|
|
3091
3091
|
...subscription,
|
|
3092
3092
|
addButton
|
|
3093
3093
|
];
|
|
3094
3094
|
}
|
|
3095
|
-
const info = rowsLayout([title, ...variantTitle, price, ...description, ...
|
|
3095
|
+
const info = rowsLayout([title, ...variantTitle, ...reviews, price, ...description, ...variants]);
|
|
3096
3096
|
const actions = [...subscription, addButton];
|
|
3097
3097
|
const actionsCell = actions.length === 1 ? actions[0] : rowsLayout(actions);
|
|
3098
3098
|
const card = {
|
|
@@ -3026,16 +3026,16 @@ var buildOfferCard = (settings, horizontal) => {
|
|
|
3026
3026
|
gridCardImage(image),
|
|
3027
3027
|
title,
|
|
3028
3028
|
...variantTitle,
|
|
3029
|
+
...reviews,
|
|
3029
3030
|
price,
|
|
3030
3031
|
...description,
|
|
3031
|
-
...reviews,
|
|
3032
3032
|
...variants,
|
|
3033
3033
|
...quantity,
|
|
3034
3034
|
...subscription,
|
|
3035
3035
|
addButton
|
|
3036
3036
|
];
|
|
3037
3037
|
}
|
|
3038
|
-
const info = rowsLayout([title, ...variantTitle, price, ...description, ...
|
|
3038
|
+
const info = rowsLayout([title, ...variantTitle, ...reviews, price, ...description, ...variants]);
|
|
3039
3039
|
const actions = [...subscription, addButton];
|
|
3040
3040
|
const actionsCell = actions.length === 1 ? actions[0] : rowsLayout(actions);
|
|
3041
3041
|
const card = {
|
|
@@ -7,9 +7,12 @@ export declare const borderToken: <T extends readonly string[]>(tokens: T, value
|
|
|
7
7
|
*
|
|
8
8
|
* `horizontal` mirrors the v1 layout orientation (see `convertOfferToV2`):
|
|
9
9
|
* - horizontal (v1 `list`/default, vertical-stacked products): a 3-column grid card —
|
|
10
|
-
* image | title/price/
|
|
10
|
+
* image | title/reviews/price/variants | actions — matching the compact V1 row.
|
|
11
11
|
* - vertical (v1 `grid`/`line`, side-by-side products): a flat top-to-bottom stack
|
|
12
|
-
* (image → title → price → … → button), the narrow column card.
|
|
12
|
+
* (image → title → reviews → price → … → button), the narrow column card.
|
|
13
|
+
*
|
|
14
|
+
* Reviews sit ABOVE the price in both orientations: legacy `OfferDetail` renders the star
|
|
15
|
+
* rating between the variant title and the price line (REB-24225).
|
|
13
16
|
*
|
|
14
17
|
* Quantity is a full-width row beneath the card in the horizontal layout; in the vertical
|
|
15
18
|
* layout it sits inline in the stack. NOT carried: the savings-amount badge (no live v1
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebuy/rebuy",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.13.0",
|
|
4
4
|
"description": "Shared zod schemas, legacy-to-CAB widget transforms, and server orchestrators for Rebuy's Shopify consumers (rebuy-api, admin-nextjs, rebuy-shopify-extensions)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Rebuy, Inc.",
|