@appfunnel-dev/sdk 2.0.0-canary.1 → 2.0.0-canary.11
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 +1 -1
- package/dist/capabilities-Dq22RCr_.d.cts +180 -0
- package/dist/capabilities-Dq22RCr_.d.ts +180 -0
- package/dist/checkout-ClaO5IYV.d.ts +333 -0
- package/dist/checkout-DBp4bCpC.d.cts +333 -0
- package/dist/chunk-7JLOF6CJ.cjs +172 -0
- package/dist/chunk-7JLOF6CJ.cjs.map +1 -0
- package/dist/chunk-EMMSS5I5.cjs +37 -0
- package/dist/chunk-EMMSS5I5.cjs.map +1 -0
- package/dist/chunk-G3PMV62Z.js +33 -0
- package/dist/chunk-G3PMV62Z.js.map +1 -0
- package/dist/chunk-JAO6AA4R.js +99 -0
- package/dist/chunk-JAO6AA4R.js.map +1 -0
- package/dist/chunk-OXQBEKZ5.js +157 -0
- package/dist/chunk-OXQBEKZ5.js.map +1 -0
- package/dist/chunk-RKPRMTSU.cjs +517 -0
- package/dist/chunk-RKPRMTSU.cjs.map +1 -0
- package/dist/chunk-SXENKZ4U.js +486 -0
- package/dist/chunk-SXENKZ4U.js.map +1 -0
- package/dist/chunk-VV7TFC64.cjs +106 -0
- package/dist/chunk-VV7TFC64.cjs.map +1 -0
- package/dist/chunk-WYUDL4FI.cjs +8 -0
- package/dist/chunk-WYUDL4FI.cjs.map +1 -0
- package/dist/chunk-ZZJG4EYL.js +6 -0
- package/dist/chunk-ZZJG4EYL.js.map +1 -0
- package/dist/driver-paddle.cjs +817 -0
- package/dist/driver-paddle.cjs.map +1 -0
- package/dist/driver-paddle.d.cts +10 -0
- package/dist/driver-paddle.d.ts +10 -0
- package/dist/driver-paddle.js +814 -0
- package/dist/driver-paddle.js.map +1 -0
- package/dist/driver-stripe.cjs +2312 -0
- package/dist/driver-stripe.cjs.map +1 -0
- package/dist/driver-stripe.d.cts +24 -0
- package/dist/driver-stripe.d.ts +24 -0
- package/dist/driver-stripe.js +2305 -0
- package/dist/driver-stripe.js.map +1 -0
- package/dist/index.cjs +2677 -846
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +358 -1005
- package/dist/index.d.ts +358 -1005
- package/dist/index.js +2395 -698
- package/dist/index.js.map +1 -1
- package/dist/manifest-C2mDXWNU.d.cts +996 -0
- package/dist/manifest-C2mDXWNU.d.ts +996 -0
- package/dist/manifest-entry.cjs +408 -0
- package/dist/manifest-entry.cjs.map +1 -0
- package/dist/manifest-entry.d.cts +192 -0
- package/dist/manifest-entry.d.ts +192 -0
- package/dist/manifest-entry.js +270 -0
- package/dist/manifest-entry.js.map +1 -0
- package/dist/protocol.cjs +13 -0
- package/dist/protocol.cjs.map +1 -0
- package/dist/protocol.d.cts +182 -0
- package/dist/protocol.d.ts +182 -0
- package/dist/protocol.js +4 -0
- package/dist/protocol.js.map +1 -0
- package/package.json +49 -4
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
// src/i18n/locale.ts
|
|
2
|
+
var RTL_LANGS = /* @__PURE__ */ new Set(["ar", "he", "fa", "ur", "ps", "sd", "dv", "yi"]);
|
|
3
|
+
function isRtl(locale) {
|
|
4
|
+
return RTL_LANGS.has(locale.split("-")[0]);
|
|
5
|
+
}
|
|
6
|
+
var base = (l) => l?.split("-")[0];
|
|
7
|
+
function resolveLocale(config, detected, override) {
|
|
8
|
+
const supported = config?.supported ?? (config?.default ? [config.default] : ["en"]);
|
|
9
|
+
const def = config?.default ?? supported[0] ?? "en";
|
|
10
|
+
const pick = (l) => {
|
|
11
|
+
if (!l) return void 0;
|
|
12
|
+
if (supported.includes(l)) return l;
|
|
13
|
+
return supported.find((s) => base(s) === base(l));
|
|
14
|
+
};
|
|
15
|
+
return pick(override) ?? pick(detected) ?? def;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/commerce/money.ts
|
|
19
|
+
var PERIOD_DAYS = {
|
|
20
|
+
day: 1,
|
|
21
|
+
week: 7,
|
|
22
|
+
month: 365 / 12,
|
|
23
|
+
year: 365,
|
|
24
|
+
one_time: 0
|
|
25
|
+
};
|
|
26
|
+
function periodDays(interval, count) {
|
|
27
|
+
return PERIOD_DAYS[interval] * count;
|
|
28
|
+
}
|
|
29
|
+
function intervalLabel(interval, count) {
|
|
30
|
+
if (interval === "one_time") return { period: "one-time", periodly: "one-time" };
|
|
31
|
+
if (interval === "month" && count === 3) return { period: "quarter", periodly: "quarterly" };
|
|
32
|
+
if (interval === "month" && count === 6) return { period: "6 months", periodly: "semiannually" };
|
|
33
|
+
if (interval === "week" && count === 2) return { period: "2 weeks", periodly: "biweekly" };
|
|
34
|
+
if (count === 1) {
|
|
35
|
+
const periodly = { day: "daily", week: "weekly", month: "monthly", year: "yearly" }[interval];
|
|
36
|
+
return { period: interval, periodly };
|
|
37
|
+
}
|
|
38
|
+
return { period: `${count} ${interval}s`, periodly: `every ${count} ${interval}s` };
|
|
39
|
+
}
|
|
40
|
+
function resolveOffering(input) {
|
|
41
|
+
const interval = input.interval ?? "one_time";
|
|
42
|
+
const count = input.intervalCount ?? 1;
|
|
43
|
+
const days = periodDays(interval, count);
|
|
44
|
+
const per = (targetDays) => days > 0 ? Math.round(input.amount * targetDays / days) : input.amount;
|
|
45
|
+
const label = intervalLabel(interval, count);
|
|
46
|
+
const trialDays = input.trialDays ?? 0;
|
|
47
|
+
const trialMinor = input.trialAmount ?? 0;
|
|
48
|
+
return {
|
|
49
|
+
id: input.id,
|
|
50
|
+
catalogKey: input.catalogKey ?? input.id,
|
|
51
|
+
name: input.name ?? input.id,
|
|
52
|
+
displayName: input.displayName ?? input.name ?? input.id,
|
|
53
|
+
provider: input.provider ?? "stripe",
|
|
54
|
+
currency: input.currency,
|
|
55
|
+
priceMinor: input.amount,
|
|
56
|
+
perDayMinor: per(1),
|
|
57
|
+
perWeekMinor: per(7),
|
|
58
|
+
perMonthMinor: per(365 / 12),
|
|
59
|
+
perYearMinor: per(365),
|
|
60
|
+
period: label.period,
|
|
61
|
+
periodly: label.periodly,
|
|
62
|
+
hasTrial: trialDays > 0 || trialMinor > 0,
|
|
63
|
+
trialDays,
|
|
64
|
+
trialMinor
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function buildCatalog(inputs) {
|
|
68
|
+
return new Map(inputs.map((i) => [i.id, resolveOffering(i)]));
|
|
69
|
+
}
|
|
70
|
+
var EXPONENT_CACHE = /* @__PURE__ */ new Map();
|
|
71
|
+
function currencyExponent(currency) {
|
|
72
|
+
const code = currency.toUpperCase();
|
|
73
|
+
const hit = EXPONENT_CACHE.get(code);
|
|
74
|
+
if (hit !== void 0) return hit;
|
|
75
|
+
let exp = 2;
|
|
76
|
+
try {
|
|
77
|
+
exp = new Intl.NumberFormat("en", { style: "currency", currency: code }).resolvedOptions().maximumFractionDigits ?? 2;
|
|
78
|
+
} catch {
|
|
79
|
+
exp = 2;
|
|
80
|
+
}
|
|
81
|
+
EXPONENT_CACHE.set(code, exp);
|
|
82
|
+
return exp;
|
|
83
|
+
}
|
|
84
|
+
function formatMoney(minor, currency, locale) {
|
|
85
|
+
const code = currency.toUpperCase();
|
|
86
|
+
const exp = currencyExponent(code);
|
|
87
|
+
const amount = minor / 10 ** exp;
|
|
88
|
+
let formatted;
|
|
89
|
+
try {
|
|
90
|
+
formatted = new Intl.NumberFormat(locale, { style: "currency", currency: code }).format(amount);
|
|
91
|
+
} catch {
|
|
92
|
+
formatted = `${code} ${amount.toFixed(exp)}`;
|
|
93
|
+
}
|
|
94
|
+
return { amount, minor, currency: code, formatted };
|
|
95
|
+
}
|
|
96
|
+
function formatOffering(r, locale) {
|
|
97
|
+
const m = (minor) => formatMoney(minor, r.currency, locale);
|
|
98
|
+
return {
|
|
99
|
+
id: r.id,
|
|
100
|
+
catalogKey: r.catalogKey,
|
|
101
|
+
name: r.name,
|
|
102
|
+
displayName: r.displayName,
|
|
103
|
+
provider: r.provider,
|
|
104
|
+
price: m(r.priceMinor),
|
|
105
|
+
period: r.period,
|
|
106
|
+
periodly: r.periodly,
|
|
107
|
+
perDay: m(r.perDayMinor),
|
|
108
|
+
perWeek: m(r.perWeekMinor),
|
|
109
|
+
perMonth: m(r.perMonthMinor),
|
|
110
|
+
perYear: m(r.perYearMinor),
|
|
111
|
+
hasTrial: r.hasTrial,
|
|
112
|
+
trialDays: r.trialDays,
|
|
113
|
+
trialPrice: r.hasTrial ? m(r.trialMinor) : null
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/flow/spine.ts
|
|
118
|
+
function readField(s, field) {
|
|
119
|
+
let cur = s;
|
|
120
|
+
for (const part of field.split(".")) {
|
|
121
|
+
if (cur == null) return void 0;
|
|
122
|
+
cur = cur[part];
|
|
123
|
+
}
|
|
124
|
+
return cur;
|
|
125
|
+
}
|
|
126
|
+
function evaluateCondition(cond, s) {
|
|
127
|
+
const val = readField(s, cond.field);
|
|
128
|
+
const { op, value } = cond;
|
|
129
|
+
switch (op) {
|
|
130
|
+
case "eq":
|
|
131
|
+
return val === value;
|
|
132
|
+
case "neq":
|
|
133
|
+
return val !== value;
|
|
134
|
+
case "in":
|
|
135
|
+
return Array.isArray(value) && value.includes(val);
|
|
136
|
+
case "gt":
|
|
137
|
+
return typeof val === "number" && typeof value === "number" && val > value;
|
|
138
|
+
case "gte":
|
|
139
|
+
return typeof val === "number" && typeof value === "number" && val >= value;
|
|
140
|
+
case "lt":
|
|
141
|
+
return typeof val === "number" && typeof value === "number" && val < value;
|
|
142
|
+
case "lte":
|
|
143
|
+
return typeof val === "number" && typeof value === "number" && val <= value;
|
|
144
|
+
case "contains":
|
|
145
|
+
if (typeof val === "string") return val.includes(String(value));
|
|
146
|
+
if (Array.isArray(val))
|
|
147
|
+
return val.includes(value);
|
|
148
|
+
return false;
|
|
149
|
+
case "exists":
|
|
150
|
+
return val !== void 0 && val !== null && val !== "";
|
|
151
|
+
case "empty":
|
|
152
|
+
return val === void 0 || val === null || val === "" || Array.isArray(val) && val.length === 0;
|
|
153
|
+
default:
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function evaluateGate(gate, s) {
|
|
158
|
+
return typeof gate === "function" ? gate(s) : evaluateCondition(gate, s);
|
|
159
|
+
}
|
|
160
|
+
function resolveRoute(routes, s) {
|
|
161
|
+
if (!routes) return void 0;
|
|
162
|
+
for (const route of routes) {
|
|
163
|
+
if (!route.when || evaluateGate(route.when, s)) return route.to;
|
|
164
|
+
}
|
|
165
|
+
return void 0;
|
|
166
|
+
}
|
|
167
|
+
function pageMeta(meta) {
|
|
168
|
+
return meta;
|
|
169
|
+
}
|
|
170
|
+
var TYPE_GUARDS = {
|
|
171
|
+
paywall: { field: "user.email", op: "exists" },
|
|
172
|
+
upsell: { field: "user.email", op: "exists" }
|
|
173
|
+
};
|
|
174
|
+
function entryGuard(meta) {
|
|
175
|
+
return meta?.guard ?? (meta?.type ? TYPE_GUARDS[meta.type] : void 0);
|
|
176
|
+
}
|
|
177
|
+
function definePage(component) {
|
|
178
|
+
return component;
|
|
179
|
+
}
|
|
180
|
+
function nextPage(pages, currentKey, s) {
|
|
181
|
+
const idx = pages.findIndex((p) => p.key === currentKey);
|
|
182
|
+
if (idx === -1) return void 0;
|
|
183
|
+
const routed = resolveRoute(pages[idx].meta?.next, s);
|
|
184
|
+
if (routed) return routed;
|
|
185
|
+
return pages[idx + 1]?.key;
|
|
186
|
+
}
|
|
187
|
+
function outgoingKeys(pages, currentKey) {
|
|
188
|
+
const idx = pages.findIndex((p) => p.key === currentKey);
|
|
189
|
+
if (idx === -1) return [];
|
|
190
|
+
const out = /* @__PURE__ */ new Set();
|
|
191
|
+
for (const route of pages[idx].meta?.next ?? []) out.add(route.to);
|
|
192
|
+
const linear = pages[idx + 1]?.key;
|
|
193
|
+
if (linear) out.add(linear);
|
|
194
|
+
return [...out];
|
|
195
|
+
}
|
|
196
|
+
function expectedPathLength(pages, startKey, s) {
|
|
197
|
+
const seen = /* @__PURE__ */ new Set();
|
|
198
|
+
let key = startKey;
|
|
199
|
+
let count = 0;
|
|
200
|
+
while (key && !seen.has(key)) {
|
|
201
|
+
seen.add(key);
|
|
202
|
+
count++;
|
|
203
|
+
if (pages.find((p) => p.key === key)?.meta?.type === "finish") break;
|
|
204
|
+
key = nextPage(pages, key, s);
|
|
205
|
+
}
|
|
206
|
+
return count;
|
|
207
|
+
}
|
|
208
|
+
function groupProgress(pages, startKey, currentKey, s) {
|
|
209
|
+
const path = [];
|
|
210
|
+
const seen = /* @__PURE__ */ new Set();
|
|
211
|
+
let key = startKey;
|
|
212
|
+
while (key && !seen.has(key)) {
|
|
213
|
+
seen.add(key);
|
|
214
|
+
const page = pages.find((p) => p.key === key);
|
|
215
|
+
path.push(page?.meta?.group);
|
|
216
|
+
if (page?.meta?.type === "finish") break;
|
|
217
|
+
key = nextPage(pages, key, s);
|
|
218
|
+
}
|
|
219
|
+
const found = [...seen].indexOf(currentKey);
|
|
220
|
+
const cursor = found === -1 ? 0 : found;
|
|
221
|
+
const order = [];
|
|
222
|
+
const tally = /* @__PURE__ */ new Map();
|
|
223
|
+
path.forEach((group, i) => {
|
|
224
|
+
if (!group) return;
|
|
225
|
+
let g = tally.get(group);
|
|
226
|
+
if (!g) {
|
|
227
|
+
g = { pageCount: 0, completedCount: 0 };
|
|
228
|
+
tally.set(group, g);
|
|
229
|
+
order.push(group);
|
|
230
|
+
}
|
|
231
|
+
g.pageCount++;
|
|
232
|
+
if (i < cursor) g.completedCount++;
|
|
233
|
+
});
|
|
234
|
+
const currentGroup = path[cursor];
|
|
235
|
+
const groups = order.map((key2) => ({
|
|
236
|
+
key: key2,
|
|
237
|
+
pageCount: tally.get(key2).pageCount,
|
|
238
|
+
completedCount: tally.get(key2).completedCount,
|
|
239
|
+
active: key2 === currentGroup
|
|
240
|
+
}));
|
|
241
|
+
const activeIndex = currentGroup ? order.indexOf(currentGroup) : -1;
|
|
242
|
+
const active = activeIndex >= 0 ? groups[activeIndex] : void 0;
|
|
243
|
+
const activeFraction = active && active.pageCount > 0 ? (active.completedCount + 1) / active.pageCount : 0;
|
|
244
|
+
return { groups, activeIndex, activeFraction };
|
|
245
|
+
}
|
|
246
|
+
function defineFunnel(def) {
|
|
247
|
+
return def;
|
|
248
|
+
}
|
|
249
|
+
function normalizeOfferings(offerings) {
|
|
250
|
+
if (!offerings) return [];
|
|
251
|
+
if (Array.isArray(offerings))
|
|
252
|
+
return offerings.map((k) => ({ slot: k, catalogKey: k }));
|
|
253
|
+
return Object.entries(offerings).map(([slot, catalogKey]) => ({
|
|
254
|
+
slot,
|
|
255
|
+
catalogKey: catalogKey ?? null
|
|
256
|
+
}));
|
|
257
|
+
}
|
|
258
|
+
function funnelCatalogKeys(offerings) {
|
|
259
|
+
return [
|
|
260
|
+
...new Set(
|
|
261
|
+
normalizeOfferings(offerings).map((s) => s.catalogKey).filter((k) => !!k)
|
|
262
|
+
)
|
|
263
|
+
];
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// src/flow/experiments.ts
|
|
267
|
+
function fnv1a(input) {
|
|
268
|
+
let h = 2166136261;
|
|
269
|
+
for (let i = 0; i < input.length; i++) {
|
|
270
|
+
h ^= input.charCodeAt(i);
|
|
271
|
+
h = h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24)) >>> 0;
|
|
272
|
+
}
|
|
273
|
+
return h >>> 0;
|
|
274
|
+
}
|
|
275
|
+
function hashToUnit(seed) {
|
|
276
|
+
return fnv1a(seed) / 4294967296;
|
|
277
|
+
}
|
|
278
|
+
function pickByWeight(weights, u) {
|
|
279
|
+
const entries = Object.entries(weights).filter(([, w]) => w > 0);
|
|
280
|
+
if (entries.length === 0) return Object.keys(weights)[0] ?? "";
|
|
281
|
+
const total = entries.reduce((sum, [, w]) => sum + w, 0);
|
|
282
|
+
const target = u * total;
|
|
283
|
+
let acc = 0;
|
|
284
|
+
for (const [key, w] of entries) {
|
|
285
|
+
acc += w;
|
|
286
|
+
if (target < acc) return key;
|
|
287
|
+
}
|
|
288
|
+
return entries[entries.length - 1][0];
|
|
289
|
+
}
|
|
290
|
+
function assignVariant(experimentId, seed, weights) {
|
|
291
|
+
return pickByWeight(weights, hashToUnit(`${experimentId}:${seed}`));
|
|
292
|
+
}
|
|
293
|
+
function bucketingSeed(context) {
|
|
294
|
+
return context.identity.visitorId ?? context.identity.customerId ?? null;
|
|
295
|
+
}
|
|
296
|
+
function parseSlotKey(key) {
|
|
297
|
+
const at = key.indexOf("@");
|
|
298
|
+
return at === -1 ? { slot: key } : { slot: key.slice(0, at), variant: key.slice(at + 1) };
|
|
299
|
+
}
|
|
300
|
+
function isVariantKey(key) {
|
|
301
|
+
return parseSlotKey(key).variant !== void 0;
|
|
302
|
+
}
|
|
303
|
+
function weightsOf(variants) {
|
|
304
|
+
const out = {};
|
|
305
|
+
for (const [label, v] of Object.entries(variants)) out[label] = v.weight;
|
|
306
|
+
return out;
|
|
307
|
+
}
|
|
308
|
+
function resolveExperiments(pages, experiments, seed) {
|
|
309
|
+
const assignments = {};
|
|
310
|
+
const render = {};
|
|
311
|
+
const experimentOf = {};
|
|
312
|
+
const versionOf = {};
|
|
313
|
+
if (seed) {
|
|
314
|
+
for (const exp of experiments) {
|
|
315
|
+
const label = assignVariant(exp.id, seed, weightsOf(exp.variants));
|
|
316
|
+
const arm = exp.variants[label];
|
|
317
|
+
if (!arm) continue;
|
|
318
|
+
assignments[exp.id] = label;
|
|
319
|
+
experimentOf[exp.slot] = exp.id;
|
|
320
|
+
if (exp.version !== void 0) versionOf[exp.id] = exp.version;
|
|
321
|
+
if (arm.page !== exp.slot) render[exp.slot] = arm.page;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
const slotOf = {};
|
|
325
|
+
const activeKeys = [];
|
|
326
|
+
for (const p of pages) {
|
|
327
|
+
const { slot, variant } = parseSlotKey(p.key);
|
|
328
|
+
if (variant !== void 0) slotOf[p.key] = slot;
|
|
329
|
+
else activeKeys.push(p.key);
|
|
330
|
+
}
|
|
331
|
+
return { assignments, activeKeys, render, slotOf, experimentOf, versionOf };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// src/manifest/manifest.ts
|
|
335
|
+
function isVariantNode(page) {
|
|
336
|
+
return isVariantKey(page.key);
|
|
337
|
+
}
|
|
338
|
+
function serializeGate(when) {
|
|
339
|
+
if (!when) return void 0;
|
|
340
|
+
if (typeof when === "function") return { kind: "predicate" };
|
|
341
|
+
return { kind: "declarative", condition: when };
|
|
342
|
+
}
|
|
343
|
+
function compileManifest(input) {
|
|
344
|
+
const { funnel, pages } = input;
|
|
345
|
+
const keys = new Set(pages.map((p) => p.key));
|
|
346
|
+
const manifestPages = pages.map((p, index) => ({
|
|
347
|
+
id: p.key,
|
|
348
|
+
key: p.key,
|
|
349
|
+
type: p.meta?.type ?? "default",
|
|
350
|
+
slug: p.meta?.slug ?? p.key,
|
|
351
|
+
index,
|
|
352
|
+
// Progress-bar group membership, carried additively (undefined when the page declares no group).
|
|
353
|
+
group: p.meta?.group,
|
|
354
|
+
variantOf: isVariantKey(p.key) ? parseSlotKey(p.key).slot : void 0,
|
|
355
|
+
// The effective entry guard (explicit meta.guard, else the page-type default), serialized so
|
|
356
|
+
// the renderer can evaluate a DECLARATIVE guard server-side (Option B, FIX 1). Predicate guards
|
|
357
|
+
// serialize to an opaque marker → the renderer falls back to live SSR for that deep-link.
|
|
358
|
+
entryGuard: serializeGate(entryGuard(p.meta)),
|
|
359
|
+
// Prerender opt-out: `dynamic: true` OR `prerender: false` (a page whose STRUCTURE depends on
|
|
360
|
+
// sessionValues — Option B, FIX 2). Undefined stays undefined (prerendered, the default).
|
|
361
|
+
dynamic: p.meta?.dynamic === true || p.meta?.prerender === false ? true : void 0
|
|
362
|
+
}));
|
|
363
|
+
const edges = [];
|
|
364
|
+
const badTargets = [];
|
|
365
|
+
const nextAnchor = (from) => {
|
|
366
|
+
for (let j = from + 1; j < pages.length; j++) {
|
|
367
|
+
if (!isVariantNode(pages[j])) return pages[j];
|
|
368
|
+
}
|
|
369
|
+
return void 0;
|
|
370
|
+
};
|
|
371
|
+
pages.forEach((p, i) => {
|
|
372
|
+
if (isVariantNode(p)) {
|
|
373
|
+
for (const route of p.meta?.next ?? []) {
|
|
374
|
+
if (!keys.has(route.to)) {
|
|
375
|
+
badTargets.push({ from: p.key, to: route.to });
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
edges.push({ from: p.key, to: route.to, kind: "branch", variant: true, condition: serializeGate(route.when) });
|
|
379
|
+
}
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
const routes = p.meta?.next ?? [];
|
|
383
|
+
let hasUnconditional = false;
|
|
384
|
+
for (const route of routes) {
|
|
385
|
+
if (!keys.has(route.to)) {
|
|
386
|
+
badTargets.push({ from: p.key, to: route.to });
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
edges.push({ from: p.key, to: route.to, kind: "branch", condition: serializeGate(route.when) });
|
|
390
|
+
if (!route.when) hasUnconditional = true;
|
|
391
|
+
}
|
|
392
|
+
const next = nextAnchor(i);
|
|
393
|
+
if (!hasUnconditional && next && p.meta?.type !== "finish") {
|
|
394
|
+
edges.push({ from: p.key, to: next.key, kind: "linear" });
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
const startPage = pages.find((p) => !isVariantNode(p));
|
|
398
|
+
const start = startPage?.key ?? null;
|
|
399
|
+
const variantKeys = new Set(pages.filter(isVariantNode).map((p) => p.key));
|
|
400
|
+
const offeringSlots = normalizeOfferings(funnel.offerings);
|
|
401
|
+
const validation = validate(manifestPages, edges, badTargets, start, variantKeys, offeringSlots, input.offeringRefs);
|
|
402
|
+
return {
|
|
403
|
+
id: funnel.id,
|
|
404
|
+
pages: manifestPages,
|
|
405
|
+
flow: { start, nodes: manifestPages.map((p) => p.id), edges },
|
|
406
|
+
offerings: funnelCatalogKeys(funnel.offerings),
|
|
407
|
+
offeringSlots,
|
|
408
|
+
offeringRefs: input.offeringRefs,
|
|
409
|
+
// Carry the funnel.ts `checkout:` bundle + the scanned checkout-surface usage for the publish
|
|
410
|
+
// capability gate. Both are provider-agnostic here (the SDK can't resolve offering→provider);
|
|
411
|
+
// the API gate combines them with the DB-resolved provider to run the (provider × surface) matrix.
|
|
412
|
+
checkout: funnel.checkout,
|
|
413
|
+
checkoutRefs: input.checkoutRefs,
|
|
414
|
+
locales: funnel.locales,
|
|
415
|
+
validation
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
function validate(pages, edges, badTargets, start, variantKeys, slots, offeringRefs) {
|
|
419
|
+
const outgoing = /* @__PURE__ */ new Map();
|
|
420
|
+
for (const e of edges) outgoing.set(e.from, (outgoing.get(e.from) ?? 0) + 1);
|
|
421
|
+
const deadEnds = pages.filter((p) => p.type !== "finish" && !variantKeys.has(p.id) && !(outgoing.get(p.id) > 0)).map((p) => p.id);
|
|
422
|
+
const reachable = /* @__PURE__ */ new Set();
|
|
423
|
+
if (start) {
|
|
424
|
+
const adj = /* @__PURE__ */ new Map();
|
|
425
|
+
for (const e of edges) {
|
|
426
|
+
const list = adj.get(e.from);
|
|
427
|
+
if (list) list.push(e.to);
|
|
428
|
+
else adj.set(e.from, [e.to]);
|
|
429
|
+
}
|
|
430
|
+
for (const p of pages) {
|
|
431
|
+
if (p.variantOf === void 0) continue;
|
|
432
|
+
const list = adj.get(p.variantOf);
|
|
433
|
+
if (list) list.push(p.id);
|
|
434
|
+
else adj.set(p.variantOf, [p.id]);
|
|
435
|
+
}
|
|
436
|
+
const stack = [start];
|
|
437
|
+
while (stack.length) {
|
|
438
|
+
const id = stack.pop();
|
|
439
|
+
if (reachable.has(id)) continue;
|
|
440
|
+
reachable.add(id);
|
|
441
|
+
for (const to of adj.get(id) ?? []) stack.push(to);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
const unreachable = pages.filter((p) => !variantKeys.has(p.id) && !reachable.has(p.id)).map((p) => p.id);
|
|
445
|
+
const missingEmailCapture = !pages.some((p) => p.type === "email-capture");
|
|
446
|
+
const seen = /* @__PURE__ */ new Set();
|
|
447
|
+
const duplicateKeys = [];
|
|
448
|
+
for (const p of pages) {
|
|
449
|
+
if (seen.has(p.key) && !duplicateKeys.includes(p.key)) duplicateKeys.push(p.key);
|
|
450
|
+
seen.add(p.key);
|
|
451
|
+
}
|
|
452
|
+
const bySlug = /* @__PURE__ */ new Map();
|
|
453
|
+
for (const p of pages) {
|
|
454
|
+
if (variantKeys.has(p.id)) continue;
|
|
455
|
+
const list = bySlug.get(p.slug);
|
|
456
|
+
if (list) list.push(p.id);
|
|
457
|
+
else bySlug.set(p.slug, [p.id]);
|
|
458
|
+
}
|
|
459
|
+
const slugCollisions = [...bySlug.entries()].filter(([, ids]) => ids.length > 1).map(([slug, ids]) => ({ slug, pages: ids }));
|
|
460
|
+
const flowKeys = new Set(pages.filter((p) => !variantKeys.has(p.id)).map((p) => p.key));
|
|
461
|
+
const orphanVariants = pages.filter((p) => p.variantOf !== void 0 && !flowKeys.has(p.variantOf)).map((p) => p.id);
|
|
462
|
+
const noReachableFinish = start !== null && !pages.some((p) => p.type === "finish" && reachable.has(p.id));
|
|
463
|
+
const declaredSlots = new Set(slots.map((s) => s.slot));
|
|
464
|
+
const assignedSlots = new Set(slots.filter((s) => s.catalogKey).map((s) => s.slot));
|
|
465
|
+
const refs = offeringRefs ?? [];
|
|
466
|
+
const unknownOfferingRefs = [...new Set(refs.filter((r) => !declaredSlots.has(r)))];
|
|
467
|
+
const unassignedSlots = [...new Set(refs.filter((r) => declaredSlots.has(r) && !assignedSlots.has(r)))];
|
|
468
|
+
return {
|
|
469
|
+
// Original hard errors AND the structural ones; slug collisions stay a warning.
|
|
470
|
+
ok: deadEnds.length === 0 && unreachable.length === 0 && badTargets.length === 0 && !missingEmailCapture && duplicateKeys.length === 0 && orphanVariants.length === 0 && !noReachableFinish && unknownOfferingRefs.length === 0 && unassignedSlots.length === 0,
|
|
471
|
+
deadEnds,
|
|
472
|
+
unreachable,
|
|
473
|
+
badTargets,
|
|
474
|
+
missingEmailCapture,
|
|
475
|
+
duplicateKeys,
|
|
476
|
+
slugCollisions,
|
|
477
|
+
orphanVariants,
|
|
478
|
+
noReachableFinish,
|
|
479
|
+
unknownOfferingRefs,
|
|
480
|
+
unassignedSlots
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export { assignVariant, bucketingSeed, buildCatalog, compileManifest, currencyExponent, defineFunnel, definePage, entryGuard, evaluateCondition, evaluateGate, expectedPathLength, fnv1a, formatMoney, formatOffering, funnelCatalogKeys, groupProgress, hashToUnit, isRtl, isVariantKey, nextPage, normalizeOfferings, outgoingKeys, pageMeta, parseSlotKey, pickByWeight, resolveExperiments, resolveLocale, resolveOffering, resolveRoute, weightsOf };
|
|
485
|
+
//# sourceMappingURL=chunk-SXENKZ4U.js.map
|
|
486
|
+
//# sourceMappingURL=chunk-SXENKZ4U.js.map
|