@rebuy/rebuy 3.2.0 → 3.3.1

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.
@@ -23,6 +23,7 @@ __export(gwp_exports, {
23
23
  getValidGwpProductIds: () => getValidGwpProductIds,
24
24
  hasLostDiscount: () => hasLostDiscount,
25
25
  hasUnsupportedRuleTypes: () => hasUnsupportedRuleTypes,
26
+ isConfirmedRuleMiss: () => isConfirmedRuleMiss,
26
27
  isDefinitiveRuleAnswer: () => isDefinitiveRuleAnswer,
27
28
  shouldRemoveGift: () => shouldRemoveGift
28
29
  });
@@ -40,6 +41,9 @@ var ruleHasUnsupportedType = (rule) => (rule.logic ?? []).some(
40
41
  );
41
42
  var hasUnsupportedRuleTypes = (metadata) => [...metadata?.matchedRules ?? [], ...metadata?.unmatchedRules ?? []].some(ruleHasUnsupportedType);
42
43
 
44
+ // src/gwp/isConfirmedRuleMiss.ts
45
+ var isConfirmedRuleMiss = (metadata) => (metadata.unmatchedRules?.length ?? 0) > 0 && (metadata.matchedRules?.length ?? 0) === 0;
46
+
43
47
  // src/gwp/isDefinitiveRuleAnswer.ts
44
48
  var isDefinitiveRuleAnswer = (metadata) => metadata != null && (metadata.matchedRules != null || metadata.unmatchedRules != null);
45
49
 
@@ -1,5 +1,6 @@
1
1
  export * from '../gwp/getValidGwpProductIds';
2
2
  export * from '../gwp/hasUnsupportedRuleTypes';
3
+ export * from '../gwp/isConfirmedRuleMiss';
3
4
  export * from '../gwp/isDefinitiveRuleAnswer';
4
5
  export * from '../gwp/shouldRemoveGift';
5
6
  export * from '../gwp/types';
@@ -10,6 +10,9 @@ var ruleHasUnsupportedType = (rule) => (rule.logic ?? []).some(
10
10
  );
11
11
  var hasUnsupportedRuleTypes = (metadata) => [...metadata?.matchedRules ?? [], ...metadata?.unmatchedRules ?? []].some(ruleHasUnsupportedType);
12
12
 
13
+ // src/gwp/isConfirmedRuleMiss.ts
14
+ var isConfirmedRuleMiss = (metadata) => (metadata.unmatchedRules?.length ?? 0) > 0 && (metadata.matchedRules?.length ?? 0) === 0;
15
+
13
16
  // src/gwp/isDefinitiveRuleAnswer.ts
14
17
  var isDefinitiveRuleAnswer = (metadata) => metadata != null && (metadata.matchedRules != null || metadata.unmatchedRules != null);
15
18
 
@@ -20,6 +23,7 @@ export {
20
23
  getValidGwpProductIds,
21
24
  hasLostDiscount,
22
25
  hasUnsupportedRuleTypes,
26
+ isConfirmedRuleMiss,
23
27
  isDefinitiveRuleAnswer,
24
28
  shouldRemoveGift
25
29
  };
@@ -0,0 +1,12 @@
1
+ import { type GwpMetadata } from '../gwp/types';
2
+ /**
3
+ * Whether a definitive answer affirmatively evaluated the ruleset and NOTHING matched:
4
+ * `unmatchedRules` carries the evaluated-and-failed rules while `matchedRules` is empty. This is
5
+ * the only empty-valid-set shape that may remove gifts on qualification. An empty valid set is
6
+ * otherwise ambiguous — a MATCHED rule whose output enumerates no product ids (collection /
7
+ * endpoint / tag outputs carry none) or a zero-rule ruleset proves nothing, and removing on it
8
+ * would confiscate a still-qualified gift permanently (the watcher never re-adds). Mirrors the
9
+ * React extension's `isConfirmedRuleMiss` (REB-23842); the discount-lost pass bounds the
10
+ * fail-open cost on every path.
11
+ */
12
+ export declare const isConfirmedRuleMiss: (metadata: GwpMetadata) => boolean;
@@ -8,8 +8,11 @@ import { type GiftValidationInput, type ServerContext } from '../server/shared';
8
8
  * the rules) needs the engine's answer, so it is **fail-safe**: any error, a missing endpoint, a
9
9
  * non-definitive rule answer (no metadata, or metadata with neither rule array — the tolerated-empty-2xx
10
10
  * path), or unsupported rule types (`url`/`order_tag`) → don't drop on qualification (never remove a
11
- * genuinely-qualified gift on uncertainty). A definitive answer that omits a gift even `matchedRules: []`
12
- * removes it. **Discount loss** ({@link hasLostDiscount}: `!discounted && cost > 0`) is cart-observable
11
+ * genuinely-qualified gift on uncertainty). A definitive answer removes a gift its matched-rule product
12
+ * output omits; when that output yields NO ids at all, only a confirmed miss ({@link isConfirmedRuleMiss}:
13
+ * `unmatchedRules` populated, `matchedRules` empty) removes — a matched rule with a non-product output
14
+ * (collection/endpoint/tag) or a zero-rule ruleset proves nothing, so those keep the gifts (REB-23842,
15
+ * matching the React extension). **Discount loss** ({@link hasLostDiscount}: `!discounted && cost > 0`) is cart-observable
13
16
  * and needs NO engine answer, so it removes the gift on EVERY path, degraded or not — a buyer must not keep
14
17
  * paying for a "gift" because the rule engine was degraded. (React removed on any successful fetch; this
15
18
  * splits the observable discount-loss out of the qualification fail-safe rather than gating both behind it.)
@@ -61,6 +61,9 @@ var ruleHasUnsupportedType = (rule) => (rule.logic ?? []).some(
61
61
  );
62
62
  var hasUnsupportedRuleTypes = (metadata) => [...metadata?.matchedRules ?? [], ...metadata?.unmatchedRules ?? []].some(ruleHasUnsupportedType);
63
63
 
64
+ // src/gwp/isConfirmedRuleMiss.ts
65
+ var isConfirmedRuleMiss = (metadata) => (metadata.unmatchedRules?.length ?? 0) > 0 && (metadata.matchedRules?.length ?? 0) === 0;
66
+
64
67
  // src/gwp/isDefinitiveRuleAnswer.ts
65
68
  var isDefinitiveRuleAnswer = (metadata) => metadata != null && (metadata.matchedRules != null || metadata.unmatchedRules != null);
66
69
 
@@ -3882,6 +3885,7 @@ var validateGifts = async (input, ctx) => {
3882
3885
  const meta = metadata ?? void 0;
3883
3886
  if (!isDefinitiveRuleAnswer(meta) || hasUnsupportedRuleTypes(meta)) return discountLost;
3884
3887
  const validIds = getValidGwpProductIds(meta.matchedRules);
3888
+ if (validIds.size === 0 && !isConfirmedRuleMiss(meta)) return discountLost;
3885
3889
  return gifts.filter((gift) => shouldRemoveGift(gift, validIds)).map((gift) => gift.lineId);
3886
3890
  } catch {
3887
3891
  return discountLost;
@@ -4386,7 +4390,8 @@ var MonetizeInputSchema = import_zod28.z.object({
4386
4390
  language: import_zod28.z.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional(),
4387
4391
  /** Buyer-facing order name (e.g. `#1001`) for the server-composed "Order … Confirmed" chrome line. */
4388
4392
  orderName: import_zod28.z.string().max(REQUEST_BOUNDS.STRING_TEXT_MAX).optional(),
4389
- placement: import_zod28.z.enum(["order_status", "thank_you_page"]),
4393
+ /** Ads surface label, forwarded verbatim; `order_index_page` is measured/floored as its own surface (REB-23901). */
4394
+ placement: import_zod28.z.enum(["order_index_page", "order_status", "thank_you_page"]),
4390
4395
  /** Privacy Policy footer href — the Monetize widget's `privacy_policy_url` setting (React parity). */
4391
4396
  privacyPolicyUrl: import_zod28.z.string().max(REQUEST_BOUNDS.STRING_URL_MAX).optional(),
4392
4397
  /** 30-min rolling session id (React parity); used for the ads `session_id`, else the visitor id. */
@@ -13,6 +13,9 @@ var ruleHasUnsupportedType = (rule) => (rule.logic ?? []).some(
13
13
  );
14
14
  var hasUnsupportedRuleTypes = (metadata) => [...metadata?.matchedRules ?? [], ...metadata?.unmatchedRules ?? []].some(ruleHasUnsupportedType);
15
15
 
16
+ // src/gwp/isConfirmedRuleMiss.ts
17
+ var isConfirmedRuleMiss = (metadata) => (metadata.unmatchedRules?.length ?? 0) > 0 && (metadata.matchedRules?.length ?? 0) === 0;
18
+
16
19
  // src/gwp/isDefinitiveRuleAnswer.ts
17
20
  var isDefinitiveRuleAnswer = (metadata) => metadata != null && (metadata.matchedRules != null || metadata.unmatchedRules != null);
18
21
 
@@ -3834,6 +3837,7 @@ var validateGifts = async (input, ctx) => {
3834
3837
  const meta = metadata ?? void 0;
3835
3838
  if (!isDefinitiveRuleAnswer(meta) || hasUnsupportedRuleTypes(meta)) return discountLost;
3836
3839
  const validIds = getValidGwpProductIds(meta.matchedRules);
3840
+ if (validIds.size === 0 && !isConfirmedRuleMiss(meta)) return discountLost;
3837
3841
  return gifts.filter((gift) => shouldRemoveGift(gift, validIds)).map((gift) => gift.lineId);
3838
3842
  } catch {
3839
3843
  return discountLost;
@@ -4338,7 +4342,8 @@ var MonetizeInputSchema = z28.object({
4338
4342
  language: z28.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional(),
4339
4343
  /** Buyer-facing order name (e.g. `#1001`) for the server-composed "Order … Confirmed" chrome line. */
4340
4344
  orderName: z28.string().max(REQUEST_BOUNDS.STRING_TEXT_MAX).optional(),
4341
- placement: z28.enum(["order_status", "thank_you_page"]),
4345
+ /** Ads surface label, forwarded verbatim; `order_index_page` is measured/floored as its own surface (REB-23901). */
4346
+ placement: z28.enum(["order_index_page", "order_status", "thank_you_page"]),
4342
4347
  /** Privacy Policy footer href — the Monetize widget's `privacy_policy_url` setting (React parity). */
4343
4348
  privacyPolicyUrl: z28.string().max(REQUEST_BOUNDS.STRING_URL_MAX).optional(),
4344
4349
  /** 30-min rolling session id (React parity); used for the ads `session_id`, else the visitor id. */
@@ -217,6 +217,7 @@ export declare const MonetizeInputSchema: z.ZodObject<{
217
217
  language: z.ZodOptional<z.ZodString>;
218
218
  orderName: z.ZodOptional<z.ZodString>;
219
219
  placement: z.ZodEnum<{
220
+ order_index_page: "order_index_page";
220
221
  order_status: "order_status";
221
222
  thank_you_page: "thank_you_page";
222
223
  }>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rebuy/rebuy",
3
- "version": "3.2.0",
3
+ "version": "3.3.1",
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.",