@rebuy/rebuy 3.23.0 → 3.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,6 +17,7 @@ Node `^22.14.0 || ^24.10.0 || ^26.0.0` for development; the published artifacts
17
17
  | Subpath | Contents |
18
18
  | --- | --- |
19
19
  | `@rebuy/rebuy` | Root barrel: schemas, client helpers, utilities |
20
+ | `@rebuy/rebuy/cab` | zod-free CAB renderer helpers shared by every renderer (`contentText`, `tiptapPlainText`, `tiptapRuns`, `asLayout`, `idFromGid`, `toCents`, `decoratedOrderId`) |
20
21
  | `@rebuy/rebuy/client` | `RebuyClient` API client |
21
22
  | `@rebuy/rebuy/gwp` | Gift-with-purchase validation helpers |
22
23
  | `@rebuy/rebuy/pricing` | Variant pricing/discount forecast (`priceVariant`, `currencyDecimals`) |
@@ -0,0 +1,12 @@
1
+ import type { CABLayoutSection } from '../schema/widgets/checkout-and-beyond/layout';
2
+ /**
3
+ * Render a layout-derived section (cart-line, offers, carousel, banner) as a plain layout; the variable
4
+ * fields are dropped — the scope was already mounted and the visibility binding already evaluated at
5
+ * dispatch, and `mutators` already has its host (a re-dispatch must not double any of them).
6
+ *
7
+ * The literal (checked against `SectionType`) rather than the runtime enum: `common.ts` is not tree-shakeable,
8
+ * so importing one constant would drag every CAB enum into each renderer that bundles this subpath.
9
+ */
10
+ export declare const asLayout: <T extends {
11
+ sectionType: string;
12
+ }>(section: T) => CABLayoutSection;
@@ -0,0 +1,8 @@
1
+ import { type Variables } from '../cab/interpolateAndSanitize';
2
+ import type { CABTextSection } from '../schema/widgets/checkout-and-beyond/text';
3
+ /**
4
+ * A text section's buyer-language copy as ONE interpolated, sanitized plain string — rich content flattens
5
+ * to bare text (marks and structure drop). The shared reading behind button label slots and the `content`
6
+ * mutator trigger, so "what does this copy say" has a single answer.
7
+ */
8
+ export declare const contentText: (content: CABTextSection["content"], language?: string, variables?: Variables) => string;
@@ -0,0 +1,2 @@
1
+ /** Numeric part of the decorated order id (`gid://shopify/Order(Identity)/123` → `123`) → ads `transaction_id`. */
2
+ export declare const decoratedOrderId: (orderId: string | undefined) => string | undefined;
@@ -0,0 +1,2 @@
1
+ /** `gid://shopify/X/123` → `123` — the engine speaks numeric ids, Shopify's APIs speak gids. */
2
+ export declare const idFromGid: (gid: string) => string;
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/cab/index.ts
21
+ var cab_exports = {};
22
+ __export(cab_exports, {
23
+ asLayout: () => asLayout,
24
+ contentText: () => contentText,
25
+ decoratedOrderId: () => decoratedOrderId,
26
+ idFromGid: () => idFromGid,
27
+ interpolateAndSanitize: () => interpolateAndSanitize,
28
+ isTiptapDoc: () => isTiptapDoc,
29
+ selectLanguage: () => selectLanguage,
30
+ tiptapPlainText: () => tiptapPlainText,
31
+ tiptapRuns: () => tiptapRuns,
32
+ toCents: () => toCents
33
+ });
34
+ module.exports = __toCommonJS(cab_exports);
35
+
36
+ // src/cab/asLayout.ts
37
+ var asLayout = (section) => ({
38
+ ...section,
39
+ mutators: void 0,
40
+ sectionType: "layout",
41
+ variables: void 0,
42
+ visibility: void 0
43
+ });
44
+
45
+ // src/schema/widgets/checkout-and-beyond/regex.ts
46
+ var DYNAMIC_TOKEN_REGEX = /^\{\{\s*[A-Za-z]+\s*\}\}$/;
47
+ var DYNAMIC_TOKEN_ANYWHERE_REGEX = /\{\{\s*[A-Za-z0-9_]+\s*\}\}/;
48
+ var HTML_TAGS_REGEX = /<\/?[a-zA-Z][^>]*>/g;
49
+
50
+ // src/cab/interpolateAndSanitize.ts
51
+ var TOKEN_REGEX = /{{1,2}\s*([^{}\s]+)\s*}{1,2}/g;
52
+ var toCamelCase = (key) => key.replace(/_([a-z0-9])/g, (_, char) => char.toUpperCase());
53
+ var resolveKey = (key, variables) => {
54
+ if (Object.hasOwn(variables, key)) return key;
55
+ const camel = toCamelCase(key);
56
+ if (Object.hasOwn(variables, camel)) return camel;
57
+ const lower = camel.toLowerCase();
58
+ return Object.keys(variables).find((name) => name.toLowerCase() === lower);
59
+ };
60
+ var interpolateAndSanitize = (input, variables) => (input ?? "").replace(TOKEN_REGEX, (match, key) => {
61
+ if (!variables) return match;
62
+ const resolved = resolveKey(key, variables);
63
+ return resolved === void 0 ? match : String(variables[resolved] ?? "");
64
+ }).replace(HTML_TAGS_REGEX, "");
65
+
66
+ // src/cab/selectLanguage.ts
67
+ var FALLBACK_LANGUAGE = "en";
68
+ var selectLanguage = (content, language) => {
69
+ if (!content) return void 0;
70
+ if (language && Object.hasOwn(content, language)) return content[language];
71
+ if (Object.hasOwn(content, FALLBACK_LANGUAGE)) return content[FALLBACK_LANGUAGE];
72
+ return Object.values(content)[0];
73
+ };
74
+
75
+ // src/cab/listOf.ts
76
+ var listOf = (value) => Array.isArray(value) ? value.filter((item) => !!item) : [];
77
+
78
+ // src/cab/tiptapPlain.ts
79
+ var isTiptapDoc = (value) => !!value && typeof value === "object" && value.type === "doc";
80
+ var tiptapPlainText = (doc) => listOf(doc?.content).map(
81
+ (paragraph) => listOf(paragraph.content).map((node) => node.text ?? "").join("")
82
+ ).join(" ").trim();
83
+
84
+ // src/cab/contentText.ts
85
+ var contentText = (content, language, variables) => {
86
+ const selected = selectLanguage(content, language);
87
+ const text = typeof selected === "string" ? selected : isTiptapDoc(selected) ? tiptapPlainText(selected) : "";
88
+ return interpolateAndSanitize(text, variables);
89
+ };
90
+
91
+ // src/cab/decoratedOrderId.ts
92
+ var decoratedOrderId = (orderId) => orderId?.match(/\/Order(?:Identity)?\/(\d+)$/)?.[1] ?? orderId;
93
+
94
+ // src/cab/gid.ts
95
+ var idFromGid = (gid) => gid.slice(gid.lastIndexOf("/") + 1);
96
+
97
+ // src/transforms/htmlToTiptap/isValidHref.ts
98
+ var RELATIVE_BASE = "https://x.invalid";
99
+ var ALLOWED_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]);
100
+ var isValidHref = (href) => {
101
+ if (!href) return false;
102
+ if (href === "#") return true;
103
+ if (DYNAMIC_TOKEN_REGEX.test(href)) return true;
104
+ if (href.startsWith("/")) {
105
+ try {
106
+ return new URL(href, RELATIVE_BASE).origin === RELATIVE_BASE;
107
+ } catch {
108
+ return false;
109
+ }
110
+ }
111
+ try {
112
+ const url = new URL(href);
113
+ return ALLOWED_PROTOCOLS.has(url.protocol);
114
+ } catch {
115
+ return false;
116
+ }
117
+ };
118
+
119
+ // src/transforms/htmlToTiptap/linkSafety.ts
120
+ var DEFAULT_LINK_TARGET = "_blank";
121
+ var DEFAULT_LINK_REL = "noopener noreferrer nofollow";
122
+ var ensureSafeRel = (rel, target) => {
123
+ if (target !== DEFAULT_LINK_TARGET) return rel;
124
+ const sourceTokens = (rel ?? "").split(/\s+/).filter(Boolean);
125
+ if (sourceTokens.length === 0) return DEFAULT_LINK_REL;
126
+ const tokens = new Set(sourceTokens.filter((token) => token.toLowerCase() !== "opener"));
127
+ tokens.add("noopener");
128
+ tokens.add("noreferrer");
129
+ return Array.from(tokens).join(" ");
130
+ };
131
+
132
+ // src/cab/tiptapRuns.ts
133
+ var HEADING = "heading";
134
+ var isHeadingMark = (mark) => mark.type === "textStyle" && mark.attrs?.fontSize === HEADING;
135
+ var isRenderableHref = (href) => isValidHref(href) && !DYNAMIC_TOKEN_ANYWHERE_REGEX.test(href);
136
+ var toRun = (node, variables) => {
137
+ const run = {
138
+ bold: false,
139
+ italic: false,
140
+ strike: false,
141
+ text: interpolateAndSanitize(node.text ?? "", variables)
142
+ };
143
+ let link;
144
+ let textStyle;
145
+ for (const mark of listOf(node.marks)) {
146
+ if (mark.type === "bold") run.bold = true;
147
+ else if (mark.type === "italic") run.italic = true;
148
+ else if (mark.type === "strike") run.strike = true;
149
+ else if (mark.type === "link") link = mark;
150
+ else if (mark.type === "textStyle") textStyle = mark;
151
+ }
152
+ const color = textStyle?.attrs?.color;
153
+ const fontSize = textStyle?.attrs?.fontSize;
154
+ if (color) run.color = color;
155
+ if (fontSize && fontSize !== HEADING) run.fontSize = fontSize;
156
+ if (link) {
157
+ const href = interpolateAndSanitize(link.attrs?.href ?? "", variables);
158
+ if (isRenderableHref(href)) {
159
+ const target = link.attrs?.target ?? DEFAULT_LINK_TARGET;
160
+ const rel = ensureSafeRel(link.attrs?.rel, target);
161
+ run.href = href;
162
+ if (rel) run.rel = rel;
163
+ if (target === DEFAULT_LINK_TARGET) run.target = DEFAULT_LINK_TARGET;
164
+ }
165
+ }
166
+ return run;
167
+ };
168
+ var tiptapRuns = (paragraph, variables) => {
169
+ const content = listOf(paragraph?.content);
170
+ return {
171
+ heading: content.some((node) => listOf(node.marks).some(isHeadingMark)),
172
+ runs: content.map((node) => toRun(node, variables))
173
+ };
174
+ };
175
+
176
+ // src/cab/toCents.ts
177
+ var toCents = (amount) => Math.round(Number(amount ?? 0) * 100);
@@ -0,0 +1,9 @@
1
+ export * from '../cab/asLayout';
2
+ export * from '../cab/contentText';
3
+ export * from '../cab/decoratedOrderId';
4
+ export * from '../cab/gid';
5
+ export * from '../cab/interpolateAndSanitize';
6
+ export * from '../cab/selectLanguage';
7
+ export * from '../cab/tiptapPlain';
8
+ export * from '../cab/tiptapRuns';
9
+ export * from '../cab/toCents';
@@ -0,0 +1,154 @@
1
+ // src/cab/asLayout.ts
2
+ var asLayout = (section) => ({
3
+ ...section,
4
+ mutators: void 0,
5
+ sectionType: "layout",
6
+ variables: void 0,
7
+ visibility: void 0
8
+ });
9
+
10
+ // src/schema/widgets/checkout-and-beyond/regex.ts
11
+ var DYNAMIC_TOKEN_REGEX = /^\{\{\s*[A-Za-z]+\s*\}\}$/;
12
+ var DYNAMIC_TOKEN_ANYWHERE_REGEX = /\{\{\s*[A-Za-z0-9_]+\s*\}\}/;
13
+ var HTML_TAGS_REGEX = /<\/?[a-zA-Z][^>]*>/g;
14
+
15
+ // src/cab/interpolateAndSanitize.ts
16
+ var TOKEN_REGEX = /{{1,2}\s*([^{}\s]+)\s*}{1,2}/g;
17
+ var toCamelCase = (key) => key.replace(/_([a-z0-9])/g, (_, char) => char.toUpperCase());
18
+ var resolveKey = (key, variables) => {
19
+ if (Object.hasOwn(variables, key)) return key;
20
+ const camel = toCamelCase(key);
21
+ if (Object.hasOwn(variables, camel)) return camel;
22
+ const lower = camel.toLowerCase();
23
+ return Object.keys(variables).find((name) => name.toLowerCase() === lower);
24
+ };
25
+ var interpolateAndSanitize = (input, variables) => (input ?? "").replace(TOKEN_REGEX, (match, key) => {
26
+ if (!variables) return match;
27
+ const resolved = resolveKey(key, variables);
28
+ return resolved === void 0 ? match : String(variables[resolved] ?? "");
29
+ }).replace(HTML_TAGS_REGEX, "");
30
+
31
+ // src/cab/selectLanguage.ts
32
+ var FALLBACK_LANGUAGE = "en";
33
+ var selectLanguage = (content, language) => {
34
+ if (!content) return void 0;
35
+ if (language && Object.hasOwn(content, language)) return content[language];
36
+ if (Object.hasOwn(content, FALLBACK_LANGUAGE)) return content[FALLBACK_LANGUAGE];
37
+ return Object.values(content)[0];
38
+ };
39
+
40
+ // src/cab/listOf.ts
41
+ var listOf = (value) => Array.isArray(value) ? value.filter((item) => !!item) : [];
42
+
43
+ // src/cab/tiptapPlain.ts
44
+ var isTiptapDoc = (value) => !!value && typeof value === "object" && value.type === "doc";
45
+ var tiptapPlainText = (doc) => listOf(doc?.content).map(
46
+ (paragraph) => listOf(paragraph.content).map((node) => node.text ?? "").join("")
47
+ ).join(" ").trim();
48
+
49
+ // src/cab/contentText.ts
50
+ var contentText = (content, language, variables) => {
51
+ const selected = selectLanguage(content, language);
52
+ const text = typeof selected === "string" ? selected : isTiptapDoc(selected) ? tiptapPlainText(selected) : "";
53
+ return interpolateAndSanitize(text, variables);
54
+ };
55
+
56
+ // src/cab/decoratedOrderId.ts
57
+ var decoratedOrderId = (orderId) => orderId?.match(/\/Order(?:Identity)?\/(\d+)$/)?.[1] ?? orderId;
58
+
59
+ // src/cab/gid.ts
60
+ var idFromGid = (gid) => gid.slice(gid.lastIndexOf("/") + 1);
61
+
62
+ // src/transforms/htmlToTiptap/isValidHref.ts
63
+ var RELATIVE_BASE = "https://x.invalid";
64
+ var ALLOWED_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]);
65
+ var isValidHref = (href) => {
66
+ if (!href) return false;
67
+ if (href === "#") return true;
68
+ if (DYNAMIC_TOKEN_REGEX.test(href)) return true;
69
+ if (href.startsWith("/")) {
70
+ try {
71
+ return new URL(href, RELATIVE_BASE).origin === RELATIVE_BASE;
72
+ } catch {
73
+ return false;
74
+ }
75
+ }
76
+ try {
77
+ const url = new URL(href);
78
+ return ALLOWED_PROTOCOLS.has(url.protocol);
79
+ } catch {
80
+ return false;
81
+ }
82
+ };
83
+
84
+ // src/transforms/htmlToTiptap/linkSafety.ts
85
+ var DEFAULT_LINK_TARGET = "_blank";
86
+ var DEFAULT_LINK_REL = "noopener noreferrer nofollow";
87
+ var ensureSafeRel = (rel, target) => {
88
+ if (target !== DEFAULT_LINK_TARGET) return rel;
89
+ const sourceTokens = (rel ?? "").split(/\s+/).filter(Boolean);
90
+ if (sourceTokens.length === 0) return DEFAULT_LINK_REL;
91
+ const tokens = new Set(sourceTokens.filter((token) => token.toLowerCase() !== "opener"));
92
+ tokens.add("noopener");
93
+ tokens.add("noreferrer");
94
+ return Array.from(tokens).join(" ");
95
+ };
96
+
97
+ // src/cab/tiptapRuns.ts
98
+ var HEADING = "heading";
99
+ var isHeadingMark = (mark) => mark.type === "textStyle" && mark.attrs?.fontSize === HEADING;
100
+ var isRenderableHref = (href) => isValidHref(href) && !DYNAMIC_TOKEN_ANYWHERE_REGEX.test(href);
101
+ var toRun = (node, variables) => {
102
+ const run = {
103
+ bold: false,
104
+ italic: false,
105
+ strike: false,
106
+ text: interpolateAndSanitize(node.text ?? "", variables)
107
+ };
108
+ let link;
109
+ let textStyle;
110
+ for (const mark of listOf(node.marks)) {
111
+ if (mark.type === "bold") run.bold = true;
112
+ else if (mark.type === "italic") run.italic = true;
113
+ else if (mark.type === "strike") run.strike = true;
114
+ else if (mark.type === "link") link = mark;
115
+ else if (mark.type === "textStyle") textStyle = mark;
116
+ }
117
+ const color = textStyle?.attrs?.color;
118
+ const fontSize = textStyle?.attrs?.fontSize;
119
+ if (color) run.color = color;
120
+ if (fontSize && fontSize !== HEADING) run.fontSize = fontSize;
121
+ if (link) {
122
+ const href = interpolateAndSanitize(link.attrs?.href ?? "", variables);
123
+ if (isRenderableHref(href)) {
124
+ const target = link.attrs?.target ?? DEFAULT_LINK_TARGET;
125
+ const rel = ensureSafeRel(link.attrs?.rel, target);
126
+ run.href = href;
127
+ if (rel) run.rel = rel;
128
+ if (target === DEFAULT_LINK_TARGET) run.target = DEFAULT_LINK_TARGET;
129
+ }
130
+ }
131
+ return run;
132
+ };
133
+ var tiptapRuns = (paragraph, variables) => {
134
+ const content = listOf(paragraph?.content);
135
+ return {
136
+ heading: content.some((node) => listOf(node.marks).some(isHeadingMark)),
137
+ runs: content.map((node) => toRun(node, variables))
138
+ };
139
+ };
140
+
141
+ // src/cab/toCents.ts
142
+ var toCents = (amount) => Math.round(Number(amount ?? 0) * 100);
143
+ export {
144
+ asLayout,
145
+ contentText,
146
+ decoratedOrderId,
147
+ idFromGid,
148
+ interpolateAndSanitize,
149
+ isTiptapDoc,
150
+ selectLanguage,
151
+ tiptapPlainText,
152
+ tiptapRuns,
153
+ toCents
154
+ };
@@ -0,0 +1,8 @@
1
+ export type Variables = Record<string, string | number | null | undefined>;
2
+ /**
3
+ * Interpolate tokens then strip HTML (with the same tag-shaped regex the schema validates against, so the
4
+ * client never mangles copy the server accepts) — defense-in-depth, since token values can carry HTML from
5
+ * cart/merchant settings. Unknown tokens are left intact so missing bindings surface during dev (and so
6
+ * incidental single-brace text that names no variable is never touched).
7
+ */
8
+ export declare const interpolateAndSanitize: (input: string, variables?: Variables) => string;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Raw-payload tolerance for the list-shaped Tiptap fields. `TiptapDocument.content`,
3
+ * `TiptapParagraph.content` and `TiptapText.marks` are each
4
+ * `z.union([z.array(…), z.strictObject({}).transform(() => [])])` — that second arm exists because stored
5
+ * payloads serialize an empty list as `{}` — and any of them can carry `null` holes. The CAB renderer
6
+ * helpers read UNPARSED payloads, so none of those shapes may throw; a malformed list degrades to empty
7
+ * rather than killing the section around it.
8
+ */
9
+ export declare const listOf: <T>(value: unknown) => T[];
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Fallback chain: requested language, then 'en', then first available value.
3
+ *
4
+ * `Object.hasOwn`, never `in` — `in` would resolve an inherited `Object.prototype` key (`constructor`,
5
+ * `toString`) as if it were a translation. Far less reachable than in `interpolateAndSanitize`, since a
6
+ * language code is not merchant-authored, but the two must agree on what counts as present.
7
+ */
8
+ export declare const selectLanguage: <T>(content: Record<string, T> | undefined | null, language: string | undefined) => T | undefined;
@@ -0,0 +1,10 @@
1
+ import type { TiptapDocument } from '../schema/widgets/checkout-and-beyond/text';
2
+ /** Locale-resolved text content is either a plain string or a Tiptap document. */
3
+ export declare const isTiptapDoc: (value: unknown) => value is TiptapDocument;
4
+ /**
5
+ * The document's bare text — nodes concatenated, paragraphs space-joined; marks and structure drop.
6
+ *
7
+ * Every level goes through `listOf`, including the doc itself, because this reads raw payloads: the `{}`
8
+ * empty-list arm and `null` holes are both shapes the schema accepts, at any depth.
9
+ */
10
+ export declare const tiptapPlainText: (doc: TiptapDocument) => string;
@@ -0,0 +1,40 @@
1
+ import { type Variables } from '../cab/interpolateAndSanitize';
2
+ import type { InlineTextSizeName, TextColorName } from '../schema/widgets/checkout-and-beyond/common';
3
+ import type { TiptapParagraph } from '../schema/widgets/checkout-and-beyond/text';
4
+ /**
5
+ * One text run of a paragraph with its marks resolved to facts. Tokens (`color`, `fontSize`) are the
6
+ * payload's — mapping them to px/hex/tone is each renderer's. `bold` is reported even inside a heading;
7
+ * whether the heading element is already bold is the renderer's to know.
8
+ *
9
+ * A run carrying `href` is safe to hand to an anchor as-is: the scheme is allowlisted and, for
10
+ * `target: '_blank'`, `rel` is guaranteed to contain `noopener noreferrer`. Render BOTH — dropping `rel`
11
+ * re-opens reverse tabnabbing on merchant-authored links, which is the per-renderer knowledge this
12
+ * subpath exists to delete. A link whose href fails the allowlist yields a run with no `href`/`rel`/
13
+ * `target` at all, so it renders as plain text rather than as a live unsafe link.
14
+ */
15
+ export type TiptapRun = {
16
+ bold: boolean;
17
+ color?: TextColorName;
18
+ fontSize?: InlineTextSizeName;
19
+ href?: string;
20
+ italic: boolean;
21
+ rel?: string;
22
+ strike: boolean;
23
+ target?: '_blank';
24
+ text: string;
25
+ };
26
+ /** `heading` promotes the WHOLE paragraph (a block signal, never an inline size — see `textSizeNames`). */
27
+ export type TiptapParagraphRuns = {
28
+ heading: boolean;
29
+ runs: TiptapRun[];
30
+ };
31
+ /**
32
+ * A Tiptap paragraph as ordered styled runs plus its heading promotion — the one mark reading every
33
+ * renderer shares.
34
+ *
35
+ * Takes a payload in EITHER shape: schema-parsed, or raw off the wire. Malformed lists degrade to empty
36
+ * rather than throwing — including a nullish paragraph, so a renderer can map this straight over a raw
37
+ * `doc.content` — and the two link fields the schema defaults (`target`, `rel`) are resolved to the same
38
+ * effective values a parse would have produced, so a run never depends on whether the caller parsed.
39
+ */
40
+ export declare const tiptapRuns: (paragraph: TiptapParagraph, variables?: Variables) => TiptapParagraphRuns;
@@ -0,0 +1,2 @@
1
+ /** Presentment amount → integer cents (engine money unit); matches the cart-context subtotal conversion. */
2
+ export declare const toCents: (amount: string | number | undefined) => number;
package/dist/index.cjs CHANGED
@@ -90,6 +90,7 @@ __export(src_exports, {
90
90
  ContentOperator: () => ContentOperator,
91
91
  DEFAULT_ENDPOINTS: () => DEFAULT_ENDPOINTS,
92
92
  DESCRIPTION_LABELS: () => DESCRIPTION_LABELS,
93
+ DYNAMIC_TOKEN_ANYWHERE_REGEX: () => DYNAMIC_TOKEN_ANYWHERE_REGEX,
93
94
  DYNAMIC_TOKEN_REGEX: () => DYNAMIC_TOKEN_REGEX,
94
95
  Direction: () => Direction,
95
96
  DiscountSource: () => DiscountSource,
@@ -1936,6 +1937,7 @@ var import_compat = require("es-toolkit/compat");
1936
1937
  // src/schema/widgets/checkout-and-beyond/regex.ts
1937
1938
  var ARRAY_INDEX_STRING = /^(0|[1-9]\d*)$/;
1938
1939
  var DYNAMIC_TOKEN_REGEX = /^\{\{\s*[A-Za-z]+\s*\}\}$/;
1940
+ var DYNAMIC_TOKEN_ANYWHERE_REGEX = /\{\{\s*[A-Za-z0-9_]+\s*\}\}/;
1939
1941
  var HEX_COLOR_REGEX = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
1940
1942
  var ROOT_RELATIVE_PATH_REGEX = /^\/(?!\/)/;
1941
1943
  var HTML_TAGS_REGEX = /<\/?[a-zA-Z][^>]*>/g;
package/dist/index.mjs CHANGED
@@ -1642,6 +1642,7 @@ import { get as get2, isString } from "es-toolkit/compat";
1642
1642
  // src/schema/widgets/checkout-and-beyond/regex.ts
1643
1643
  var ARRAY_INDEX_STRING = /^(0|[1-9]\d*)$/;
1644
1644
  var DYNAMIC_TOKEN_REGEX = /^\{\{\s*[A-Za-z]+\s*\}\}$/;
1645
+ var DYNAMIC_TOKEN_ANYWHERE_REGEX = /\{\{\s*[A-Za-z0-9_]+\s*\}\}/;
1645
1646
  var HEX_COLOR_REGEX = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
1646
1647
  var ROOT_RELATIVE_PATH_REGEX = /^\/(?!\/)/;
1647
1648
  var HTML_TAGS_REGEX = /<\/?[a-zA-Z][^>]*>/g;
@@ -4915,6 +4916,7 @@ export {
4915
4916
  ContentOperator,
4916
4917
  DEFAULT_ENDPOINTS,
4917
4918
  DESCRIPTION_LABELS,
4919
+ DYNAMIC_TOKEN_ANYWHERE_REGEX,
4918
4920
  DYNAMIC_TOKEN_REGEX,
4919
4921
  Direction,
4920
4922
  DiscountSource,
@@ -79,6 +79,7 @@ __export(checkout_and_beyond_exports, {
79
79
  ContentOperator: () => ContentOperator,
80
80
  DEFAULT_ENDPOINTS: () => DEFAULT_ENDPOINTS,
81
81
  DESCRIPTION_LABELS: () => DESCRIPTION_LABELS,
82
+ DYNAMIC_TOKEN_ANYWHERE_REGEX: () => DYNAMIC_TOKEN_ANYWHERE_REGEX,
82
83
  DYNAMIC_TOKEN_REGEX: () => DYNAMIC_TOKEN_REGEX,
83
84
  Direction: () => Direction,
84
85
  DiscountSource: () => DiscountSource,
@@ -510,6 +511,7 @@ var IconTone = freezeEnum(iconTones);
510
511
  // src/schema/widgets/checkout-and-beyond/regex.ts
511
512
  var ARRAY_INDEX_STRING = /^(0|[1-9]\d*)$/;
512
513
  var DYNAMIC_TOKEN_REGEX = /^\{\{\s*[A-Za-z]+\s*\}\}$/;
514
+ var DYNAMIC_TOKEN_ANYWHERE_REGEX = /\{\{\s*[A-Za-z0-9_]+\s*\}\}/;
513
515
  var HEX_COLOR_REGEX = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
514
516
  var ROOT_RELATIVE_PATH_REGEX = /^\/(?!\/)/;
515
517
  var HTML_TAGS_REGEX = /<\/?[a-zA-Z][^>]*>/g;
@@ -306,6 +306,7 @@ var IconTone = freezeEnum(iconTones);
306
306
  // src/schema/widgets/checkout-and-beyond/regex.ts
307
307
  var ARRAY_INDEX_STRING = /^(0|[1-9]\d*)$/;
308
308
  var DYNAMIC_TOKEN_REGEX = /^\{\{\s*[A-Za-z]+\s*\}\}$/;
309
+ var DYNAMIC_TOKEN_ANYWHERE_REGEX = /\{\{\s*[A-Za-z0-9_]+\s*\}\}/;
309
310
  var HEX_COLOR_REGEX = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
310
311
  var ROOT_RELATIVE_PATH_REGEX = /^\/(?!\/)/;
311
312
  var HTML_TAGS_REGEX = /<\/?[a-zA-Z][^>]*>/g;
@@ -1942,6 +1943,7 @@ export {
1942
1943
  ContentOperator,
1943
1944
  DEFAULT_ENDPOINTS,
1944
1945
  DESCRIPTION_LABELS,
1946
+ DYNAMIC_TOKEN_ANYWHERE_REGEX,
1945
1947
  DYNAMIC_TOKEN_REGEX,
1946
1948
  Direction,
1947
1949
  DiscountSource,
@@ -1,7 +1,29 @@
1
1
  /** Matches a valid array index string (e.g., "0", "1", "42"). */
2
2
  export declare const ARRAY_INDEX_STRING: RegExp;
3
- /** Matches a dynamic token. */
3
+ /**
4
+ * VALIDATION grammar: whether a whole value IS a dynamic token. Gates what may be STORED (a link href in
5
+ * `text.ts`, an image source in `image.ts`), so widening it is a schema shape change that ripples to every
6
+ * consumer. Deliberately narrow — letters only.
7
+ */
4
8
  export declare const DYNAMIC_TOKEN_REGEX: RegExp;
9
+ /**
10
+ * DETECTION grammar: whether a value still CONTAINS a token after interpolation, i.e. one that failed to
11
+ * bind. Deliberately NOT derived from the validation grammar above — the two pull in opposite directions.
12
+ * Validation must stay narrow to keep stored values predictable; detection must be as wide as what
13
+ * `interpolateAndSanitize` binds in the CANONICAL double-brace spelling, because anything it misses there
14
+ * is a dead link handed to a renderer. Hence `[A-Za-z0-9_]+` on the key: `resolveKey` camelizes legacy
15
+ * snake_case (`{{utm_source}}` binds to `utmSource`), so those spellings have to be detectable too.
16
+ *
17
+ * Still double-brace only, and still no `.`: that is what keeps third-party click macros — Google Ads'
18
+ * `{placement}`, Meta's `{{ad.id}}` — out of it. Nothing binds those names today, and dropping a working
19
+ * ad link is worse than leaving a dead one.
20
+ *
21
+ * BOTH exclusions are deliberate carve-outs rather than faithful widenings. `TOKEN_REGEX` quantifies open
22
+ * and close braces independently, so `{slug}` and `{{slug}` bind yet go undetected; its key class is
23
+ * `[^{}\s]+`, so a dotted name would bind too if anything were ever registered under one. Each keeps a
24
+ * probably-dead link, known and tolerated as the cheaper error.
25
+ */
26
+ export declare const DYNAMIC_TOKEN_ANYWHERE_REGEX: RegExp;
5
27
  /** Matches a valid hex color code. */
6
28
  export declare const HEX_COLOR_REGEX: RegExp;
7
29
  /**
@@ -2453,13 +2453,19 @@ var isSkipping = (stack = []) => stack.some(({ skip }) => skip);
2453
2453
  var makeDoc = (content, blockSpacing) => TiptapDocument.parse({ attrs: { blockSpacing }, content });
2454
2454
 
2455
2455
  // src/transforms/htmlToTiptap/isValidHref.ts
2456
- var DYNAMIC_TOKEN_REGEX2 = /^\{\{\s*[A-Za-z]+\s*\}\}$/;
2456
+ var RELATIVE_BASE = "https://x.invalid";
2457
2457
  var ALLOWED_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]);
2458
2458
  var isValidHref = (href) => {
2459
2459
  if (!href) return false;
2460
2460
  if (href === "#") return true;
2461
- if (DYNAMIC_TOKEN_REGEX2.test(href)) return true;
2462
- if (href.startsWith("/") && !href.startsWith("//")) return true;
2461
+ if (DYNAMIC_TOKEN_REGEX.test(href)) return true;
2462
+ if (href.startsWith("/")) {
2463
+ try {
2464
+ return new URL(href, RELATIVE_BASE).origin === RELATIVE_BASE;
2465
+ } catch {
2466
+ return false;
2467
+ }
2468
+ }
2463
2469
  try {
2464
2470
  const url = new URL(href);
2465
2471
  return ALLOWED_PROTOCOLS.has(url.protocol);
@@ -2468,26 +2474,32 @@ var isValidHref = (href) => {
2468
2474
  }
2469
2475
  };
2470
2476
 
2471
- // src/transforms/htmlToTiptap/buildLinkMark.ts
2472
- var DEFAULT_TARGET = "_blank";
2473
- var DEFAULT_REL = "noopener noreferrer nofollow";
2477
+ // src/transforms/htmlToTiptap/linkSafety.ts
2478
+ var DEFAULT_LINK_TARGET = "_blank";
2479
+ var DEFAULT_LINK_REL = "noopener noreferrer nofollow";
2474
2480
  var ensureSafeRel = (rel, target) => {
2475
- if (target !== "_blank") return rel;
2481
+ if (target !== DEFAULT_LINK_TARGET) return rel;
2476
2482
  const sourceTokens = (rel ?? "").split(/\s+/).filter(Boolean);
2477
- if (sourceTokens.length === 0) return DEFAULT_REL;
2483
+ if (sourceTokens.length === 0) return DEFAULT_LINK_REL;
2478
2484
  const tokens = new Set(sourceTokens.filter((token) => token.toLowerCase() !== "opener"));
2479
2485
  tokens.add("noopener");
2480
2486
  tokens.add("noreferrer");
2481
2487
  return Array.from(tokens).join(" ");
2482
2488
  };
2489
+
2490
+ // src/transforms/htmlToTiptap/buildLinkMark.ts
2483
2491
  var buildLinkMark = (attrs = {}) => {
2484
2492
  const { href } = attrs;
2485
2493
  if (!href || !isValidHref(href)) return null;
2486
- const target = attrs.target ?? DEFAULT_TARGET;
2494
+ const target = attrs.target ?? DEFAULT_LINK_TARGET;
2487
2495
  return {
2488
2496
  attrs: {
2489
2497
  class: null,
2490
2498
  href,
2499
+ // `Mark` is the POST-parse shape, where `rel` is always a string. This value is PRE-parse: a
2500
+ // non-`_blank` link whose source omits `rel` deliberately stays undefined so the schema's
2501
+ // `.default()` supplies it. Hence the cast — the old implicit `rel: string` parameter was
2502
+ // making the same claim, just without saying so.
2491
2503
  rel: ensureSafeRel(attrs.rel, target),
2492
2504
  target
2493
2505
  },
@@ -2402,13 +2402,19 @@ var isSkipping = (stack = []) => stack.some(({ skip }) => skip);
2402
2402
  var makeDoc = (content, blockSpacing) => TiptapDocument.parse({ attrs: { blockSpacing }, content });
2403
2403
 
2404
2404
  // src/transforms/htmlToTiptap/isValidHref.ts
2405
- var DYNAMIC_TOKEN_REGEX2 = /^\{\{\s*[A-Za-z]+\s*\}\}$/;
2405
+ var RELATIVE_BASE = "https://x.invalid";
2406
2406
  var ALLOWED_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]);
2407
2407
  var isValidHref = (href) => {
2408
2408
  if (!href) return false;
2409
2409
  if (href === "#") return true;
2410
- if (DYNAMIC_TOKEN_REGEX2.test(href)) return true;
2411
- if (href.startsWith("/") && !href.startsWith("//")) return true;
2410
+ if (DYNAMIC_TOKEN_REGEX.test(href)) return true;
2411
+ if (href.startsWith("/")) {
2412
+ try {
2413
+ return new URL(href, RELATIVE_BASE).origin === RELATIVE_BASE;
2414
+ } catch {
2415
+ return false;
2416
+ }
2417
+ }
2412
2418
  try {
2413
2419
  const url = new URL(href);
2414
2420
  return ALLOWED_PROTOCOLS.has(url.protocol);
@@ -2417,26 +2423,32 @@ var isValidHref = (href) => {
2417
2423
  }
2418
2424
  };
2419
2425
 
2420
- // src/transforms/htmlToTiptap/buildLinkMark.ts
2421
- var DEFAULT_TARGET = "_blank";
2422
- var DEFAULT_REL = "noopener noreferrer nofollow";
2426
+ // src/transforms/htmlToTiptap/linkSafety.ts
2427
+ var DEFAULT_LINK_TARGET = "_blank";
2428
+ var DEFAULT_LINK_REL = "noopener noreferrer nofollow";
2423
2429
  var ensureSafeRel = (rel, target) => {
2424
- if (target !== "_blank") return rel;
2430
+ if (target !== DEFAULT_LINK_TARGET) return rel;
2425
2431
  const sourceTokens = (rel ?? "").split(/\s+/).filter(Boolean);
2426
- if (sourceTokens.length === 0) return DEFAULT_REL;
2432
+ if (sourceTokens.length === 0) return DEFAULT_LINK_REL;
2427
2433
  const tokens = new Set(sourceTokens.filter((token) => token.toLowerCase() !== "opener"));
2428
2434
  tokens.add("noopener");
2429
2435
  tokens.add("noreferrer");
2430
2436
  return Array.from(tokens).join(" ");
2431
2437
  };
2438
+
2439
+ // src/transforms/htmlToTiptap/buildLinkMark.ts
2432
2440
  var buildLinkMark = (attrs = {}) => {
2433
2441
  const { href } = attrs;
2434
2442
  if (!href || !isValidHref(href)) return null;
2435
- const target = attrs.target ?? DEFAULT_TARGET;
2443
+ const target = attrs.target ?? DEFAULT_LINK_TARGET;
2436
2444
  return {
2437
2445
  attrs: {
2438
2446
  class: null,
2439
2447
  href,
2448
+ // `Mark` is the POST-parse shape, where `rel` is always a string. This value is PRE-parse: a
2449
+ // non-`_blank` link whose source omits `rel` deliberately stays undefined so the schema's
2450
+ // `.default()` supplies it. Hence the cast — the old implicit `rel: string` parameter was
2451
+ // making the same claim, just without saying so.
2440
2452
  rel: ensureSafeRel(attrs.rel, target),
2441
2453
  target
2442
2454
  },
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The schema defaults for an `<a>` mark when a payload omits them. Match `TiptapText` in
3
+ * `~/schema/widgets/checkout-and-beyond/text` so anything resolving a link BEFORE (or without) a parse
4
+ * lands on the same effective values the downstream parse would have used.
5
+ */
6
+ export declare const DEFAULT_LINK_TARGET = "_blank";
7
+ export declare const DEFAULT_LINK_REL = "noopener noreferrer nofollow";
8
+ /**
9
+ * Forces `noopener noreferrer` on every `_blank` link to defend against reverse-tabnabbing
10
+ * (window.opener attacks). When the source omits `rel` entirely, fall back to the schema default
11
+ * (`noopener noreferrer nofollow`). When source `rel` is non-empty, preserve the existing tokens, drop
12
+ * `opener`, and ensure the safe pair is present.
13
+ *
14
+ * Non-`_blank` targets pass through UNCHANGED — they can't be tabnabbed. That includes passing `undefined`
15
+ * straight back: `TiptapText`'s `rel: z.string().default('noopener noreferrer nofollow')` fires on
16
+ * `undefined` only, so substituting `''` here would silently strip `nofollow` off every `_self` link at
17
+ * parse time. The absent value is the signal that the schema default should apply.
18
+ */
19
+ export declare const ensureSafeRel: (rel: string | undefined, target: string) => string | undefined;
@@ -1837,13 +1837,19 @@ var isSkipping = (stack = []) => stack.some(({ skip }) => skip);
1837
1837
  var makeDoc = (content, blockSpacing) => TiptapDocument.parse({ attrs: { blockSpacing }, content });
1838
1838
 
1839
1839
  // src/transforms/htmlToTiptap/isValidHref.ts
1840
- var DYNAMIC_TOKEN_REGEX2 = /^\{\{\s*[A-Za-z]+\s*\}\}$/;
1840
+ var RELATIVE_BASE = "https://x.invalid";
1841
1841
  var ALLOWED_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]);
1842
1842
  var isValidHref = (href) => {
1843
1843
  if (!href) return false;
1844
1844
  if (href === "#") return true;
1845
- if (DYNAMIC_TOKEN_REGEX2.test(href)) return true;
1846
- if (href.startsWith("/") && !href.startsWith("//")) return true;
1845
+ if (DYNAMIC_TOKEN_REGEX.test(href)) return true;
1846
+ if (href.startsWith("/")) {
1847
+ try {
1848
+ return new URL(href, RELATIVE_BASE).origin === RELATIVE_BASE;
1849
+ } catch {
1850
+ return false;
1851
+ }
1852
+ }
1847
1853
  try {
1848
1854
  const url = new URL(href);
1849
1855
  return ALLOWED_PROTOCOLS.has(url.protocol);
@@ -1852,26 +1858,32 @@ var isValidHref = (href) => {
1852
1858
  }
1853
1859
  };
1854
1860
 
1855
- // src/transforms/htmlToTiptap/buildLinkMark.ts
1856
- var DEFAULT_TARGET = "_blank";
1857
- var DEFAULT_REL = "noopener noreferrer nofollow";
1861
+ // src/transforms/htmlToTiptap/linkSafety.ts
1862
+ var DEFAULT_LINK_TARGET = "_blank";
1863
+ var DEFAULT_LINK_REL = "noopener noreferrer nofollow";
1858
1864
  var ensureSafeRel = (rel, target) => {
1859
- if (target !== "_blank") return rel;
1865
+ if (target !== DEFAULT_LINK_TARGET) return rel;
1860
1866
  const sourceTokens = (rel ?? "").split(/\s+/).filter(Boolean);
1861
- if (sourceTokens.length === 0) return DEFAULT_REL;
1867
+ if (sourceTokens.length === 0) return DEFAULT_LINK_REL;
1862
1868
  const tokens = new Set(sourceTokens.filter((token) => token.toLowerCase() !== "opener"));
1863
1869
  tokens.add("noopener");
1864
1870
  tokens.add("noreferrer");
1865
1871
  return Array.from(tokens).join(" ");
1866
1872
  };
1873
+
1874
+ // src/transforms/htmlToTiptap/buildLinkMark.ts
1867
1875
  var buildLinkMark = (attrs = {}) => {
1868
1876
  const { href } = attrs;
1869
1877
  if (!href || !isValidHref(href)) return null;
1870
- const target = attrs.target ?? DEFAULT_TARGET;
1878
+ const target = attrs.target ?? DEFAULT_LINK_TARGET;
1871
1879
  return {
1872
1880
  attrs: {
1873
1881
  class: null,
1874
1882
  href,
1883
+ // `Mark` is the POST-parse shape, where `rel` is always a string. This value is PRE-parse: a
1884
+ // non-`_blank` link whose source omits `rel` deliberately stays undefined so the schema's
1885
+ // `.default()` supplies it. Hence the cast — the old implicit `rel: string` parameter was
1886
+ // making the same claim, just without saying so.
1875
1887
  rel: ensureSafeRel(attrs.rel, target),
1876
1888
  target
1877
1889
  },
@@ -1778,13 +1778,19 @@ var isSkipping = (stack = []) => stack.some(({ skip }) => skip);
1778
1778
  var makeDoc = (content, blockSpacing) => TiptapDocument.parse({ attrs: { blockSpacing }, content });
1779
1779
 
1780
1780
  // src/transforms/htmlToTiptap/isValidHref.ts
1781
- var DYNAMIC_TOKEN_REGEX2 = /^\{\{\s*[A-Za-z]+\s*\}\}$/;
1781
+ var RELATIVE_BASE = "https://x.invalid";
1782
1782
  var ALLOWED_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]);
1783
1783
  var isValidHref = (href) => {
1784
1784
  if (!href) return false;
1785
1785
  if (href === "#") return true;
1786
- if (DYNAMIC_TOKEN_REGEX2.test(href)) return true;
1787
- if (href.startsWith("/") && !href.startsWith("//")) return true;
1786
+ if (DYNAMIC_TOKEN_REGEX.test(href)) return true;
1787
+ if (href.startsWith("/")) {
1788
+ try {
1789
+ return new URL(href, RELATIVE_BASE).origin === RELATIVE_BASE;
1790
+ } catch {
1791
+ return false;
1792
+ }
1793
+ }
1788
1794
  try {
1789
1795
  const url = new URL(href);
1790
1796
  return ALLOWED_PROTOCOLS.has(url.protocol);
@@ -1793,26 +1799,32 @@ var isValidHref = (href) => {
1793
1799
  }
1794
1800
  };
1795
1801
 
1796
- // src/transforms/htmlToTiptap/buildLinkMark.ts
1797
- var DEFAULT_TARGET = "_blank";
1798
- var DEFAULT_REL = "noopener noreferrer nofollow";
1802
+ // src/transforms/htmlToTiptap/linkSafety.ts
1803
+ var DEFAULT_LINK_TARGET = "_blank";
1804
+ var DEFAULT_LINK_REL = "noopener noreferrer nofollow";
1799
1805
  var ensureSafeRel = (rel, target) => {
1800
- if (target !== "_blank") return rel;
1806
+ if (target !== DEFAULT_LINK_TARGET) return rel;
1801
1807
  const sourceTokens = (rel ?? "").split(/\s+/).filter(Boolean);
1802
- if (sourceTokens.length === 0) return DEFAULT_REL;
1808
+ if (sourceTokens.length === 0) return DEFAULT_LINK_REL;
1803
1809
  const tokens = new Set(sourceTokens.filter((token) => token.toLowerCase() !== "opener"));
1804
1810
  tokens.add("noopener");
1805
1811
  tokens.add("noreferrer");
1806
1812
  return Array.from(tokens).join(" ");
1807
1813
  };
1814
+
1815
+ // src/transforms/htmlToTiptap/buildLinkMark.ts
1808
1816
  var buildLinkMark = (attrs = {}) => {
1809
1817
  const { href } = attrs;
1810
1818
  if (!href || !isValidHref(href)) return null;
1811
- const target = attrs.target ?? DEFAULT_TARGET;
1819
+ const target = attrs.target ?? DEFAULT_LINK_TARGET;
1812
1820
  return {
1813
1821
  attrs: {
1814
1822
  class: null,
1815
1823
  href,
1824
+ // `Mark` is the POST-parse shape, where `rel` is always a string. This value is PRE-parse: a
1825
+ // non-`_blank` link whose source omits `rel` deliberately stays undefined so the schema's
1826
+ // `.default()` supplies it. Hence the cast — the old implicit `rel: string` parameter was
1827
+ // making the same claim, just without saying so.
1816
1828
  rel: ensureSafeRel(attrs.rel, target),
1817
1829
  target
1818
1830
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rebuy/rebuy",
3
- "version": "3.23.0",
3
+ "version": "3.24.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.",
@@ -12,6 +12,11 @@
12
12
  "import": "./dist/index.mjs",
13
13
  "require": "./dist/index.cjs"
14
14
  },
15
+ "./cab": {
16
+ "types": "./dist/cab/index.d.ts",
17
+ "import": "./dist/cab/index.mjs",
18
+ "require": "./dist/cab/index.cjs"
19
+ },
15
20
  "./client": {
16
21
  "types": "./dist/client.d.ts",
17
22
  "import": "./dist/client.mjs",
@@ -79,6 +84,9 @@
79
84
  "types": "./dist/index.d.ts",
80
85
  "typesVersions": {
81
86
  "*": {
87
+ "cab": [
88
+ "./dist/cab/index.d.ts"
89
+ ],
82
90
  "client": [
83
91
  "./dist/client.d.ts"
84
92
  ],