@appfunnel-dev/sdk 2.0.0-canary.2 → 2.0.0-canary.4

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.
Files changed (37) hide show
  1. package/dist/{checkout-DiQvRT5q.d.ts → checkout-7Dy6IedP.d.ts} +3 -0
  2. package/dist/{checkout-CZmEvWfC.d.cts → checkout-Dz8cGkB_.d.cts} +3 -0
  3. package/dist/{chunk-7UC5VXOR.js → chunk-AKO6XKXP.js} +28 -8
  4. package/dist/chunk-AKO6XKXP.js.map +1 -0
  5. package/dist/{chunk-VQOD2Z6Q.cjs → chunk-CY4VBSMX.cjs} +5 -3
  6. package/dist/chunk-CY4VBSMX.cjs.map +1 -0
  7. package/dist/{chunk-Z3TWO2PW.cjs → chunk-JSRKA375.cjs} +29 -7
  8. package/dist/chunk-JSRKA375.cjs.map +1 -0
  9. package/dist/{chunk-UIR6TGEW.js → chunk-M6U3FNRW.js} +5 -3
  10. package/dist/chunk-M6U3FNRW.js.map +1 -0
  11. package/dist/driver-paddle.cjs +14 -14
  12. package/dist/driver-paddle.d.cts +1 -1
  13. package/dist/driver-paddle.d.ts +1 -1
  14. package/dist/driver-paddle.js +1 -1
  15. package/dist/driver-stripe.cjs +13 -13
  16. package/dist/driver-stripe.d.cts +1 -1
  17. package/dist/driver-stripe.d.ts +1 -1
  18. package/dist/driver-stripe.js +1 -1
  19. package/dist/index.cjs +93 -54
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.d.cts +29 -6
  22. package/dist/index.d.ts +29 -6
  23. package/dist/index.js +55 -16
  24. package/dist/index.js.map +1 -1
  25. package/dist/{manifest-DQThneiG.d.cts → manifest-Cr2y1op6.d.cts} +40 -3
  26. package/dist/{manifest-DQThneiG.d.ts → manifest-Cr2y1op6.d.ts} +40 -3
  27. package/dist/manifest-entry.cjs +129 -20
  28. package/dist/manifest-entry.cjs.map +1 -1
  29. package/dist/manifest-entry.d.cts +28 -3
  30. package/dist/manifest-entry.d.ts +28 -3
  31. package/dist/manifest-entry.js +105 -5
  32. package/dist/manifest-entry.js.map +1 -1
  33. package/package.json +1 -1
  34. package/dist/chunk-7UC5VXOR.js.map +0 -1
  35. package/dist/chunk-UIR6TGEW.js.map +0 -1
  36. package/dist/chunk-VQOD2Z6Q.cjs.map +0 -1
  37. package/dist/chunk-Z3TWO2PW.cjs.map +0 -1
@@ -1,7 +1,100 @@
1
- export { assignVariant, bucketingSeed, buildCatalog, compileManifest, currencyExponent, defineFunnel, formatMoney, formatProduct, isRtl, isVariantKey, pageMeta, parseSlotKey, resolveLocale, resolveProduct, validateExperiments, weightsOf } from './chunk-7UC5VXOR.js';
1
+ export { assignVariant, bucketingSeed, buildCatalog, compileManifest, currencyExponent, defineFunnel, formatMoney, formatProduct, funnelCatalogKeys, isRtl, isVariantKey, normalizeProducts, pageMeta, parseSlotKey, resolveLocale, resolveProduct, validateExperiments, weightsOf } from './chunk-AKO6XKXP.js';
2
2
  export { INLINE_SURFACES, PROVIDER_PROFILES, checkoutError, isInlineSurface, isMerchantOfRecord, isOrchestrator, surfacesFor, validateCheckout, validateUpsell } from './chunk-YY375F2B.js';
3
3
 
4
+ // src/manifest/product-refs.ts
5
+ function mask(src) {
6
+ const out = src.split("");
7
+ const n = src.length;
8
+ let i = 0;
9
+ while (i < n) {
10
+ const c = src[i];
11
+ const d = src[i + 1];
12
+ if (c === "/" && d === "/") {
13
+ while (i < n && src[i] !== "\n") {
14
+ out[i] = " ";
15
+ i++;
16
+ }
17
+ } else if (c === "/" && d === "*") {
18
+ out[i] = " ";
19
+ out[i + 1] = " ";
20
+ i += 2;
21
+ while (i < n) {
22
+ if (src[i] === "*" && src[i + 1] === "/") {
23
+ out[i] = " ";
24
+ out[i + 1] = " ";
25
+ i += 2;
26
+ break;
27
+ }
28
+ if (src[i] !== "\n") out[i] = " ";
29
+ i++;
30
+ }
31
+ } else if (c === "'" || c === '"' || c === "`") {
32
+ i++;
33
+ while (i < n && src[i] !== c) {
34
+ if (src[i] === "\\") {
35
+ out[i] = " ";
36
+ i++;
37
+ if (i < n && src[i] !== "\n") out[i] = " ";
38
+ i++;
39
+ continue;
40
+ }
41
+ if (src[i] !== "\n") out[i] = " ";
42
+ i++;
43
+ }
44
+ i++;
45
+ } else i++;
46
+ }
47
+ return out.join("");
48
+ }
49
+ function literalAt(src, masked, open) {
50
+ const q = masked[open];
51
+ if (q !== "'" && q !== '"' && q !== "`") return null;
52
+ const close = masked.indexOf(q, open + 1);
53
+ if (close === -1) return null;
54
+ return src.slice(open + 1, close);
55
+ }
56
+ function skipWs(masked, i) {
57
+ while (i < masked.length && (masked[i] === " " || masked[i] === " " || masked[i] === "\n" || masked[i] === "\r")) i++;
58
+ return i;
59
+ }
60
+ function scanProductRefs(files) {
61
+ const seen = /* @__PURE__ */ new Set();
62
+ const order = [];
63
+ let dynamic = false;
64
+ const add = (v) => {
65
+ if (v && !seen.has(v)) {
66
+ seen.add(v);
67
+ order.push(v);
68
+ }
69
+ };
70
+ for (const [path, source] of Object.entries(files)) {
71
+ if (!/\.(tsx|jsx|ts|js)$/.test(path)) continue;
72
+ const masked = mask(source);
73
+ for (const m of masked.matchAll(/\buseProducts?\s*\(/g)) {
74
+ const plural = source.slice(m.index, m.index + 11) === "useProducts";
75
+ const j = skipWs(masked, m.index + m[0].length);
76
+ if (masked[j] === ")") {
77
+ if (plural) dynamic = true;
78
+ continue;
79
+ }
80
+ const lit = literalAt(source, masked, j);
81
+ if (lit !== null) add(lit);
82
+ else dynamic = true;
83
+ }
84
+ for (const m of masked.matchAll(/<(?:Checkout|Upsell)\b[^>]*?\bproduct\s*=\s*/g)) {
85
+ const j = m.index + m[0].length;
86
+ const lit = literalAt(source, masked, j);
87
+ if (lit !== null) add(lit);
88
+ else if (masked[j] === "{") dynamic = true;
89
+ }
90
+ }
91
+ return { slots: order, dynamic };
92
+ }
93
+
4
94
  // src/flow/scaffold.ts
95
+ function msgId(locale) {
96
+ return "msg_" + locale.replace(/[^a-zA-Z0-9]/g, "_");
97
+ }
5
98
  var DRIVERS = {
6
99
  stripe: { name: "stripeCheckoutDriver", sub: "driver-stripe" },
7
100
  paddle: { name: "paddleCheckoutDriver", sub: "driver-paddle" }
@@ -10,11 +103,13 @@ var HEADER = "// GENERATED by AppFunnel \u2014 do not edit. Built from funnel.ts
10
103
  function generateMount(opts) {
11
104
  const p = opts.importPrefix ?? "./";
12
105
  const driver = opts.checkout ? DRIVERS[opts.checkout] : null;
106
+ const locales = opts.messageLocales ?? [];
13
107
  const imports = [
14
108
  `import { createFunnelTree, type MountOpts, type RuntimePage } from '@appfunnel-dev/sdk'`,
15
109
  driver && `import { ${driver.name} } from '@appfunnel-dev/sdk/${driver.sub}'`,
16
110
  `import { config } from '${p}funnel'`,
17
- opts.hasLayout && `import Layout from '${p}layout'`
111
+ opts.hasLayout && `import Layout from '${p}layout'`,
112
+ ...locales.map((loc) => `import ${msgId(loc)} from '${p}messages/${loc}.json'`)
18
113
  ].filter(Boolean).join("\n");
19
114
  const loaders = opts.pageKeys.map(
20
115
  (k) => ` ${JSON.stringify(k)}: () => import(${JSON.stringify(`${p}pages/${k}`)}),`
@@ -23,8 +118,12 @@ function generateMount(opts) {
23
118
  "config",
24
119
  "pages",
25
120
  opts.hasLayout && "layout: Layout",
26
- driver && `checkoutDriver: ${driver.name}`
121
+ driver && `checkoutDriver: ${driver.name}`,
122
+ locales.length > 0 && "messages"
27
123
  ].filter(Boolean).join(", ");
124
+ const messagesConst = locales.length > 0 ? `
125
+ const messages = { ${locales.map((loc) => `${JSON.stringify(loc)}: ${msgId(loc)}`).join(", ")} }
126
+ ` : "";
28
127
  return `${HEADER}${imports}
29
128
 
30
129
  const loaders: Record<string, () => Promise<unknown>> = {
@@ -41,7 +140,7 @@ export const pages: RuntimePage[] = [
41
140
  .filter((k) => !flowKeys.has(k))
42
141
  .map((k) => ({ key: k, meta: {}, load: loaders[k] })),
43
142
  ] as RuntimePage[]
44
-
143
+ ${messagesConst}
45
144
  export const tree = createFunnelTree({ ${treeArgs} })
46
145
 
47
146
  export type { MountOpts }
@@ -72,6 +171,7 @@ import { tree, pages, type MountOpts } from './mount'
72
171
  export const manifest = compileManifest({
73
172
  funnel: config,
74
173
  pages: pages.map((pg) => ({ key: pg.key, meta: pg.meta })),
174
+ productRefs: ${JSON.stringify(opts.productRefs ?? [])},
75
175
  })
76
176
 
77
177
  export async function ssr(opts: MountOpts): Promise<string> {
@@ -93,6 +193,6 @@ if (el) createRoot(el).render(tree(${JSON.stringify(opts.data)}))
93
193
  `;
94
194
  }
95
195
 
96
- export { generateDevEntry, generateEntryClient, generateEntryServer, generateMount };
196
+ export { generateDevEntry, generateEntryClient, generateEntryServer, generateMount, scanProductRefs };
97
197
  //# sourceMappingURL=manifest-entry.js.map
98
198
  //# sourceMappingURL=manifest-entry.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/flow/scaffold.ts"],"names":[],"mappings":"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","file":"manifest-entry.js","sourcesContent":["/**\n * Source generators for the funnel build \"machinery\" authors no longer write. `funnel.ts` (the\n * spine: id, responses, products, checkout, and the page list + declarative routing) plus the\n * `pages/` folder are the ONLY authored source. The build (renderer/builder) and the CLI dev server\n * call these to synthesize:\n * - `mount.tsx` — code-split page loaders + `createFunnelTree`\n * - `entry.client.tsx` — hydrate the SSR'd markup from injected data\n * - `entry.server.tsx` — `ssr()` + the compiled `manifest`\n *\n * All string templates, zero runtime deps — this module lives in the pure `@appfunnel-dev/sdk/\n * manifest` entry so tooling imports it without the funnel runtime.\n */\n\nexport interface ScaffoldOptions {\n\t/** Page component keys = `pages/<key>.tsx` filenames (base flow pages AND `@variant` pages). */\n\tpageKeys: string[]\n\t/** Import prefix to reach the funnel root: `'./'` at the root (build), `'../../'` in a subdir (CLI dev). */\n\timportPrefix?: string\n\t/** Whether a `layout.tsx` exists to wrap the pages. */\n\thasLayout?: boolean\n\t/** Whether a `styles.css` exists (client entry imports it). */\n\thasStyles?: boolean\n\t/** Checkout provider from `config.checkout` — wires the driver; omit for none. */\n\tcheckout?: 'stripe' | 'paddle' | null\n}\n\nconst DRIVERS = {\n\tstripe: { name: 'stripeCheckoutDriver', sub: 'driver-stripe' },\n\tpaddle: { name: 'paddleCheckoutDriver', sub: 'driver-paddle' },\n} as const\n\nconst HEADER = '// GENERATED by AppFunnel — do not edit. Built from funnel.ts + the pages/ folder.\\n'\n\n/** `mount.tsx` — the code-split loaders + tree, derived from `config.pages` and the `pages/` files. */\nexport function generateMount(opts: ScaffoldOptions): string {\n\tconst p = opts.importPrefix ?? './'\n\tconst driver = opts.checkout ? DRIVERS[opts.checkout] : null\n\n\tconst imports = [\n\t\t`import { createFunnelTree, type MountOpts, type RuntimePage } from '@appfunnel-dev/sdk'`,\n\t\tdriver && `import { ${driver.name} } from '@appfunnel-dev/sdk/${driver.sub}'`,\n\t\t`import { config } from '${p}funnel'`,\n\t\topts.hasLayout && `import Layout from '${p}layout'`,\n\t]\n\t\t.filter(Boolean)\n\t\t.join('\\n')\n\n\t// A static import() per page file so Vite emits one code-split chunk each.\n\tconst loaders = opts.pageKeys\n\t\t.map(\n\t\t\t(k) => `\\t${JSON.stringify(k)}: () => import(${JSON.stringify(`${p}pages/${k}`)}),`\n\t\t)\n\t\t.join('\\n')\n\n\tconst treeArgs = [\n\t\t'config',\n\t\t'pages',\n\t\topts.hasLayout && 'layout: Layout',\n\t\tdriver && `checkoutDriver: ${driver.name}`,\n\t]\n\t\t.filter(Boolean)\n\t\t.join(', ')\n\n\treturn `${HEADER}${imports}\n\nconst loaders: Record<string, () => Promise<unknown>> = {\n${loaders}\n}\n\n// Flow pages come from funnel.ts (order + meta + routing). Any remaining page file is an off-flow\n// \\`@variant\\` page (wired by experiment records at render time), appended so it's loadable.\nconst decls = config.pages ?? []\nconst flowKeys = new Set(decls.map((d) => d.key))\nexport const pages: RuntimePage[] = [\n\t...decls.map((d) => ({ key: d.key, meta: d, load: loaders[d.key] })),\n\t...Object.keys(loaders)\n\t\t.filter((k) => !flowKeys.has(k))\n\t\t.map((k) => ({ key: k, meta: {}, load: loaders[k] })),\n] as RuntimePage[]\n\nexport const tree = createFunnelTree({ ${treeArgs} })\n\nexport type { MountOpts }\n`\n}\n\n/** `entry.client.tsx` — hydrate the SSR'd markup from the injected `#__af_data`. */\nexport function generateEntryClient(opts: Pick<ScaffoldOptions, 'importPrefix' | 'hasStyles'>): string {\n\tconst p = opts.importPrefix ?? './'\n\tconst styles = opts.hasStyles ? `import '${p}styles.css'\\n` : ''\n\treturn `${HEADER}${styles}import { hydrateRoot } from 'react-dom/client'\nimport { tree, type MountOpts } from './mount'\n\nconst dataEl = document.getElementById('__af_data')\nconst data = (dataEl?.textContent ? JSON.parse(dataEl.textContent) : {}) as MountOpts & {\n\tinitialKey?: string | null\n}\nconst root = document.getElementById('root')\nif (root) hydrateRoot(root, tree({ ...data, initialKey: data.initialKey ?? undefined }))\n`\n}\n\n/** `entry.server.tsx` — `ssr(opts)` HTML + the compiled `manifest` (read by the build). */\nexport function generateEntryServer(opts: Pick<ScaffoldOptions, 'importPrefix'>): string {\n\tconst p = opts.importPrefix ?? './'\n\treturn `${HEADER}import { renderToReadableStream } from 'react-dom/server.browser'\nimport { compileManifest } from '@appfunnel-dev/sdk'\nimport { config } from '${p}funnel'\nimport { tree, pages, type MountOpts } from './mount'\n\nexport const manifest = compileManifest({\n\tfunnel: config,\n\tpages: pages.map((pg) => ({ key: pg.key, meta: pg.meta })),\n})\n\nexport async function ssr(opts: MountOpts): Promise<string> {\n\tconst stream = await renderToReadableStream(tree(opts))\n\tawait stream.allReady\n\treturn await new Response(stream).text()\n}\n`\n}\n\n/** Client-only dev entry (createRoot, no SSR) — for the CLI `dev` server. */\nexport function generateDevEntry(\n\topts: Pick<ScaffoldOptions, 'importPrefix' | 'hasStyles'> & { data: unknown }\n): string {\n\tconst p = opts.importPrefix ?? './'\n\tconst styles = opts.hasStyles ? `import '${p}styles.css'\\n` : ''\n\treturn `${HEADER}${styles}import { createRoot } from 'react-dom/client'\nimport { tree } from './mount'\n\nconst el = document.getElementById('root')\nif (el) createRoot(el).render(tree(${JSON.stringify(opts.data)}))\n`\n}\n"]}
1
+ {"version":3,"sources":["../src/manifest/product-refs.ts","../src/flow/scaffold.ts"],"names":[],"mappings":"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","file":"manifest-entry.js","sourcesContent":["/**\n * Pure, React-free page scan: find which product SLOTS a funnel's pages reference — the first\n * string-literal arg of `useProduct(...)` and the `product` prop of `<Checkout>` / `<Upsell>`.\n * Ships via `@appfunnel-dev/sdk/manifest` so the BUILD, the EDITOR, and the AI all run the SAME\n * check. The result feeds `compileManifest({ productRefs })`, which flags references to slots the\n * funnel doesn't declare (unknown) or hasn't assigned a catalog product to (unassigned).\n *\n * Non-literal args (`useProduct(selectedId)`, `product={expr}`) and the `useProducts()` wildcard are\n * DYNAMIC — they can't be statically enumerated, so they're reported via `dynamic` and never turned\n * into a hard error (validation relaxes when a funnel is dynamic). Mirrors the mask-then-read\n * discipline the funnel.ts code-mods use, so a `useProduct('x')` inside a comment or string literal\n * can't false-positive.\n */\n\nexport interface ProductRefScan {\n /** Concrete slot names referenced by LITERAL args, deduped, in first-seen order. */\n slots: string[]\n /** True if any ref is a non-literal arg or a `useProducts()` wildcard (not statically enumerable). */\n dynamic: boolean\n}\n\n/** Blank comment + string/template CONTENTS (keep quote delimiters + length) so scans don't fire inside them. */\nfunction mask(src: string): string {\n const out = src.split('')\n const n = src.length\n let i = 0\n while (i < n) {\n const c = src[i]\n const d = src[i + 1]\n if (c === '/' && d === '/') {\n while (i < n && src[i] !== '\\n') { out[i] = ' '; i++ }\n } else if (c === '/' && d === '*') {\n out[i] = ' '; out[i + 1] = ' '; i += 2\n while (i < n) {\n if (src[i] === '*' && src[i + 1] === '/') { out[i] = ' '; out[i + 1] = ' '; i += 2; break }\n if (src[i] !== '\\n') out[i] = ' '\n i++\n }\n } else if (c === \"'\" || c === '\"' || c === '`') {\n i++\n while (i < n && src[i] !== c) {\n if (src[i] === '\\\\') { out[i] = ' '; i++; if (i < n && src[i] !== '\\n') out[i] = ' '; i++; continue }\n if (src[i] !== '\\n') out[i] = ' '\n i++\n }\n i++ // closing delimiter (kept)\n } else i++\n }\n return out.join('')\n}\n\n/** A quoted string literal starting at `open` (a quote char in the MASK). Value read from the ORIGINAL. */\nfunction literalAt(src: string, masked: string, open: number): string | null {\n const q = masked[open]\n if (q !== \"'\" && q !== '\"' && q !== '`') return null\n const close = masked.indexOf(q, open + 1)\n if (close === -1) return null\n return src.slice(open + 1, close)\n}\n\n/** Advance past whitespace in `masked` from `i`. */\nfunction skipWs(masked: string, i: number): number {\n while (i < masked.length && (masked[i] === ' ' || masked[i] === '\\t' || masked[i] === '\\n' || masked[i] === '\\r')) i++\n return i\n}\n\nexport function scanProductRefs(files: Record<string, string>): ProductRefScan {\n const seen = new Set<string>()\n const order: string[] = []\n let dynamic = false\n const add = (v: string) => { if (v && !seen.has(v)) { seen.add(v); order.push(v) } }\n\n for (const [path, source] of Object.entries(files)) {\n if (!/\\.(tsx|jsx|ts|js)$/.test(path)) continue\n const masked = mask(source)\n\n // useProduct( … ) / useProducts( … ) — positions on the MASK (a call inside a string won't match).\n for (const m of masked.matchAll(/\\buseProducts?\\s*\\(/g)) {\n const plural = source.slice(m.index!, m.index! + 11) === 'useProducts'\n const j = skipWs(masked, m.index! + m[0].length)\n if (masked[j] === ')') { if (plural) dynamic = true; continue } // useProducts() = whole-catalog wildcard\n const lit = literalAt(source, masked, j)\n if (lit !== null) add(lit)\n else dynamic = true // useProduct(variable) — can't resolve statically\n }\n\n // <Checkout product=\"…\"> / <Upsell product=\"…\"> — the `product` attribute value.\n for (const m of masked.matchAll(/<(?:Checkout|Upsell)\\b[^>]*?\\bproduct\\s*=\\s*/g)) {\n const j = m.index! + m[0].length\n const lit = literalAt(source, masked, j)\n if (lit !== null) add(lit)\n else if (masked[j] === '{') dynamic = true // product={expr}\n }\n }\n return { slots: order, dynamic }\n}\n","/**\n * Source generators for the funnel build \"machinery\" authors no longer write. `funnel.ts` (the\n * spine: id, responses, products, checkout, and the page list + declarative routing) plus the\n * `pages/` folder are the ONLY authored source. The build (renderer/builder) and the CLI dev server\n * call these to synthesize:\n * - `mount.tsx` — code-split page loaders + `createFunnelTree`\n * - `entry.client.tsx` — hydrate the SSR'd markup from injected data\n * - `entry.server.tsx` — `ssr()` + the compiled `manifest`\n *\n * All string templates, zero runtime deps — this module lives in the pure `@appfunnel-dev/sdk/\n * manifest` entry so tooling imports it without the funnel runtime.\n */\n\nexport interface ScaffoldOptions {\n\t/** Page component keys = `pages/<key>.tsx` filenames (base flow pages AND `@variant` pages). */\n\tpageKeys: string[]\n\t/** Import prefix to reach the funnel root: `'./'` at the root (build), `'../../'` in a subdir (CLI dev). */\n\timportPrefix?: string\n\t/** Whether a `layout.tsx` exists to wrap the pages. */\n\thasLayout?: boolean\n\t/** Whether a `styles.css` exists (client entry imports it). */\n\thasStyles?: boolean\n\t/** Checkout provider from `config.checkout` — wires the driver; omit for none. */\n\tcheckout?: 'stripe' | 'paddle' | null\n\t/** Locales with a `messages/<locale>.json` catalog to bake into the mount (so `t()` resolves at render). */\n\tmessageLocales?: string[]\n\t/** Slot names the pages reference (from `scanProductRefs`), baked into the manifest for validation. */\n\tproductRefs?: string[]\n}\n\n/** A safe JS identifier for a message-catalog import (`en` → `msg_en`, `pt-BR` → `msg_pt_BR`). */\nfunction msgId(locale: string): string {\n\treturn 'msg_' + locale.replace(/[^a-zA-Z0-9]/g, '_')\n}\n\nconst DRIVERS = {\n\tstripe: { name: 'stripeCheckoutDriver', sub: 'driver-stripe' },\n\tpaddle: { name: 'paddleCheckoutDriver', sub: 'driver-paddle' },\n} as const\n\nconst HEADER = '// GENERATED by AppFunnel — do not edit. Built from funnel.ts + the pages/ folder.\\n'\n\n/** `mount.tsx` — the code-split loaders + tree, derived from `config.pages` and the `pages/` files. */\nexport function generateMount(opts: ScaffoldOptions): string {\n\tconst p = opts.importPrefix ?? './'\n\tconst driver = opts.checkout ? DRIVERS[opts.checkout] : null\n\n\tconst locales = opts.messageLocales ?? []\n\tconst imports = [\n\t\t`import { createFunnelTree, type MountOpts, type RuntimePage } from '@appfunnel-dev/sdk'`,\n\t\tdriver && `import { ${driver.name} } from '@appfunnel-dev/sdk/${driver.sub}'`,\n\t\t`import { config } from '${p}funnel'`,\n\t\topts.hasLayout && `import Layout from '${p}layout'`,\n\t\t...locales.map((loc) => `import ${msgId(loc)} from '${p}messages/${loc}.json'`),\n\t]\n\t\t.filter(Boolean)\n\t\t.join('\\n')\n\n\t// A static import() per page file so Vite emits one code-split chunk each.\n\tconst loaders = opts.pageKeys\n\t\t.map(\n\t\t\t(k) => `\\t${JSON.stringify(k)}: () => import(${JSON.stringify(`${p}pages/${k}`)}),`\n\t\t)\n\t\t.join('\\n')\n\n\tconst treeArgs = [\n\t\t'config',\n\t\t'pages',\n\t\topts.hasLayout && 'layout: Layout',\n\t\tdriver && `checkoutDriver: ${driver.name}`,\n\t\tlocales.length > 0 && 'messages',\n\t]\n\t\t.filter(Boolean)\n\t\t.join(', ')\n\n\t// The baked message catalog: locale → messages/<locale>.json (so `t()` resolves at render).\n\tconst messagesConst =\n\t\tlocales.length > 0\n\t\t\t? `\\nconst messages = { ${locales.map((loc) => `${JSON.stringify(loc)}: ${msgId(loc)}`).join(', ')} }\\n`\n\t\t\t: ''\n\n\treturn `${HEADER}${imports}\n\nconst loaders: Record<string, () => Promise<unknown>> = {\n${loaders}\n}\n\n// Flow pages come from funnel.ts (order + meta + routing). Any remaining page file is an off-flow\n// \\`@variant\\` page (wired by experiment records at render time), appended so it's loadable.\nconst decls = config.pages ?? []\nconst flowKeys = new Set(decls.map((d) => d.key))\nexport const pages: RuntimePage[] = [\n\t...decls.map((d) => ({ key: d.key, meta: d, load: loaders[d.key] })),\n\t...Object.keys(loaders)\n\t\t.filter((k) => !flowKeys.has(k))\n\t\t.map((k) => ({ key: k, meta: {}, load: loaders[k] })),\n] as RuntimePage[]\n${messagesConst}\nexport const tree = createFunnelTree({ ${treeArgs} })\n\nexport type { MountOpts }\n`\n}\n\n/** `entry.client.tsx` — hydrate the SSR'd markup from the injected `#__af_data`. */\nexport function generateEntryClient(opts: Pick<ScaffoldOptions, 'importPrefix' | 'hasStyles'>): string {\n\tconst p = opts.importPrefix ?? './'\n\tconst styles = opts.hasStyles ? `import '${p}styles.css'\\n` : ''\n\treturn `${HEADER}${styles}import { hydrateRoot } from 'react-dom/client'\nimport { tree, type MountOpts } from './mount'\n\nconst dataEl = document.getElementById('__af_data')\nconst data = (dataEl?.textContent ? JSON.parse(dataEl.textContent) : {}) as MountOpts & {\n\tinitialKey?: string | null\n}\nconst root = document.getElementById('root')\nif (root) hydrateRoot(root, tree({ ...data, initialKey: data.initialKey ?? undefined }))\n`\n}\n\n/** `entry.server.tsx` — `ssr(opts)` HTML + the compiled `manifest` (read by the build). */\nexport function generateEntryServer(opts: Pick<ScaffoldOptions, 'importPrefix' | 'productRefs'>): string {\n\tconst p = opts.importPrefix ?? './'\n\treturn `${HEADER}import { renderToReadableStream } from 'react-dom/server.browser'\nimport { compileManifest } from '@appfunnel-dev/sdk'\nimport { config } from '${p}funnel'\nimport { tree, pages, type MountOpts } from './mount'\n\nexport const manifest = compileManifest({\n\tfunnel: config,\n\tpages: pages.map((pg) => ({ key: pg.key, meta: pg.meta })),\n\tproductRefs: ${JSON.stringify(opts.productRefs ?? [])},\n})\n\nexport async function ssr(opts: MountOpts): Promise<string> {\n\tconst stream = await renderToReadableStream(tree(opts))\n\tawait stream.allReady\n\treturn await new Response(stream).text()\n}\n`\n}\n\n/** Client-only dev entry (createRoot, no SSR) — for the CLI `dev` server. */\nexport function generateDevEntry(\n\topts: Pick<ScaffoldOptions, 'importPrefix' | 'hasStyles'> & { data: unknown }\n): string {\n\tconst p = opts.importPrefix ?? './'\n\tconst styles = opts.hasStyles ? `import '${p}styles.css'\\n` : ''\n\treturn `${HEADER}${styles}import { createRoot } from 'react-dom/client'\nimport { tree } from './mount'\n\nconst el = document.getElementById('root')\nif (el) createRoot(el).render(tree(${JSON.stringify(opts.data)}))\n`\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appfunnel-dev/sdk",
3
- "version": "2.0.0-canary.2",
3
+ "version": "2.0.0-canary.4",
4
4
  "description": "The AppFunnel funnel framework — typed namespaces, routing, commerce, modals, i18n for React funnels",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/i18n/locale.ts","../src/commerce/money.ts","../src/flow/spine.ts","../src/flow/experiments.ts","../src/manifest/manifest.ts"],"names":[],"mappings":"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","file":"chunk-7UC5VXOR.js","sourcesContent":["/**\n * Pure locale resolution — no React. Split out of {@link ./translation} so the\n * server/tooling entry (`@appfunnel-dev/sdk/manifest`) can resolve a visitor's\n * locale without pulling component code. {@link ./translation} re-exports it.\n */\n\nimport type { FunnelLocales } from '../flow/spine'\n\nexport type Direction = 'ltr' | 'rtl'\n\nconst RTL_LANGS = new Set(['ar', 'he', 'fa', 'ur', 'ps', 'sd', 'dv', 'yi'])\n\n/** Whether a locale is right-to-left (by language subtag). */\nexport function isRtl(locale: string): boolean {\n return RTL_LANGS.has(locale.split('-')[0])\n}\n\nconst base = (l: string | undefined): string | undefined => l?.split('-')[0]\n\n/**\n * Resolve the active locale: `override` (URL/param) → `detected` (device) →\n * funnel default, each only if supported (matched by exact tag or language\n * subtag). Mirrors doc 05's resolution chain (the geo/header steps happen\n * server-side and arrive via `override`/`detected`).\n */\nexport function resolveLocale(\n config: FunnelLocales | undefined,\n detected: string,\n override?: string,\n): string {\n const supported = config?.supported ?? (config?.default ? [config.default] : ['en'])\n const def = config?.default ?? supported[0] ?? 'en'\n const pick = (l?: string): string | undefined => {\n if (!l) return undefined\n if (supported.includes(l)) return l\n return supported.find((s) => base(s) === base(l))\n }\n return pick(override) ?? pick(detected) ?? def\n}\n","/**\n * Pure catalog math — no React, no I/O. Split out of {@link ./catalog} so the\n * server/tooling entry (`@appfunnel-dev/sdk/manifest`) can compile/resolve\n * products without pulling react-dom or any component code into its graph.\n * {@link ./catalog} re-exports everything here and adds the React wiring.\n */\n\n// ── Shape ────────────────────────────────────────────────\n\nexport type Interval = 'day' | 'week' | 'month' | 'year' | 'one_time'\n\n/** What the platform hands the funnel per product (a single provider-resolved price). */\nexport interface ProductInput {\n id: string\n name?: string\n displayName?: string\n /** Provider-resolved price in **minor units** (the provider chose the currency). */\n amount: number\n /** Currency of `amount`, as the provider resolved it (e.g. `DKK`). */\n currency: string\n interval?: Interval\n intervalCount?: number\n trialDays?: number\n /** Trial price in minor units (omit/0 = free trial). */\n trialAmount?: number\n /** Settlement provider — opaque to the author, used by the checkout driver. */\n provider?: string\n}\n\n/** A money value with a locale-formatted string. */\nexport interface Money {\n /** Major units, e.g. `79`. */\n amount: number\n /** Minor units, e.g. `7900`. */\n minor: number\n currency: string\n /** Locale-formatted, e.g. `kr 79,00` / `€9,99` / `$9.99`. */\n formatted: string\n}\n\n/**\n * The product with its amounts resolved (currency + period math), **before**\n * locale formatting — what the catalog stores. `useProduct` formats it.\n */\nexport interface ResolvedProduct {\n id: string\n name: string\n displayName: string\n provider: string\n currency: string\n priceMinor: number\n perDayMinor: number\n perWeekMinor: number\n perMonthMinor: number\n perYearMinor: number\n period: string\n periodly: string\n hasTrial: boolean\n trialDays: number\n trialMinor: number\n}\n\n/** Display-ready product the author reads (every amount locale-formatted). */\nexport interface Product {\n id: string\n name: string\n displayName: string\n provider: string\n price: Money\n /** e.g. `month`, `quarter`, `one-time`. */\n period: string\n /** e.g. `monthly`, `quarterly`, `one-time`. */\n periodly: string\n /** Period-normalized prices (for \"just $0.27/day\" style copy). */\n perDay: Money\n perWeek: Money\n perMonth: Money\n perYear: Money\n hasTrial: boolean\n trialDays: number\n trialPrice: Money | null\n}\n\n// ── Resolve (currency-agnostic) ──────────────────────────\n\n/**\n * The period model: the **day is the canonical unit** and every interval is a\n * consistent number of days — `year = 365`, `month = 365/12` (~30.42),\n * `week = 7`. Consistent means the derived periods are exact multiples of each\n * other: a monthly product's `perMonthMinor` is its price, its `perYearMinor`\n * is exactly `12×` the price, and a yearly product's `perMonthMinor` is exactly\n * `price / 12` — no 360-vs-365 drift (the old `month = 30` model made a yearly\n * plan's \"per month\" ~1.4% off).\n */\nconst PERIOD_DAYS: Record<Interval, number> = {\n day: 1, week: 7, month: 365 / 12, year: 365, one_time: 0,\n}\n\nfunction periodDays(interval: Interval, count: number): number {\n return PERIOD_DAYS[interval] * count\n}\n\nfunction intervalLabel(interval: Interval, count: number): { period: string; periodly: string } {\n if (interval === 'one_time') return { period: 'one-time', periodly: 'one-time' }\n if (interval === 'month' && count === 3) return { period: 'quarter', periodly: 'quarterly' }\n if (interval === 'month' && count === 6) return { period: '6 months', periodly: 'semiannually' }\n if (interval === 'week' && count === 2) return { period: '2 weeks', periodly: 'biweekly' }\n if (count === 1) {\n const periodly = { day: 'daily', week: 'weekly', month: 'monthly', year: 'yearly' }[interval]\n return { period: interval, periodly }\n }\n return { period: `${count} ${interval}s`, periodly: `every ${count} ${interval}s` }\n}\n\n/**\n * Resolve a {@link ProductInput} into amounts + period math (no formatting yet).\n * Per-period amounts are **integer minor units** (each derived from the exact\n * daily rate, then rounded — never round-then-multiply, so error doesn't compound).\n */\nexport function resolveProduct(input: ProductInput): ResolvedProduct {\n const interval = input.interval ?? 'one_time'\n const count = input.intervalCount ?? 1\n const days = periodDays(interval, count)\n // Derive each period from the exact price/days ratio, rounding each result\n // independently to whole minor units.\n const per = (targetDays: number): number =>\n days > 0 ? Math.round((input.amount * targetDays) / days) : input.amount\n const label = intervalLabel(interval, count)\n // A paid trial can be \"N days for X\" OR an intro price with no day count —\n // a set trialAmount alone still makes it a trial (trialDays: 0 must not\n // swallow the trial price).\n const trialDays = input.trialDays ?? 0\n const trialMinor = input.trialAmount ?? 0\n\n return {\n id: input.id,\n name: input.name ?? input.id,\n displayName: input.displayName ?? input.name ?? input.id,\n provider: input.provider ?? 'stripe',\n currency: input.currency,\n priceMinor: input.amount,\n perDayMinor: per(1),\n perWeekMinor: per(7),\n perMonthMinor: per(365 / 12),\n perYearMinor: per(365),\n period: label.period,\n periodly: label.periodly,\n hasTrial: trialDays > 0 || trialMinor > 0,\n trialDays,\n trialMinor,\n }\n}\n\n/** Build the id → {@link ResolvedProduct} catalog. Formatting happens at read time. */\nexport function buildCatalog(inputs: ProductInput[]): Map<string, ResolvedProduct> {\n return new Map(inputs.map((i) => [i.id, resolveProduct(i)]))\n}\n\n// ── Format (locale-dependent) ────────────────────────────\n\n/** currency code → minor-unit exponent, resolved once via Intl (JPY→0, KWD→3, …). */\nconst EXPONENT_CACHE = new Map<string, number>()\n\n/**\n * The minor-unit exponent for a currency (`USD`→2, `JPY`→0, `KWD`/`BHD`→3),\n * derived from `Intl.NumberFormat(...).resolvedOptions().maximumFractionDigits`\n * with a fallback of 2 for unknown/invalid codes.\n */\nexport function currencyExponent(currency: string): number {\n const code = currency.toUpperCase()\n const hit = EXPONENT_CACHE.get(code)\n if (hit !== undefined) return hit\n let exp = 2\n try {\n exp = new Intl.NumberFormat('en', { style: 'currency', currency: code })\n .resolvedOptions().maximumFractionDigits ?? 2\n } catch {\n exp = 2\n }\n EXPONENT_CACHE.set(code, exp)\n return exp\n}\n\n/** Format minor units of `currency` in `locale` (exponent-aware: 7900 JPY = ¥7,900). */\nexport function formatMoney(minor: number, currency: string, locale: string): Money {\n const code = currency.toUpperCase()\n const exp = currencyExponent(code)\n const amount = minor / 10 ** exp\n let formatted: string\n try {\n formatted = new Intl.NumberFormat(locale, { style: 'currency', currency: code }).format(amount)\n } catch {\n formatted = `${code} ${amount.toFixed(exp)}`\n }\n return { amount, minor, currency: code, formatted }\n}\n\n/** Format a {@link ResolvedProduct} into a display {@link Product} in `locale`. */\nexport function formatProduct(r: ResolvedProduct, locale: string): Product {\n const m = (minor: number): Money => formatMoney(minor, r.currency, locale)\n return {\n id: r.id,\n name: r.name,\n displayName: r.displayName,\n provider: r.provider,\n price: m(r.priceMinor),\n period: r.period,\n periodly: r.periodly,\n perDay: m(r.perDayMinor),\n perWeek: m(r.perWeekMinor),\n perMonth: m(r.perMonthMinor),\n perYear: m(r.perYearMinor),\n hasTrial: r.hasTrial,\n trialDays: r.trialDays,\n trialPrice: r.hasTrial ? m(r.trialMinor) : null,\n }\n}\n","import type { ComponentType } from 'react'\nimport type { VariableConfig, VariableValue } from '../types'\nimport type { FunnelContext } from '../state/context'\n\n/**\n * The funnel **spine** — the constrained, declarative configuration authored as\n * code (`defineFunnel` / `pageMeta` / `definePage`) yet **dashboard-editable**\n * (static AST parse → pretty UI → codegen) and compiled to the manifest IR.\n *\n * Principle: **presentation = code** (the page components), **configuration =\n * data** (this spine). See docs/platform-v2/06-funnel-project-model.md.\n *\n * Routing is **declarative nodes + code predicates** (decided — doc 06): a\n * page's `next` is an ordered list of {@link Route}s, each with a **static `to`\n * page key** (so the dashboard can draw every possible edge without executing\n * anything) and an optional `when` **gate** that returns true/false. The gate is\n * either a declarative {@link Condition} (one field — UI-editable data) or a code\n * {@link Predicate} (`s => boolean` — the escape hatch, opaque body but the edge\n * is still visible). First matching route wins; a route with no `when` is the\n * fallback. Multiple conditions = multiple routes (or boolean logic in one\n * predicate). Omit `next` entirely for the default linear flow.\n */\n\n// ── Snapshot (what a predicate sees) ─────────────────────\n\n/**\n * Read-only view of every namespace a routing/condition predicate can branch\n * on. `user`/`responses`/`data` are the writable namespaces; `context` is the\n * read-only computed one ({@link FunnelContext}).\n */\nexport interface FunnelSnapshot {\n user: Record<string, VariableValue>\n responses: Record<string, VariableValue>\n data: Record<string, VariableValue>\n context: FunnelContext\n}\n\n/** A code-predicate gate — `s => s.responses.goal === 'gain'`. */\nexport type Predicate = (s: FunnelSnapshot) => boolean\n\nexport type ConditionOp =\n | 'eq' | 'neq' | 'in' | 'gt' | 'gte' | 'lt' | 'lte'\n | 'exists' | 'empty' | 'contains'\n\n/**\n * A declarative gate over **one** namespaced field — UI-editable data the\n * dashboard renders as a condition row (`field` `op` `value`). `field` is a\n * dotted path into the snapshot: `responses.goal`, `user.country`,\n * `context.device.isMobile`.\n */\nexport interface Condition {\n field: string\n op: ConditionOp\n value?: VariableValue | VariableValue[]\n}\n\n/** An edge gate: either declarative data ({@link Condition}) or a {@link Predicate}. */\nexport type Gate = Condition | Predicate\n\n/**\n * One routing edge out of a page. `to` is a **static** target key (the dashboard\n * reads these to draw the flow graph); `when` gates the edge (omit = fallback).\n */\nexport interface Route {\n to: string\n when?: Gate\n}\n\n/** Read a dotted path out of the snapshot (flat namespaces + nested `context`). */\nfunction readField(s: FunnelSnapshot, field: string): VariableValue {\n let cur: unknown = s\n for (const part of field.split('.')) {\n if (cur == null) return undefined\n cur = (cur as Record<string, unknown>)[part]\n }\n return cur as VariableValue\n}\n\n/** Evaluate a declarative {@link Condition} against a snapshot. */\nexport function evaluateCondition(cond: Condition, s: FunnelSnapshot): boolean {\n const val = readField(s, cond.field)\n const { op, value } = cond\n switch (op) {\n case 'eq': return val === value\n case 'neq': return val !== value\n case 'in': return Array.isArray(value) && (value as VariableValue[]).includes(val as VariableValue)\n case 'gt': return typeof val === 'number' && typeof value === 'number' && val > value\n case 'gte': return typeof val === 'number' && typeof value === 'number' && val >= value\n case 'lt': return typeof val === 'number' && typeof value === 'number' && val < value\n case 'lte': return typeof val === 'number' && typeof value === 'number' && val <= value\n case 'contains':\n if (typeof val === 'string') return val.includes(String(value))\n if (Array.isArray(val)) return (val as VariableValue[]).includes(value as VariableValue)\n return false\n case 'exists': return val !== undefined && val !== null && val !== ''\n case 'empty':\n return val === undefined || val === null || val === '' ||\n (Array.isArray(val) && val.length === 0)\n default: return false\n }\n}\n\n/** Evaluate either gate kind to a boolean. */\nexport function evaluateGate(gate: Gate, s: FunnelSnapshot): boolean {\n return typeof gate === 'function' ? gate(s) : evaluateCondition(gate, s)\n}\n\n/**\n * Resolve an ordered list of routes to a target page key, or `undefined` to fall\n * through to the linear next. First route whose gate passes (or has no `when`)\n * wins.\n */\nexport function resolveRoute(\n routes: Route[] | undefined,\n s: FunnelSnapshot,\n): string | undefined {\n if (!routes) return undefined\n for (const route of routes) {\n if (!route.when || evaluateGate(route.when, s)) return route.to\n }\n return undefined\n}\n\n// ── Pages ────────────────────────────────────────────────\n\nexport type PageType =\n | 'default' // plain content/step page (the baseline)\n | 'email-capture' // collects user.email — the mandatory identity step (validated)\n | 'paywall' // the offer/pricing page → drives paywall-view + paywall-CR\n | 'upsell' // post-purchase one-click → drives Upsell Rate + off-session charge\n | 'finish' // terminal success/thank-you page → completion analytics, ends the flow\n\nexport interface PageMeta {\n /** Page kind — drives behavior + analytics (e.g. `paywall`, `upsell`). */\n type?: PageType\n /** URL slug; defaults to the page key. */\n slug?: string\n /**\n * Ordered routing edges out of this page (first match wins). Each has a static\n * `to` key + an optional `when` gate. Omit for the default linear flow.\n */\n next?: Route[]\n /**\n * Entry precondition for **deep-link / reload** restoration. When the runtime is\n * asked to start *on this page* (not via in-funnel navigation), it restores there\n * only if `guard` passes against the restored snapshot — otherwise it falls back\n * to the start page. e.g. an upsell page: `guard: s => s.data.purchased === true`.\n * Reaching the page via `next()` is unaffected (routing already gated it). Omit =\n * always restorable. Same gate kind as routing (`Condition` or predicate).\n */\n guard?: Gate\n}\n\n/**\n * Identity helper for a page's co-located metadata.\n * `export const meta = pageMeta({ type: 'paywall', next: s => … })`.\n */\nexport function pageMeta(meta: PageMeta): PageMeta {\n return meta\n}\n\n/**\n * Default entry guard implied by a page **type** — a built-in precondition every\n * funnel inherits. `paywall` and `upsell` require a captured `user.email` (the\n * mandatory identity step), so a deep-link / reload straight into them without\n * email bounces to the start. Authors don't write this; an explicit `meta.guard`\n * **overrides** it (see {@link entryGuard}).\n */\nconst TYPE_GUARDS: Partial<Record<PageType, Gate>> = {\n paywall: { field: 'user.email', op: 'exists' },\n upsell: { field: 'user.email', op: 'exists' },\n}\n\n/**\n * The effective entry guard for a page: an explicit `meta.guard` if the author set\n * one, **otherwise** the page-type default (e.g. paywall/upsell need email). The\n * explicit guard fully **overrides** the default — it's not combined. Returns\n * `undefined` when neither applies.\n */\nexport function entryGuard(meta?: PageMeta): Gate | undefined {\n return meta?.guard ?? (meta?.type ? TYPE_GUARDS[meta.type] : undefined)\n}\n\n/**\n * Identity helper for a page component. The page is plain React that reaches the\n * runtime through hooks (`useResponse`, `useNavigation`, …) — there is no `sdk`\n * prop. `export default definePage(function Welcome() { … })`.\n */\nexport function definePage<P extends object = Record<string, never>>(\n component: ComponentType<P>,\n): ComponentType<P> {\n return component\n}\n\n// ── Flow resolution ──────────────────────────────────────\n\n/** A page in the flow: its key + (optional) co-located meta. */\nexport interface FlowPage {\n key: string\n meta?: PageMeta\n}\n\n/**\n * A page as declared in `funnel.ts` — the key + its metadata, flattened. This is the\n * AUTHORED shape (funnel.ts owns the page list, order, and routing); the build generates\n * the code-split `mount.tsx` from it. Routing (`next`) must stay DECLARATIVE (Route[] with\n * `{ to, when }` conditions, no predicate functions) so the web builder UI can edit it.\n */\nexport interface FunnelPage extends PageMeta {\n key: string\n}\n\n/**\n * Decide the next page from `currentKey`:\n * 1. the current page's `next` routes ({@link resolveRoute}), if one matches, else\n * 2. the next page in file order (the default linear flow), else\n * 3. `undefined` (end of funnel / current key not found).\n */\nexport function nextPage(\n pages: FlowPage[],\n currentKey: string,\n s: FunnelSnapshot,\n): string | undefined {\n const idx = pages.findIndex((p) => p.key === currentKey)\n if (idx === -1) return undefined\n const routed = resolveRoute(pages[idx].meta?.next, s)\n if (routed) return routed\n return pages[idx + 1]?.key\n}\n\n/**\n * Every page a visitor *could* go to next from `currentKey` — all route targets\n * (regardless of gate, since we don't know which will pass) plus the linear next.\n * Used to **prefetch** the likely-next page chunks; not for navigation.\n */\nexport function outgoingKeys(pages: FlowPage[], currentKey: string): string[] {\n const idx = pages.findIndex((p) => p.key === currentKey)\n if (idx === -1) return []\n const out = new Set<string>()\n for (const route of pages[idx].meta?.next ?? []) out.add(route.to)\n const linear = pages[idx + 1]?.key\n if (linear) out.add(linear)\n return [...out]\n}\n\n/**\n * The number of pages on the path from `startKey` to a `finish` (or the end of\n * the flow) **given the current state** — the denominator for progress. It walks\n * the actual routing ({@link nextPage}) rather than counting all files, so a\n * branched funnel reaches 100% on whichever path the visitor's answers select.\n * Re-traced per navigation; a visited set guards against routing cycles.\n */\nexport function expectedPathLength(\n pages: FlowPage[],\n startKey: string,\n s: FunnelSnapshot,\n): number {\n const seen = new Set<string>()\n let key: string | undefined = startKey\n let count = 0\n while (key && !seen.has(key)) {\n seen.add(key)\n count++\n if (pages.find((p) => p.key === key)?.meta?.type === 'finish') break\n key = nextPage(pages, key, s)\n }\n return count\n}\n\n// ── Funnel definition ────────────────────────────────────\n\nexport interface FunnelLocales {\n default: string\n supported: string[]\n fallback?: string\n}\n\n/**\n * The funnel-level spine. Flow is mostly inferred from page file order + each\n * page's `next`; this holds the funnel-wide config. Stays **thin** — routing\n * lives co-located on pages (doc 06: \"funnel.ts stays thin\").\n */\nexport interface FunnelDefinition {\n id: string\n /**\n * The funnel's pages — order, type, and declarative routing — the source of truth the\n * web builder edits and the build generates `mount.tsx` from. Each `key` maps to a\n * `pages/<key>.tsx` component. Omit `pages` on hand-written funnels that still ship their\n * own `mount.tsx` (back-compat); new/generated funnels declare them here.\n */\n pages?: FunnelPage[]\n /** Which checkout provider the paywall/checkout uses. The build wires the driver. */\n checkout?: 'stripe' | 'paddle'\n /** `responses.*` runtime variables (default values). */\n responses?: Record<string, VariableConfig>\n /** `data.*` working/scratch variables (default values). */\n data?: Record<string, VariableConfig>\n /** Product ids from the platform catalog this funnel uses (prices resolved at render). */\n products?: string[]\n locales?: FunnelLocales\n /**\n * When `true`, the funnel shows the **geo-specific** currency/price the payment\n * provider resolves (Stripe `currency_options`, Paddle preview, Whop, SolidGate,\n * …). When `false` (default), a single fixed currency is used. AppFunnel does no\n * FX — this only toggles whether the platform asks the provider for the geo\n * price; the SDK just displays whatever resolved `{amount, currency}` it's handed.\n * (See docs/platform-v2 phase-3 §3.6.)\n */\n locationAwareCurrency?: boolean\n}\n\n/** Identity helper for a funnel definition. */\nexport function defineFunnel(def: FunnelDefinition): FunnelDefinition {\n return def\n}\n","/**\n * **Runtime page-level experiments** — A/B/n on a *single page inside one funnel*,\n * resolved at render time (phase-4). No funnel duplication: a slot (e.g. the page\n * `welcome`) has alternate versions filed as siblings (`welcome@b.tsx`), and the\n * runtime shows **one** of them per visitor, collapsing the losers out of the\n * linear flow. The flow keeps stable **slot keys** (the control's key); only the\n * rendered component swaps — so routing, the manifest, and analytics all key on the\n * slot, not the variant.\n *\n * **Where the wiring lives:** the *variant pages* are code (compiled into the\n * bundle). The *experiment wiring* — slot, variant→page map, weights, status — is\n * **operational data the platform owns** (a DB record, edited by the dashboard,\n * changed without recompiling the funnel). The SDK is storage-agnostic: the\n * platform hands the runtime experiment records to `FunnelProvider`, the same way\n * it hands in resolved product prices. This module just resolves them.\n *\n * **The only source-side marker** is the `@` in a variant file's key\n * (`welcome@b`): it tells the *build* (manifest, flow graph) that a page is an\n * off-flow variant of its slot, so the structure is correct without the DB. The\n * label/weight/experiment-id are not in source.\n *\n * Bucketing is a deterministic FNV-1a hash, **namespaced by experiment id** so\n * independent experiments bucket orthogonally (the basis for layering 100+ tests,\n * §4.3). The seed is the durable identity ({@link bucketingSeed}) — never the\n * session/fingerprint — so a returning visitor keeps their variant (landmine #4).\n * Stability across an *add-a-variant* edit is the platform's job: an experiment's\n * variant set is immutable while running (reweighting / adding an arm = a new\n * experiment version), so within one record the assignment never moves.\n *\n * This module is pure (no React, no I/O) → it unit-tests trivially and the\n * dashboard/server can reuse the same assignment math.\n */\n\nimport type { FunnelContext } from '../state/context'\n\n// ── Hashing ──────────────────────────────────────────────\n\n/**\n * FNV-1a 32-bit hash → unsigned int. Deterministic and isomorphic (no `crypto`,\n * runs the same in the browser, a worker, and a build). The same math v0 used,\n * kept because it's sound — only the *seed* changes (identity, not session).\n */\nexport function fnv1a(input: string): number {\n let h = 0x811c9dc5\n for (let i = 0; i < input.length; i++) {\n h ^= input.charCodeAt(i)\n // h *= 16777619, in uint32, via shift-adds.\n h = (h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24))) >>> 0\n }\n return h >>> 0\n}\n\n/** Map a seed string to a stable float in `[0, 1)`. */\nexport function hashToUnit(seed: string): number {\n return fnv1a(seed) / 0x1_0000_0000\n}\n\n/**\n * Pick a variant key by weight from a stable unit position `u ∈ [0,1)`. Variants\n * are walked in declaration order and the first whose cumulative weight band\n * contains `u` wins, so the same `u` always lands in the same variant.\n */\nexport function pickByWeight(weights: Record<string, number>, u: number): string {\n const entries = Object.entries(weights).filter(([, w]) => w > 0)\n if (entries.length === 0) return Object.keys(weights)[0] ?? ''\n const total = entries.reduce((sum, [, w]) => sum + w, 0)\n const target = u * total\n let acc = 0\n for (const [key, w] of entries) {\n acc += w\n if (target < acc) return key\n }\n return entries[entries.length - 1][0]\n}\n\n/**\n * Deterministically assign a visitor (`seed`) to a variant of one experiment.\n * The hash is **namespaced by `experimentId`** so two experiments on the same\n * traffic don't correlate (orthogonal layering).\n */\nexport function assignVariant(\n experimentId: string,\n seed: string,\n weights: Record<string, number>,\n): string {\n return pickByWeight(weights, hashToUnit(`${experimentId}:${seed}`))\n}\n\n// ── Seed + slot parsing ──────────────────────────────────\n\n/**\n * The stable bucketing seed: the signed `visitorId`, else the known `customerId`.\n * Never the session id or fingerprint (landmine #4). `null` when neither is known\n * — the caller then serves control instead of bucketing on nothing.\n *\n * visitorId-FIRST (not customerId-first) is deliberate: the customerId injected for an\n * anonymous visitor is minted server-side AFTER the first tracked event, so it is absent on\n * visit 1 and present on visit 2 — seeding on it would flip a returning visitor's arm and\n * count them in two arms of the same version. The signed `af_vid` cookie is the durable\n * identity that's stable from the first pageload, so it seeds; customerId is only the fallback\n * for cookieless SDK embeds. This matches the server-side FUNNEL split, which already seeds on\n * the verified visitorId (resolve.ts). (Cross-device stable bucketing for IDENTIFIED customers\n * is P3's IdentitySDK job, not this anonymous path.)\n */\nexport function bucketingSeed(context: FunnelContext): string | null {\n return context.identity.visitorId ?? context.identity.customerId ?? null\n}\n\n/** Split a page key into its slot + optional variant: `welcome@b` → `{ slot, variant: 'b' }`. */\nexport function parseSlotKey(key: string): { slot: string; variant?: string } {\n const at = key.indexOf('@')\n return at === -1 ? { slot: key } : { slot: key.slice(0, at), variant: key.slice(at + 1) }\n}\n\n/** True if a page key is an off-flow variant (`welcome@b`), by the source-side `@` marker. */\nexport function isVariantKey(key: string): boolean {\n return parseSlotKey(key).variant !== undefined\n}\n\n// ── Runtime experiment records (platform/DB-sourced) ─────\n\n/** One arm of an experiment: which page renders, at what weight. */\nexport interface ExperimentVariant {\n /** The page key this arm renders (the slot's control, or a `@variant` sibling). */\n page: string\n /** Relative weight in the split (need not sum to 100; zero = paused arm). */\n weight: number\n}\n\n/**\n * A runtime experiment record — the operational wiring the **platform** owns and\n * hands to the SDK (not authored in the funnel source). The variant *pages* are\n * code in the bundle; this is the live config over them.\n */\nexport interface RuntimeExperiment {\n id: string\n /** The page key holding the flow position (the anchor / control). */\n slot: string\n /** label → arm. One arm's `page` is the `slot` (control); others are `@variant` siblings. */\n variants: Record<string, ExperimentVariant>\n /** Lifecycle. Absent = running. */\n status?: 'running' | 'paused' | 'stopped'\n /** When stopped, the variant label everyone sees until promotion bakes it into the slot. */\n winner?: string\n /** Primary metric (analytics only; not used for resolution). */\n metric?: string\n /** Platform record version (immutable-while-running; reweight = new version). Rides on\n * exposure events so results key on (experimentId, version, variant). */\n version?: number\n}\n\n// ── Flow resolution ──────────────────────────────────────\n\nexport interface ExperimentResolution {\n /** experiment id → assigned variant label. */\n assignments: Record<string, string>\n /** Page keys forming the effective linear flow (variant siblings removed), in order. */\n activeKeys: string[]\n /** Slot key → the page key whose component should render there. */\n render: Record<string, string>\n /** Variant page key → its slot key. For remapping a route that targets a variant. */\n slotOf: Record<string, string>\n /** Slot key → the experiment id it hosts (drives the exposure event). */\n experimentOf: Record<string, string>\n /** Experiment id → platform record version (rides on exposure events). */\n versionOf: Record<string, number>\n}\n\n/**\n * Weights map (label → weight) for one experiment's arms. Exported (via the pure\n * manifest entry) so the server-side FUNNEL split (renderer resolve) projects arms\n * the same way before calling {@link assignVariant}, instead of re-inlining it.\n */\nexport function weightsOf(variants: Record<string, { weight: number }>): Record<string, number> {\n const out: Record<string, number> = {}\n for (const [label, v] of Object.entries(variants)) out[label] = v.weight\n return out\n}\n\n/** Effective lifecycle — an absent `status` means running. */\nfunction statusOf(exp: RuntimeExperiment): 'running' | 'paused' | 'stopped' {\n return exp.status ?? 'running'\n}\n\n/**\n * Resolve which page version each experiment slot shows for this visitor, and\n * which page keys remain in the linear flow.\n *\n * Variant pages are detected structurally by the `@` in their key, so they\n * collapse out of the linear flow even for a slot with no active experiment.\n * For each *running* experiment the visitor is bucketed (stable on `seed`); a\n * *stopped* one serves its `winner`; a *paused* one (or no seed) serves the slot.\n */\nexport function resolveExperiments(\n pages: { key: string }[],\n experiments: RuntimeExperiment[],\n seed: string | null,\n): ExperimentResolution {\n const assignments: Record<string, string> = {}\n const render: Record<string, string> = {}\n const experimentOf: Record<string, string> = {}\n const versionOf: Record<string, number> = {}\n // Servable-arm guard: an experiment record can outlive the build it points at (a rollback,\n // a running-reweight to a never-published page, an AI edit that deletes a variant file). If\n // the assigned arm's page isn't in THIS bundle, serving it renders a blank step AND would\n // still record an exposure — so fall back to the slot's control and record NOTHING for the\n // experiment (mirrors the FUNNEL-split candidate check in resolve.ts).\n const pageKeys = new Set(pages.map((p) => p.key))\n\n for (const exp of experiments) {\n const status = statusOf(exp)\n let label: string\n if (status === 'stopped' && exp.winner) label = exp.winner\n else if (status === 'running' && seed) label = assignVariant(exp.id, seed, weightsOf(exp.variants))\n else continue // paused, or no seed → the slot renders its own (control) page\n\n const arm = exp.variants[label]\n if (!arm) continue\n if (!pageKeys.has(arm.page) || !pageKeys.has(exp.slot)) continue // arm/slot not in the bundle → serve control, no exposure\n assignments[exp.id] = label\n experimentOf[exp.slot] = exp.id\n if (exp.version !== undefined) versionOf[exp.id] = exp.version\n if (arm.page !== exp.slot) render[exp.slot] = arm.page // swap only when a non-control arm won\n }\n\n // Variant siblings (`@`) never stand alone in the linear flow.\n const slotOf: Record<string, string> = {}\n const activeKeys: string[] = []\n for (const p of pages) {\n const { slot, variant } = parseSlotKey(p.key)\n if (variant !== undefined) slotOf[p.key] = slot\n else activeKeys.push(p.key)\n }\n\n return { assignments, activeKeys, render, slotOf, experimentOf, versionOf }\n}\n\n// ── Validation ───────────────────────────────────────────\n\n/** One referential problem with an experiment record. */\nexport interface ExperimentIssue {\n /** The offending experiment id (`'*'` for a cross-experiment issue). */\n experimentId: string\n code:\n | 'slot_missing' // slot is not a page in the funnel\n | 'slot_is_variant' // slot points at an `@variant`, not a real flow page\n | 'slot_conflict' // two experiments target the same slot\n | 'duplicate_id' // two experiments share an id (bucketing namespace collision)\n | 'variant_page_missing' // a variant references a page that doesn't exist (dangling)\n | 'variant_slot_mismatch' // a variant's `@`-page belongs to a different slot\n | 'no_control' // no arm renders the slot itself (the baseline)\n | 'no_traffic' // every arm has weight ≤ 0\n | 'negative_weight' // an arm has a negative weight\n | 'bad_winner' // a stopped experiment's winner isn't one of its variants\n | 'single_arm' // only one variant — nothing to test (warning)\n | 'orphan_variant' // a `@variant` page no running experiment renders (warning)\n message: string\n}\n\nexport interface ExperimentValidation {\n ok: boolean\n errors: ExperimentIssue[]\n warnings: ExperimentIssue[]\n}\n\n/**\n * Cross-check experiment records against the funnel's pages — the publish-time /\n * experiment-edit gate that catches the dangling references the runtime can't see\n * (the experiment wiring lives in the DB; the pages live in the build, so they can\n * drift). Pure: hand it the runtime experiments + the funnel's page keys (e.g.\n * `manifest.pages`).\n *\n * Errors block; warnings inform. `orphan_variant` is a warning, not an error — a\n * `@variant` file with no running experiment is dead-but-harmless (a paused test, or\n * one not wired yet).\n */\nexport function validateExperiments(\n experiments: RuntimeExperiment[],\n pages: { key: string }[],\n): ExperimentValidation {\n const errors: ExperimentIssue[] = []\n const warnings: ExperimentIssue[] = []\n const pageKeys = new Set(pages.map((p) => p.key))\n\n const seenIds = new Set<string>()\n const slotOwner = new Map<string, string>()\n\n for (const exp of experiments) {\n const err = (code: ExperimentIssue['code'], message: string) =>\n errors.push({ experimentId: exp.id, code, message })\n const warn = (code: ExperimentIssue['code'], message: string) =>\n warnings.push({ experimentId: exp.id, code, message })\n\n if (seenIds.has(exp.id)) err('duplicate_id', `Experiment id \"${exp.id}\" is used more than once.`)\n seenIds.add(exp.id)\n\n // Slot must be a real flow page (not missing, not itself a variant).\n if (!pageKeys.has(exp.slot)) err('slot_missing', `Slot \"${exp.slot}\" is not a page in the funnel.`)\n if (isVariantKey(exp.slot)) err('slot_is_variant', `Slot \"${exp.slot}\" is a variant page; a slot must be a real flow page.`)\n if (slotOwner.has(exp.slot)) {\n err('slot_conflict', `Slot \"${exp.slot}\" is already targeted by experiment \"${slotOwner.get(exp.slot)}\".`)\n } else {\n slotOwner.set(exp.slot, exp.id)\n }\n\n const arms = Object.entries(exp.variants)\n if (arms.length < 2) warn('single_arm', `Experiment \"${exp.id}\" has fewer than two variants — nothing to test.`)\n\n let hasControl = false\n let totalWeight = 0\n for (const [label, arm] of arms) {\n if (!pageKeys.has(arm.page)) {\n err('variant_page_missing', `Variant \"${label}\" references page \"${arm.page}\", which doesn't exist.`)\n }\n if (arm.page === exp.slot) hasControl = true\n else if (isVariantKey(arm.page) && parseSlotKey(arm.page).slot !== exp.slot) {\n err('variant_slot_mismatch', `Variant \"${label}\" page \"${arm.page}\" belongs to a different slot than \"${exp.slot}\".`)\n }\n if (arm.weight < 0) err('negative_weight', `Variant \"${label}\" has a negative weight (${arm.weight}).`)\n totalWeight += arm.weight\n }\n if (!hasControl) err('no_control', `No variant of \"${exp.id}\" renders the slot \"${exp.slot}\" itself (the control/baseline).`)\n if (totalWeight <= 0) err('no_traffic', `Experiment \"${exp.id}\" has no positive weight — no traffic would enter it.`)\n\n if (statusOf(exp) === 'stopped' && exp.winner && !exp.variants[exp.winner]) {\n err('bad_winner', `Stopped experiment \"${exp.id}\" names winner \"${exp.winner}\", which isn't one of its variants.`)\n }\n }\n\n // A `@variant` page that no running experiment renders is dead (but harmless).\n const running = new Set(experiments.filter((e) => statusOf(e) === 'running').flatMap((e) =>\n Object.values(e.variants).map((v) => v.page),\n ))\n for (const p of pages) {\n if (isVariantKey(p.key) && !running.has(p.key)) {\n warnings.push({ experimentId: '*', code: 'orphan_variant', message: `Variant page \"${p.key}\" is rendered by no running experiment.` })\n }\n }\n\n return { ok: errors.length === 0, errors, warnings }\n}\n","/**\n * The **manifest** — the declarative IR a funnel project compiles to (doc 06).\n * It's the machine-readable artifact the platform operates on without executing\n * the funnel: the dashboard draws the flow graph from it, analytics binds to its\n * **stable ids**, validation runs against it, and the renderer reads it to resolve\n * routing.\n *\n * `compileManifest` evaluates the spine (the funnel config + each page's co-located\n * `meta`) into that IR. It's pure — no I/O — so it unit-tests trivially. **It runs\n * server-side**, as the IR step of the publish build over the canonical files in our\n * storage (R2). The client/CLI never produces the authoritative manifest — R2 is the\n * source of truth, the server compiles. (A local dev preview may run it for itself,\n * but that output is never published.)\n *\n * Edge model mirrors the runtime ({@link ../flow/spine}.`nextPage`): a page's\n * `meta.next` routes become **branch** edges (with a serialized condition — a\n * declarative {@link Condition}, or an opaque marker for a code predicate), and the\n * implicit file-order fall-through becomes a **linear** edge (unless an\n * unconditional route already covers it, or the page is a `finish`).\n *\n * Experiments are **not** compiled here. A variant page is recognised structurally\n * by the `@` in its key (`welcome@b`) and tagged `variantOf` + collapsed out of the\n * linear flow; the experiment *wiring* (labels/weights/status) is operational data\n * the platform owns and overlays at render time, not part of the funnel build.\n *\n * Stable ids: the page **key** is the source anchor, so `id === key` here. The\n * durable analytics id is the semantic page name (a slot survives a winner being\n * promoted into it); transient `@variant` pages only ever key analytics by\n * `(experimentId, variant)` in immutable results — a platform concern.\n */\n\nimport type {\n Condition,\n FunnelDefinition,\n FunnelLocales,\n Gate,\n PageMeta,\n PageType,\n} from '../flow/spine'\nimport { isVariantKey, parseSlotKey } from '../flow/experiments'\n\nexport interface ManifestPage {\n id: string\n key: string\n type: PageType\n slug: string\n index: number\n /**\n * Set on a variant page (`welcome@b`) to its **slot** (`welcome`) — a structural\n * marker that this node is an off-flow alternate, not a linear step. The\n * experiment *wiring* (label/weight/id) is operational data the platform owns,\n * not the manifest.\n */\n variantOf?: string\n}\n\nexport type EdgeCondition =\n | { kind: 'declarative'; condition: Condition }\n /** A code predicate — opaque body; `label` may be supplied later by AST parsing. */\n | { kind: 'predicate'; label?: string }\n\nexport interface ManifestEdge {\n from: string\n to: string\n kind: 'branch' | 'linear'\n /** Absent = unconditional (a fallback route, or the linear fall-through). */\n condition?: EdgeCondition\n}\n\nexport interface ManifestValidation {\n /**\n * `ok` = none of the hard errors: the original set (dead ends, unreachable\n * pages, bad targets, missing email capture) AND the structural errors added\n * later (duplicate keys, orphan variants, no reachable finish). Slug\n * collisions are a **warning** — reported but not folded into `ok` (slugs are\n * display/URL sugar; keys are the routing identity).\n */\n ok: boolean\n /** Page ids you can't proceed from (and that aren't a `finish`). */\n deadEnds: string[]\n /** Page ids not reachable from the start. */\n unreachable: string[]\n /** Routes whose target page key doesn't exist. */\n badTargets: { from: string; to: string }[]\n /** True if the funnel never writes `user.email` (the mandatory identity step). */\n missingEmailCapture: boolean\n /** Page keys that appear more than once (keys are the stable ids — hard error). */\n duplicateKeys: string[]\n /** Flow pages sharing a slug (URL ambiguity — warning, not folded into `ok`). */\n slugCollisions: { slug: string; pages: string[] }[]\n /** Variant pages (`x@b`) whose slot (`x`) doesn't exist as a flow page (hard error). */\n orphanVariants: string[]\n /**\n * True when no `finish` page is reachable from the start — e.g. the flow loops\n * (a cycle with no exit) or every path stalls before a terminal page. Dead ends\n * catch pages with no outgoing edge; this catches funnels that *never end* even\n * though every page can proceed. Hard error.\n */\n noReachableFinish: boolean\n}\n\nexport interface FunnelManifest {\n id: string\n pages: ManifestPage[]\n flow: { start: string | null; nodes: string[]; edges: ManifestEdge[] }\n products: string[]\n locales?: FunnelLocales\n validation: ManifestValidation\n}\n\nexport interface CompileInput {\n funnel: FunnelDefinition\n /** Pages in file order (the default flow); `meta` holds type/slug/next. */\n pages: { key: string; meta?: PageMeta }[]\n}\n\n/**\n * A **variant** node — a page filed with an `@variant` key (e.g. `welcome@b`).\n * It's an alternate of its slot, not a step in the linear spine, so it gets no\n * linear edge and is excluded from dead-end/reachability checks (the runtime\n * resolves it by bucketing, not by an edge). The `@` is the only source-side\n * signal; the experiment wiring is the platform's operational data. The slot's\n * control (no `@`) stays the flow node. Mirrors {@link resolveExperiments}.\n */\nfunction isVariantNode(page: { key: string }): boolean {\n return isVariantKey(page.key)\n}\n\n/** Serialize a route gate for the manifest (declarative → data; predicate → opaque). */\nfunction serializeGate(when: Gate | undefined): EdgeCondition | undefined {\n if (!when) return undefined\n if (typeof when === 'function') return { kind: 'predicate' }\n return { kind: 'declarative', condition: when }\n}\n\n/** Compile a funnel's spine into its declarative {@link FunnelManifest} IR. */\nexport function compileManifest(input: CompileInput): FunnelManifest {\n const { funnel, pages } = input\n const keys = new Set(pages.map((p) => p.key))\n\n const manifestPages: ManifestPage[] = pages.map((p, index) => ({\n id: p.key,\n key: p.key,\n type: p.meta?.type ?? 'default',\n slug: p.meta?.slug ?? p.key,\n index,\n variantOf: isVariantKey(p.key) ? parseSlotKey(p.key).slot : undefined,\n }))\n\n const edges: ManifestEdge[] = []\n const badTargets: { from: string; to: string }[] = []\n\n // The next page in file order that is a real flow node (skip `@variant` siblings).\n const nextAnchor = (from: number) => {\n for (let j = from + 1; j < pages.length; j++) {\n if (!isVariantNode(pages[j])) return pages[j]\n }\n return undefined\n }\n\n pages.forEach((p, i) => {\n // Variant nodes are alternates of their slot, resolved at runtime by bucketing\n // — they carry no flow edges (routing lives on the slot's control/anchor).\n if (isVariantNode(p)) return\n\n const routes = p.meta?.next ?? []\n let hasUnconditional = false\n for (const route of routes) {\n if (!keys.has(route.to)) {\n badTargets.push({ from: p.key, to: route.to })\n continue\n }\n edges.push({ from: p.key, to: route.to, kind: 'branch', condition: serializeGate(route.when) })\n if (!route.when) hasUnconditional = true\n }\n // Linear fall-through, unless an unconditional route already covers it,\n // this is a `finish` page, or there is no next page.\n const next = nextAnchor(i)\n if (!hasUnconditional && next && p.meta?.type !== 'finish') {\n edges.push({ from: p.key, to: next.key, kind: 'linear' })\n }\n })\n\n // The start is the first real flow node (a leading variant can't be the entry).\n const startPage = pages.find((p) => !isVariantNode(p))\n const start = startPage?.key ?? null\n const variantKeys = new Set(pages.filter(isVariantNode).map((p) => p.key))\n const validation = validate(manifestPages, edges, badTargets, start, variantKeys)\n\n return {\n id: funnel.id,\n pages: manifestPages,\n flow: { start, nodes: manifestPages.map((p) => p.id), edges },\n products: funnel.products ?? [],\n locales: funnel.locales,\n validation,\n }\n}\n\nfunction validate(\n pages: ManifestPage[],\n edges: ManifestEdge[],\n badTargets: { from: string; to: string }[],\n start: string | null,\n variantKeys: Set<string>,\n): ManifestValidation {\n const outgoing = new Map<string, number>()\n for (const e of edges) outgoing.set(e.from, (outgoing.get(e.from) ?? 0) + 1)\n\n // Variant nodes are resolved by bucketing, not edges — they're neither dead\n // ends nor unreachable in the flow-graph sense, so exclude them from both.\n const deadEnds = pages\n .filter((p) => p.type !== 'finish' && !variantKeys.has(p.id) && !(outgoing.get(p.id)! > 0))\n .map((p) => p.id)\n\n // Reachability from the start over the edge set.\n const reachable = new Set<string>()\n if (start) {\n const adj = new Map<string, string[]>()\n for (const e of edges) {\n const list = adj.get(e.from)\n if (list) list.push(e.to)\n else adj.set(e.from, [e.to])\n }\n const stack = [start]\n while (stack.length) {\n const id = stack.pop()!\n if (reachable.has(id)) continue\n reachable.add(id)\n for (const to of adj.get(id) ?? []) stack.push(to)\n }\n }\n const unreachable = pages\n .filter((p) => !variantKeys.has(p.id) && !reachable.has(p.id))\n .map((p) => p.id)\n\n // The funnel must capture user.email somewhere (doc 06). We can see the typed\n // `email-capture` page; deeper detection (any user.email write) is a build-time\n // AST concern. Treat presence of an `email-capture` page as satisfying it.\n const missingEmailCapture = !pages.some((p) => p.type === 'email-capture')\n\n // Duplicate page keys — keys are the stable analytics/routing ids, so two\n // pages sharing one is a hard error (edges/experiments become ambiguous).\n const seen = new Set<string>()\n const duplicateKeys: string[] = []\n for (const p of pages) {\n if (seen.has(p.key) && !duplicateKeys.includes(p.key)) duplicateKeys.push(p.key)\n seen.add(p.key)\n }\n\n // Slug collisions among FLOW pages (variants never route by their own slug —\n // they render under their slot's URL). Warning: keys stay unambiguous.\n const bySlug = new Map<string, string[]>()\n for (const p of pages) {\n if (variantKeys.has(p.id)) continue\n const list = bySlug.get(p.slug)\n if (list) list.push(p.id)\n else bySlug.set(p.slug, [p.id])\n }\n const slugCollisions = [...bySlug.entries()]\n .filter(([, ids]) => ids.length > 1)\n .map(([slug, ids]) => ({ slug, pages: ids }))\n\n // Orphan variants — a `x@b` page whose slot `x` doesn't exist as a flow page.\n // The runtime could never bucket into it (no anchor in the flow) → hard error.\n const flowKeys = new Set(pages.filter((p) => !variantKeys.has(p.id)).map((p) => p.key))\n const orphanVariants = pages\n .filter((p) => p.variantOf !== undefined && !flowKeys.has(p.variantOf))\n .map((p) => p.id)\n\n // A funnel must be able to END: at least one `finish` page reachable from the\n // start. Catches cycles with no exit (every page proceeds — so no dead end —\n // but no path ever terminates).\n const noReachableFinish =\n start !== null && !pages.some((p) => p.type === 'finish' && reachable.has(p.id))\n\n return {\n // Original hard errors AND the structural ones; slug collisions stay a warning.\n ok:\n deadEnds.length === 0 &&\n unreachable.length === 0 &&\n badTargets.length === 0 &&\n !missingEmailCapture &&\n duplicateKeys.length === 0 &&\n orphanVariants.length === 0 &&\n !noReachableFinish,\n deadEnds,\n unreachable,\n badTargets,\n missingEmailCapture,\n duplicateKeys,\n slugCollisions,\n orphanVariants,\n noReachableFinish,\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/drivers/wire.ts"],"names":[],"mappings":"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","file":"chunk-UIR6TGEW.js","sourcesContent":["/**\n * Shared wire plumbing for the checkout drivers — the CLIENT half of the v2\n * checkout contract (commerce/checkout-api.ts). Lives under drivers/ (not\n * commerce/) on purpose: it ships only inside the `driver-*` subpath entries,\n * so the core funnel entry never bundles a checkout fetch — the core SDK talks\n * to a {@link CheckoutDriver}, never to the wire.\n *\n * Only `import type` from commerce/checkout(.tsx) here — a runtime import would\n * drag the React checkout primitives into every driver chunk. The one runtime\n * dependency is the pure error taxonomy (capabilities.ts).\n */\nimport type {\n CheckoutCompleteRequest,\n CheckoutCompleteResponse,\n CheckoutSessionRequest,\n CheckoutSessionResponse,\n CheckoutSettlement,\n} from '../commerce/checkout-api'\nimport type { CheckoutDriverContext, CheckoutRequest, CheckoutResult } from '../commerce/checkout'\nimport { checkoutError, type CheckoutError } from '../commerce/capabilities'\n\nfunction endpoint(ctx: CheckoutDriverContext, path = ''): string {\n const base = ctx.apiBase.replace(/\\/+$/, '')\n return `${base}/campaign/${encodeURIComponent(ctx.campaignId)}/v2/checkout${path}`\n}\n\nasync function postJson<T>(url: string, body: unknown): Promise<T> {\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n })\n // The contract speaks in `step` responses, not HTTP errors — a non-2xx means the\n // request never reached the checkout service properly (proxy, deploy, outage).\n if (!res.ok) throw checkoutError('processing_error', `checkout request failed (HTTP ${res.status})`)\n return (await res.json()) as T\n}\n\n/** Normalize any thrown value into the taxonomy (network drops → `processing_error`). */\nexport function toCheckoutError(e: unknown): CheckoutError {\n if (e && typeof e === 'object' && 'category' in e && 'message' in e && 'retryable' in e) {\n return e as CheckoutError\n }\n return checkoutError('processing_error', e instanceof Error ? e.message : 'Checkout request failed')\n}\n\n/**\n * Open a checkout attempt: `POST {apiBase}/campaign/{campaignId}/v2/checkout`.\n * The correlationId is client-minted per ATTEMPT (idempotency key + the id the\n * server stamps into provider metadata) — mint it once, reuse it on retries.\n */\nexport function postSession(\n ctx: CheckoutDriverContext,\n req: CheckoutRequest,\n correlationId: string,\n): Promise<CheckoutSessionResponse> {\n const body: CheckoutSessionRequest = {\n funnelId: ctx.funnelId,\n mode: ctx.mode,\n productKey: req.product,\n intent: req.intent,\n kind: req.kind,\n // null (not absent) = explicit off-session — the server validates surfaces\n // against the capability matrix, so never let one default in transit.\n surface: req.surface ?? null,\n sessionId: ctx.sessionId ?? undefined,\n visitorId: ctx.visitorId ?? undefined,\n // Buyer email collected by the funnel (user.email) — Stripe Customer creation,\n // receipts, Paddle prefill. Injected at attempt time by the FunnelProvider seam.\n email: req.email,\n correlationId,\n }\n return postJson<CheckoutSessionResponse>(endpoint(ctx), body)\n}\n\n/** Ask the server to VERIFY with the provider and write the money facts (idempotent). */\nexport function postComplete(\n ctx: CheckoutDriverContext,\n correlationId: string,\n providerRef?: string,\n): Promise<CheckoutCompleteResponse> {\n const body: CheckoutCompleteRequest = { providerRef }\n return postJson<CheckoutCompleteResponse>(\n endpoint(ctx, `/${encodeURIComponent(correlationId)}/complete`),\n body,\n )\n}\n\n/** Server settlement → the SDK's CheckoutResult (eventId passes through for pixel dedup). */\nexport function settlementToResult(s: CheckoutSettlement): CheckoutResult {\n return { ok: true, amountMinor: s.amountMinor, currency: s.currency, eventId: s.eventId }\n}\n\nexport interface CompletePollOptions {\n providerRef?: string\n /** Total complete attempts before giving up (default 5). */\n attempts?: number\n /** Delay between attempts (default 2000ms). */\n intervalMs?: number\n /** Error message when the provider hasn't confirmed inside the window (MoR/async). */\n timeoutMessage?: string\n}\n\nconst sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms))\n\n/**\n * Complete with a bounded poll. Sync providers (Stripe) settle on the first\n * attempt — the server verifies against the provider inline. MoR/async providers\n * (Paddle) settle via webhook, so `pending` is normal: re-poll a few times, then\n * stop holding the UI hostage — the charge may still land server-side, which is\n * why the timeout error is `retryable` and the message should say the receipt\n * can still arrive (the server's money facts don't depend on this browser).\n */\nexport async function completeWithPoll(\n ctx: CheckoutDriverContext,\n correlationId: string,\n opts: CompletePollOptions = {},\n): Promise<CheckoutResult> {\n const attempts = opts.attempts ?? 5\n const intervalMs = opts.intervalMs ?? 2000\n try {\n for (let i = 0; i < attempts; i++) {\n const res = await postComplete(ctx, correlationId, opts.providerRef)\n if (res.step === 'settled') return settlementToResult(res.result)\n if (res.step === 'failed') return { ok: false, error: res.error }\n if (i < attempts - 1) await sleep(intervalMs)\n }\n } catch (e) {\n return { ok: false, error: toCheckoutError(e) }\n }\n return {\n ok: false,\n error: checkoutError(\n 'processing_error',\n opts.timeoutMessage ?? 'The payment has not been confirmed yet — please check back shortly.',\n // `completion_timeout` marks \"the poll ran out while the attempt was still\n // pending\" — drivers key double-charge guards on it (the attempt may still\n // settle server-side; it was never refused).\n { retryable: true, declineCode: 'completion_timeout' },\n ),\n }\n}\n\n/**\n * The `redirect` step hand-off: navigate to the provider-hosted page and return a\n * promise that settles ONLY if the buyer comes back to this very page. The page\n * is leaving — resolving early would fire `purchase.complete`/navigation for a\n * charge that hasn't happened, and rejecting would flash an error during the\n * (successful) hand-off — so while the navigation proceeds the promise stays\n * pending and `useCheckout` stays in `loading`, the truthful UI for a page\n * mid-navigation. The hosted page's SUCCESS return URL re-enters the funnel with\n * `af_checkout={correlationId}` (a fresh load — this promise is gone; the SDK's\n * resume leg settles the attempt).\n *\n * The one way THIS page comes back to life is a bfcache restore (browser Back\n * from the hosted page): `pageshow` with `persisted: true`. That is a buyer who\n * abandoned the hosted checkout — resolve `canceled` so the trigger re-enables\n * and inline redirect placeholders recover instead of spinning forever.\n */\nexport function leaveForRedirect(url: string): Promise<CheckoutResult> {\n return new Promise<CheckoutResult>((resolve) => {\n if (typeof window !== 'undefined') {\n const onPageshow = (event: PageTransitionEvent) => {\n if (!event.persisted) return\n window.removeEventListener('pageshow', onPageshow)\n resolve({\n ok: false,\n error: checkoutError('canceled', 'Returned from the hosted checkout without completing'),\n })\n }\n window.addEventListener('pageshow', onPageshow)\n }\n window.location.assign(url)\n })\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/drivers/wire.ts"],"names":["checkoutError"],"mappings":"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","file":"chunk-VQOD2Z6Q.cjs","sourcesContent":["/**\n * Shared wire plumbing for the checkout drivers — the CLIENT half of the v2\n * checkout contract (commerce/checkout-api.ts). Lives under drivers/ (not\n * commerce/) on purpose: it ships only inside the `driver-*` subpath entries,\n * so the core funnel entry never bundles a checkout fetch — the core SDK talks\n * to a {@link CheckoutDriver}, never to the wire.\n *\n * Only `import type` from commerce/checkout(.tsx) here — a runtime import would\n * drag the React checkout primitives into every driver chunk. The one runtime\n * dependency is the pure error taxonomy (capabilities.ts).\n */\nimport type {\n CheckoutCompleteRequest,\n CheckoutCompleteResponse,\n CheckoutSessionRequest,\n CheckoutSessionResponse,\n CheckoutSettlement,\n} from '../commerce/checkout-api'\nimport type { CheckoutDriverContext, CheckoutRequest, CheckoutResult } from '../commerce/checkout'\nimport { checkoutError, type CheckoutError } from '../commerce/capabilities'\n\nfunction endpoint(ctx: CheckoutDriverContext, path = ''): string {\n const base = ctx.apiBase.replace(/\\/+$/, '')\n return `${base}/campaign/${encodeURIComponent(ctx.campaignId)}/v2/checkout${path}`\n}\n\nasync function postJson<T>(url: string, body: unknown): Promise<T> {\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n })\n // The contract speaks in `step` responses, not HTTP errors — a non-2xx means the\n // request never reached the checkout service properly (proxy, deploy, outage).\n if (!res.ok) throw checkoutError('processing_error', `checkout request failed (HTTP ${res.status})`)\n return (await res.json()) as T\n}\n\n/** Normalize any thrown value into the taxonomy (network drops → `processing_error`). */\nexport function toCheckoutError(e: unknown): CheckoutError {\n if (e && typeof e === 'object' && 'category' in e && 'message' in e && 'retryable' in e) {\n return e as CheckoutError\n }\n return checkoutError('processing_error', e instanceof Error ? e.message : 'Checkout request failed')\n}\n\n/**\n * Open a checkout attempt: `POST {apiBase}/campaign/{campaignId}/v2/checkout`.\n * The correlationId is client-minted per ATTEMPT (idempotency key + the id the\n * server stamps into provider metadata) — mint it once, reuse it on retries.\n */\nexport function postSession(\n ctx: CheckoutDriverContext,\n req: CheckoutRequest,\n correlationId: string,\n): Promise<CheckoutSessionResponse> {\n const body: CheckoutSessionRequest = {\n funnelId: ctx.funnelId,\n mode: ctx.mode,\n productKey: req.product,\n intent: req.intent,\n kind: req.kind,\n // null (not absent) = explicit off-session — the server validates surfaces\n // against the capability matrix, so never let one default in transit.\n surface: req.surface ?? null,\n sessionId: ctx.sessionId ?? undefined,\n visitorId: ctx.visitorId ?? undefined,\n // Buyer email collected by the funnel (user.email) — Stripe Customer creation,\n // receipts, Paddle prefill. Injected at attempt time by the FunnelProvider seam.\n email: req.email,\n correlationId,\n }\n return postJson<CheckoutSessionResponse>(endpoint(ctx), body)\n}\n\n/** Ask the server to VERIFY with the provider and write the money facts (idempotent). */\nexport function postComplete(\n ctx: CheckoutDriverContext,\n correlationId: string,\n providerRef?: string,\n): Promise<CheckoutCompleteResponse> {\n const body: CheckoutCompleteRequest = { providerRef }\n return postJson<CheckoutCompleteResponse>(\n endpoint(ctx, `/${encodeURIComponent(correlationId)}/complete`),\n body,\n )\n}\n\n/** Server settlement → the SDK's CheckoutResult (eventId passes through for pixel dedup). */\nexport function settlementToResult(s: CheckoutSettlement): CheckoutResult {\n return { ok: true, amountMinor: s.amountMinor, currency: s.currency, eventId: s.eventId }\n}\n\nexport interface CompletePollOptions {\n providerRef?: string\n /** Total complete attempts before giving up (default 5). */\n attempts?: number\n /** Delay between attempts (default 2000ms). */\n intervalMs?: number\n /** Error message when the provider hasn't confirmed inside the window (MoR/async). */\n timeoutMessage?: string\n}\n\nconst sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms))\n\n/**\n * Complete with a bounded poll. Sync providers (Stripe) settle on the first\n * attempt — the server verifies against the provider inline. MoR/async providers\n * (Paddle) settle via webhook, so `pending` is normal: re-poll a few times, then\n * stop holding the UI hostage — the charge may still land server-side, which is\n * why the timeout error is `retryable` and the message should say the receipt\n * can still arrive (the server's money facts don't depend on this browser).\n */\nexport async function completeWithPoll(\n ctx: CheckoutDriverContext,\n correlationId: string,\n opts: CompletePollOptions = {},\n): Promise<CheckoutResult> {\n const attempts = opts.attempts ?? 5\n const intervalMs = opts.intervalMs ?? 2000\n try {\n for (let i = 0; i < attempts; i++) {\n const res = await postComplete(ctx, correlationId, opts.providerRef)\n if (res.step === 'settled') return settlementToResult(res.result)\n if (res.step === 'failed') return { ok: false, error: res.error }\n if (i < attempts - 1) await sleep(intervalMs)\n }\n } catch (e) {\n return { ok: false, error: toCheckoutError(e) }\n }\n return {\n ok: false,\n error: checkoutError(\n 'processing_error',\n opts.timeoutMessage ?? 'The payment has not been confirmed yet — please check back shortly.',\n // `completion_timeout` marks \"the poll ran out while the attempt was still\n // pending\" — drivers key double-charge guards on it (the attempt may still\n // settle server-side; it was never refused).\n { retryable: true, declineCode: 'completion_timeout' },\n ),\n }\n}\n\n/**\n * The `redirect` step hand-off: navigate to the provider-hosted page and return a\n * promise that settles ONLY if the buyer comes back to this very page. The page\n * is leaving — resolving early would fire `purchase.complete`/navigation for a\n * charge that hasn't happened, and rejecting would flash an error during the\n * (successful) hand-off — so while the navigation proceeds the promise stays\n * pending and `useCheckout` stays in `loading`, the truthful UI for a page\n * mid-navigation. The hosted page's SUCCESS return URL re-enters the funnel with\n * `af_checkout={correlationId}` (a fresh load — this promise is gone; the SDK's\n * resume leg settles the attempt).\n *\n * The one way THIS page comes back to life is a bfcache restore (browser Back\n * from the hosted page): `pageshow` with `persisted: true`. That is a buyer who\n * abandoned the hosted checkout — resolve `canceled` so the trigger re-enables\n * and inline redirect placeholders recover instead of spinning forever.\n */\nexport function leaveForRedirect(url: string): Promise<CheckoutResult> {\n return new Promise<CheckoutResult>((resolve) => {\n if (typeof window !== 'undefined') {\n const onPageshow = (event: PageTransitionEvent) => {\n if (!event.persisted) return\n window.removeEventListener('pageshow', onPageshow)\n resolve({\n ok: false,\n error: checkoutError('canceled', 'Returned from the hosted checkout without completing'),\n })\n }\n window.addEventListener('pageshow', onPageshow)\n }\n window.location.assign(url)\n })\n}\n"]}