@xenia-martech/sdk-js 1.2.0-dev.7 → 1.2.0-dev.9

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 (96) hide show
  1. package/README.md +70 -6
  2. package/dist/{actions-35V37IT0.d.cts → actions--1_9Z8ig.d.cts} +13 -2
  3. package/dist/{actions-35V37IT0.d.ts → actions--1_9Z8ig.d.ts} +13 -2
  4. package/dist/{api-0IycTYJR.d.cts → api-DbUxT38T.d.ts} +1 -1
  5. package/dist/{api-COXWLL5L.d.ts → api-DgzJ5s-k.d.cts} +1 -1
  6. package/dist/chunk-DPXINHQR.cjs +34 -0
  7. package/dist/chunk-DPXINHQR.cjs.map +1 -0
  8. package/dist/chunk-FSJMHXS6.js +432 -0
  9. package/dist/chunk-FSJMHXS6.js.map +1 -0
  10. package/dist/chunk-GXJLU3RE.cjs +448 -0
  11. package/dist/chunk-GXJLU3RE.cjs.map +1 -0
  12. package/dist/{chunk-2DR5AVPX.cjs → chunk-I5EHLRSR.cjs} +11 -10
  13. package/dist/chunk-I5EHLRSR.cjs.map +1 -0
  14. package/dist/{chunk-LW5AEJ6K.js → chunk-MEULT5X6.js} +4 -241
  15. package/dist/chunk-MEULT5X6.js.map +1 -0
  16. package/dist/{chunk-GWRDZIVA.js → chunk-NDRRSRTU.js} +4 -3
  17. package/dist/chunk-NDRRSRTU.js.map +1 -0
  18. package/dist/chunk-RC46KXDM.js +32 -0
  19. package/dist/chunk-RC46KXDM.js.map +1 -0
  20. package/dist/{chunk-RWE2OCQL.cjs → chunk-SU25SELD.cjs} +4 -244
  21. package/dist/chunk-SU25SELD.cjs.map +1 -0
  22. package/dist/{chunk-WHGK55JP.cjs → chunk-UUIKUU5W.cjs} +5 -5
  23. package/dist/{chunk-WHGK55JP.cjs.map → chunk-UUIKUU5W.cjs.map} +1 -1
  24. package/dist/{chunk-QEYONKWY.js → chunk-UVTFGQR6.js} +3 -3
  25. package/dist/{chunk-QEYONKWY.js.map → chunk-UVTFGQR6.js.map} +1 -1
  26. package/dist/{chunk-OMMIRSPK.js → chunk-V4EGU3PM.js} +48 -11
  27. package/dist/chunk-V4EGU3PM.js.map +1 -0
  28. package/dist/{chunk-YLMMTKQL.cjs → chunk-WMGCQPHY.cjs} +52 -13
  29. package/dist/chunk-WMGCQPHY.cjs.map +1 -0
  30. package/dist/client/index.cjs +26 -10
  31. package/dist/client/index.cjs.map +1 -1
  32. package/dist/client/index.d.cts +2 -2
  33. package/dist/client/index.d.ts +2 -2
  34. package/dist/client/index.js +26 -10
  35. package/dist/client/index.js.map +1 -1
  36. package/dist/client/react/index.cjs +73 -15
  37. package/dist/client/react/index.cjs.map +1 -1
  38. package/dist/client/react/index.d.cts +9 -3
  39. package/dist/client/react/index.d.ts +9 -3
  40. package/dist/client/react/index.js +73 -15
  41. package/dist/client/react/index.js.map +1 -1
  42. package/dist/cms/contentful.cjs +7 -24
  43. package/dist/cms/contentful.cjs.map +1 -1
  44. package/dist/cms/contentful.d.cts +5 -4
  45. package/dist/cms/contentful.d.ts +5 -4
  46. package/dist/cms/contentful.js +4 -23
  47. package/dist/cms/contentful.js.map +1 -1
  48. package/dist/cms/contentstack.cjs +120 -0
  49. package/dist/cms/contentstack.cjs.map +1 -0
  50. package/dist/cms/contentstack.d.cts +41 -0
  51. package/dist/cms/contentstack.d.ts +41 -0
  52. package/dist/cms/contentstack.js +117 -0
  53. package/dist/cms/contentstack.js.map +1 -0
  54. package/dist/{consent-DRCV0N0d.d.ts → consent-Dtiqzz2B.d.cts} +71 -11
  55. package/dist/{consent-BgAETCkL.d.cts → consent-byYiDMFr.d.ts} +71 -11
  56. package/dist/{cookie-names-DU3MtcNG.d.ts → cookie-names-B4hda2rX.d.cts} +1 -1
  57. package/dist/{cookie-names-DFItnSLK.d.cts → cookie-names-DioZXuV0.d.ts} +1 -1
  58. package/dist/edge/index.cjs +122 -29
  59. package/dist/edge/index.cjs.map +1 -1
  60. package/dist/edge/index.d.cts +42 -6
  61. package/dist/edge/index.d.ts +42 -6
  62. package/dist/edge/index.js +92 -9
  63. package/dist/edge/index.js.map +1 -1
  64. package/dist/index.cjs +31 -31
  65. package/dist/index.d.cts +4 -5
  66. package/dist/index.d.ts +4 -5
  67. package/dist/index.js +2 -2
  68. package/dist/next/index.cjs +39 -34
  69. package/dist/next/index.cjs.map +1 -1
  70. package/dist/next/index.d.cts +2 -3
  71. package/dist/next/index.d.ts +2 -3
  72. package/dist/next/index.js +13 -8
  73. package/dist/next/index.js.map +1 -1
  74. package/dist/{types-DgVHS4Qw.d.ts → personalize-content-D6xeBI7-.d.cts} +150 -1
  75. package/dist/{types-DgVHS4Qw.d.cts → personalize-content-D6xeBI7-.d.ts} +150 -1
  76. package/dist/server/index.cjs +30 -30
  77. package/dist/server/index.d.cts +5 -6
  78. package/dist/server/index.d.ts +5 -6
  79. package/dist/server/index.js +5 -5
  80. package/dist/xenia-sdk.iife.js +3 -3
  81. package/dist/xenia-sdk.iife.js.map +1 -1
  82. package/dist/{xenia-server-Cf24mL0c.d.ts → xenia-server-BcgeUHX4.d.ts} +2 -3
  83. package/dist/{xenia-server-D-bx14lw.d.cts → xenia-server-lv6LtJZP.d.cts} +2 -3
  84. package/package.json +14 -1
  85. package/dist/chunk-2DR5AVPX.cjs.map +0 -1
  86. package/dist/chunk-BQ5PJCZ7.js +0 -179
  87. package/dist/chunk-BQ5PJCZ7.js.map +0 -1
  88. package/dist/chunk-GWRDZIVA.js.map +0 -1
  89. package/dist/chunk-LW5AEJ6K.js.map +0 -1
  90. package/dist/chunk-N5M4PI7C.cjs +0 -192
  91. package/dist/chunk-N5M4PI7C.cjs.map +0 -1
  92. package/dist/chunk-OMMIRSPK.js.map +0 -1
  93. package/dist/chunk-RWE2OCQL.cjs.map +0 -1
  94. package/dist/chunk-YLMMTKQL.cjs.map +0 -1
  95. package/dist/personalize-content-Cwc2AomC.d.ts +0 -141
  96. package/dist/personalize-content-CxSdplMV.d.cts +0 -141
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/edge/matcher.ts","../../src/edge/client.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Page matching for the Xenia manifest — a port of the backend's\n * `EdgePageMatcher`, which is the reference:\n *\n * - `/women` exactly that path (case, trailing slash, query ignored)\n * - `/women/*` `/women` and everything below it, not `/womenswear`\n * - `/*` every path\n * - `uk.example.com/women` that path on that host only; hostless patterns match any host\n */\n\nexport function normalizePath(path: string | null | undefined): string {\n if (!path || !path.trim()) return \"/\";\n let p = path.trim().toLowerCase();\n const cut = Math.min(\n ...[\"?\", \"#\"].map((c) => (p.includes(c) ? p.indexOf(c) : p.length)),\n );\n p = p.slice(0, cut);\n if (!p.startsWith(\"/\")) p = \"/\" + p;\n while (p.length > 1 && p.endsWith(\"/\")) p = p.slice(0, -1);\n return p;\n}\n\nconst normalizeHost = (host: string | null | undefined) =>\n (host ?? \"\").trim().toLowerCase().replace(/:\\d+$/, \"\");\n\nfunction matches(pattern: string, host: string, path: string): boolean {\n if (!pattern) return false;\n let patternPath = pattern;\n if (!pattern.startsWith(\"/\")) {\n const slash = pattern.indexOf(\"/\");\n const patternHost = slash < 0 ? pattern : pattern.slice(0, slash);\n if (patternHost !== host) return false;\n patternPath = slash < 0 ? \"/\" : pattern.slice(slash);\n }\n if (patternPath === \"/*\") return true;\n if (patternPath.endsWith(\"/*\")) {\n const prefix = patternPath.slice(0, -2);\n return path === prefix || path.startsWith(prefix + \"/\");\n }\n return path === patternPath;\n}\n\nexport function matchesAny(\n patterns: readonly string[] | null | undefined,\n host: string | null | undefined,\n path: string | null | undefined,\n): boolean {\n if (!patterns?.length) return false;\n const h = normalizeHost(host);\n const p = normalizePath(path);\n return patterns.some((pattern) => matches(pattern, h, p));\n}\n","/**\n * Proxy-side Xenia client for the hybrid personalization flow:\n *\n * 1. `match()` — synchronous, in-memory: does any journey target this page?\n * Answered from the manifest, refreshed in the background every\n * `manifestTtlMs`. Pages no journey targets never call Xenia.\n * 2. `decide()` — for targeted pages only: a per-visitor decision cache hit\n * costs nothing; a miss calls Xenia's `/decide` with a hard `budgetMs`\n * deadline. Timeouts and errors resolve to \"no decision\" (base content),\n * never an exception — Xenia can't slow down or break a page.\n *\n * Runtime-agnostic (only `fetch`, `AbortSignal`, `Map`). The decision cache is\n * per process; swap in `cache` for a shared store (Redis, edge KV).\n */\nimport { matchesAny } from \"./matcher\";\nimport type {\n DecideResult,\n DecisionSource,\n EdgeDecision,\n EdgeFacts,\n Manifest,\n ManifestKey,\n ProfileFacts,\n PublicManifest,\n TrailView,\n} from \"./types\";\n\n/** What a visitor's cached decision holds: the decisions and the profile they were made with. */\nexport type CachedDecision = { decisions: EdgeDecision[]; profile?: ProfileFacts | null };\n\nexport type DecisionCache = {\n get(key: string): CachedDecision | undefined;\n set(key: string, value: CachedDecision, ttlMs: number): void;\n};\n\nexport type XeniaEdgeConfig = {\n /** Xenia engine base URL, e.g. `https://engine.xeniamartech.com/xenia`. */\n baseUrl: string;\n /** A server-type SDK token. Never expose it to the browser. */\n token: string;\n /** Hard deadline for one `/decide` call. Default 150 ms. */\n budgetMs?: number;\n /**\n * Where to read the manifest. Default: Xenia's `/edge/manifest` (with the token). A storefront\n * can serve its own CDN-cached copy instead (e.g. a Next.js ISR route) — then no token is sent.\n */\n manifestUrl?: string;\n /**\n * A manifest baked in at build time. An instance starts with it in memory, so a cold start never\n * waits: the first request uses it and triggers a background refresh. Empty version = none.\n */\n initialManifest?: Manifest | PublicManifest;\n /** How long a cold start waits for the first manifest (only without `initialManifest`). Default 300 ms. */\n manifestWaitMs?: number;\n /** Manifest refresh interval. Default 30 s. */\n manifestTtlMs?: number;\n /** How long a visitor keeps a decision. Default 30 min (a session). */\n decisionTtlMs?: number;\n /**\n * How long a provisional decision is kept (see `provisional`): Jev's answer, or the visitor's first\n * signals, usually land seconds later, so the next view should ask again. Default 15 s.\n */\n provisionalDecisionTtlMs?: number;\n cache?: DecisionCache;\n fetch?: typeof fetch;\n onError?: (error: unknown, context: string) => void;\n};\n\nexport type DecideInput = {\n host: string | null;\n path: string;\n visitorId: string;\n facts: EdgeFacts;\n /** Facts that identify this visitor's context for caching (e.g. landing UTM, device). */\n cacheFacts?: EdgeFacts;\n /** The visitor's recent page views, most recent first (see `TrailView`). */\n trail?: TrailView[];\n /** This call's time budget, instead of the client's (a decision that calls a back office). */\n budgetMs?: number;\n /** How long to keep this call's answer, from what it said, instead of the client's. */\n ttlMs?: (answer: CachedDecision) => number;\n};\n\nexport type DecideOutcome = {\n decisions: EdgeDecision[];\n /** The profile Xenia decided with (also on cache hits), when profiles are on. */\n profile?: ProfileFacts | null;\n source: DecisionSource;\n /** Time spent in Xenia, 0 on a cache hit. */\n ms: number;\n};\n\n/** Bounded in-memory LRU with per-entry expiry. */\nexport function memoryDecisionCache(maxEntries = 10_000): DecisionCache {\n const entries = new Map<string, { value: CachedDecision; expires: number }>();\n return {\n get(key) {\n const hit = entries.get(key);\n if (!hit) return undefined;\n entries.delete(key);\n if (hit.expires < Date.now()) return undefined;\n entries.set(key, hit); // most recently used last\n return hit.value;\n },\n set(key, value, ttlMs) {\n entries.delete(key);\n entries.set(key, { value, expires: Date.now() + ttlMs });\n while (entries.size > maxEntries) {\n entries.delete(entries.keys().next().value as string);\n }\n },\n };\n}\n\nconst stableJson = (facts: EdgeFacts | undefined) =>\n JSON.stringify(\n Object.keys(facts ?? {})\n .sort()\n .map((k) => [k, facts![k]]),\n );\n\n/**\n * Xenia's manifest, or a storefront's trimmed public copy (`{version, pages}`). The trimmed form\n * becomes one key-less entry per page pattern: the proxy then decides by path, and Xenia picks the\n * content keys itself.\n */\nfunction normalizeManifest(m: Manifest | PublicManifest): Manifest {\n if (\"keys\" in m && Array.isArray(m.keys)) return m;\n const pages = (m as PublicManifest).pages ?? [];\n return {\n version: m.version,\n generatedAt: \"\",\n keys: pages.map((p) => ({ contentKey: \"\", mode: \"server\" as const, pages: [p] })),\n };\n}\n\n/**\n * A provisional decision, to ask again soon:\n * - a Visitor Intent step that refines with Jev answered from its signals because Jev hasn't yet;\n * - Visitor Intent steps with no answer at all: the visitor's first signals may land any moment —\n * and for a visitor who is the same in every browser from another browser,\n * which this browser's cache facts (trail, clicks) never see.\n * Steps answered from signals alone change with the visitor's own views, which the cache facts do see.\n */\nfunction provisional(profile: ProfileFacts | null | undefined): boolean {\n const answers = profile?.answers;\n if (!answers || typeof answers !== \"object\") return false;\n const steps = Object.values(answers as Record<string, { choice?: string | null; source?: string; jev?: boolean } | null>);\n return steps.some((a) => a?.source === \"signals\" && a.jev === true) || (steps.length > 0 && steps.every((a) => !a?.choice));\n}\n\nexport function createXeniaEdge(config: XeniaEdgeConfig) {\n const baseUrl = config.baseUrl.replace(/\\/+$/, \"\");\n const budgetMs = config.budgetMs ?? 150;\n const manifestWaitMs = config.manifestWaitMs ?? 300;\n const manifestTtlMs = config.manifestTtlMs ?? 30_000;\n const decisionTtlMs = config.decisionTtlMs ?? 30 * 60_000;\n const provisionalDecisionTtlMs = config.provisionalDecisionTtlMs ?? 15_000;\n const cache = config.cache ?? memoryDecisionCache();\n const doFetch = config.fetch ?? fetch;\n const onError = config.onError ?? (() => {});\n const auth = { Authorization: `Bearer ${config.token}` };\n\n // A bundled manifest is used right away; manifestAt 0 makes the first request refresh it in the\n // background (currentManifest only blocks when there is no manifest at all).\n let manifest: Manifest | null = config.initialManifest?.version\n ? normalizeManifest(config.initialManifest)\n : null;\n let manifestAt = 0;\n let refreshing: Promise<void> | null = null;\n const inflight = new Map<string, Promise<DecideOutcome>>();\n\n /** Fetch the manifest (304-aware); keeps the last good one on failure. */\n function refreshManifest(): Promise<void> {\n refreshing ??= (async () => {\n try {\n const res = await doFetch(config.manifestUrl ?? `${baseUrl}/api/personalization/edge/manifest`, {\n headers: config.manifestUrl\n ? {}\n : { ...auth, ...(manifest ? { \"If-None-Match\": `\"${manifest.version}\"` } : {}) },\n signal: AbortSignal.timeout(5_000),\n cache: \"no-store\",\n });\n if (res.status === 304) {\n manifestAt = Date.now();\n } else if (res.ok) {\n manifest = normalizeManifest((await res.json()) as Manifest | PublicManifest);\n manifestAt = Date.now();\n } else {\n onError(new Error(`manifest ${res.status}`), \"manifest\");\n manifestAt = Date.now(); // back off for a TTL before retrying\n }\n } catch (e) {\n onError(e, \"manifest\");\n manifestAt = Date.now();\n } finally {\n refreshing = null;\n }\n })();\n return refreshing;\n }\n\n /** The current manifest; stale-while-revalidate, a bounded wait on cold start. */\n async function currentManifest(): Promise<Manifest | null> {\n if (Date.now() - manifestAt > manifestTtlMs) {\n const pending = refreshManifest();\n if (!manifest) {\n await Promise.race([pending, new Promise((r) => setTimeout(r, manifestWaitMs))]);\n }\n }\n return manifest;\n }\n\n /**\n * The content keys targeting this page, or `null` when no manifest is\n * available yet. An empty array means \"don't call Xenia\".\n */\n async function match(host: string | null, path: string): Promise<ManifestKey[] | null> {\n const m = await currentManifest();\n if (!m) return null;\n return m.keys.filter((k) => k.mode !== \"off\" && matchesAny(k.pages, host, path));\n }\n\n async function decide(keys: ManifestKey[], input: DecideInput): Promise<DecideOutcome> {\n const version = manifest?.version ?? \"\";\n // Known keys are sent explicitly; with a trimmed manifest there are none, and Xenia selects\n // the keys whose pages match the path. Either way the cache is shared by every page that\n // matches the same keys / patterns (e.g. all of /women/*).\n const contentKeys = keys.map((k) => k.contentKey).filter(Boolean).sort();\n const scope = contentKeys.length ? contentKeys.join(\",\") : keys.flatMap((k) => k.pages).sort().join(\",\");\n const cacheKey = [version, input.visitorId, scope, stableJson(input.cacheFacts)].join(\"|\");\n\n const cached = cache.get(cacheKey);\n if (cached) return { decisions: cached.decisions, profile: cached.profile, source: \"cache\", ms: 0 };\n\n // Concurrent requests from one visitor (HTML + RSC prefetches) share a call.\n const pending = inflight.get(cacheKey);\n if (pending) return pending;\n\n const call = (async (): Promise<DecideOutcome> => {\n const started = Date.now();\n try {\n const res = await doFetch(`${baseUrl}/api/personalization/edge/decide`, {\n method: \"POST\",\n headers: { ...auth, \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n contentKeys,\n host: input.host,\n path: input.path,\n visitorId: input.visitorId,\n facts: input.facts,\n ...(input.trail?.length ? { trail: input.trail } : {}),\n }),\n signal: AbortSignal.timeout(input.budgetMs ?? budgetMs),\n cache: \"no-store\",\n });\n if (!res.ok) {\n onError(new Error(`decide ${res.status}`), \"decide\");\n return { decisions: [], source: \"error\", ms: Date.now() - started };\n }\n const result = (await res.json()) as DecideResult | null;\n // A reply in the wrong shape is an error, never cached: it would break every view for the TTL.\n if (!Array.isArray(result?.decisions)) throw new Error(\"decide: reply has no decisions list\");\n const decisions = result.decisions.filter((d) => d && Array.isArray(d.swaps));\n const answer = { decisions, profile: result.profile };\n cache.set(cacheKey, answer, input.ttlMs?.(answer) ?? (provisional(result.profile) ? provisionalDecisionTtlMs : decisionTtlMs));\n if (manifest && result.version && result.version !== manifest.version) {\n void refreshManifest(); // journeys changed — pick up the new manifest\n }\n return { decisions, profile: result.profile, source: \"xenia\", ms: Date.now() - started };\n } catch (e) {\n const timedOut = e instanceof DOMException && e.name === \"TimeoutError\";\n if (!timedOut) onError(e, \"decide\");\n return { decisions: [], source: timedOut ? \"timeout\" : \"error\", ms: Date.now() - started };\n } finally {\n inflight.delete(cacheKey);\n }\n })();\n inflight.set(cacheKey, call);\n return call;\n }\n\n return { match, decide, refreshManifest };\n}\n\nexport type XeniaEdge = ReturnType<typeof createXeniaEdge>;\n"]}
1
+ {"version":3,"sources":["../../src/edge/matcher.ts","../../src/edge/client.ts","../../src/edge/variant-page.ts"],"names":["personalizeContent","variantEntryResolver","decodeVariant"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Page matching for the Xenia manifest — a port of the backend's\n * `EdgePageMatcher`, which is the reference:\n *\n * - `/women` exactly that path (case, trailing slash, query ignored)\n * - `/women/*` `/women` and everything below it, not `/womenswear`\n * - `/*` every path\n * - `uk.example.com/women` that path on that host only; hostless patterns match any host\n */\n\nexport function normalizePath(path: string | null | undefined): string {\n if (!path || !path.trim()) return \"/\";\n let p = path.trim().toLowerCase();\n const cut = Math.min(\n ...[\"?\", \"#\"].map((c) => (p.includes(c) ? p.indexOf(c) : p.length)),\n );\n p = p.slice(0, cut);\n if (!p.startsWith(\"/\")) p = \"/\" + p;\n while (p.length > 1 && p.endsWith(\"/\")) p = p.slice(0, -1);\n return p;\n}\n\nconst normalizeHost = (host: string | null | undefined) =>\n (host ?? \"\").trim().toLowerCase().replace(/:\\d+$/, \"\");\n\nfunction matches(pattern: string, host: string, path: string): boolean {\n if (!pattern) return false;\n let patternPath = pattern;\n if (!pattern.startsWith(\"/\")) {\n const slash = pattern.indexOf(\"/\");\n const patternHost = slash < 0 ? pattern : pattern.slice(0, slash);\n if (patternHost !== host) return false;\n patternPath = slash < 0 ? \"/\" : pattern.slice(slash);\n }\n if (patternPath === \"/*\") return true;\n if (patternPath.endsWith(\"/*\")) {\n const prefix = patternPath.slice(0, -2);\n return path === prefix || path.startsWith(prefix + \"/\");\n }\n return path === patternPath;\n}\n\nexport function matchesAny(\n patterns: readonly string[] | null | undefined,\n host: string | null | undefined,\n path: string | null | undefined,\n): boolean {\n if (!patterns?.length) return false;\n const h = normalizeHost(host);\n const p = normalizePath(path);\n return patterns.some((pattern) => matches(pattern, h, p));\n}\n","/**\n * Proxy-side Xenia client for the hybrid personalization flow:\n *\n * 1. `match()` — synchronous, in-memory: does any journey target this page?\n * Answered from the manifest, refreshed in the background every\n * `manifestTtlMs`. Pages no journey targets never call Xenia.\n * 2. `decide()` — for targeted pages only: a per-visitor decision cache hit\n * costs nothing; a miss calls Xenia's `/decide` with a hard `budgetMs`\n * deadline. Timeouts and errors resolve to \"no decision\" (base content),\n * never an exception — Xenia can't slow down or break a page.\n *\n * Runtime-agnostic (only `fetch`, `AbortSignal`, `Map`). The decision cache is\n * per process; swap in `cache` for a shared store (Redis, edge KV).\n */\nimport type { EntryDecision } from \"../core/types\";\nimport { matchesAny } from \"./matcher\";\nimport type {\n DecideResult,\n DecisionSource,\n EdgeDecision,\n EdgeFacts,\n Manifest,\n ManifestKey,\n ProfileFacts,\n PublicManifest,\n TrailView,\n} from \"./types\";\n\n/** What a visitor's cached decision holds: the decisions and the profile they were made with. */\nexport type CachedDecision = {\n decisions: EdgeDecision[];\n entryDecisions?: Record<string, EntryDecision[]>;\n profile?: ProfileFacts | null;\n};\n\n/**\n * Where visitors' decisions are kept between page views. In memory per process by default; a shared\n * store (Redis, edge KV) may answer asynchronously — a store that fails or throws reads as a miss and\n * never breaks a page view, and writes never hold up the response.\n */\nexport type DecisionCache = {\n get(key: string): CachedDecision | undefined | Promise<CachedDecision | undefined>;\n set(key: string, value: CachedDecision, ttlMs: number): void | Promise<void>;\n};\n\nexport type XeniaEdgeConfig = {\n /** Xenia engine base URL, e.g. `https://engine.xeniamartech.com/xenia`. */\n baseUrl: string;\n /** A server-type SDK token. Never expose it to the browser. */\n token: string;\n /** Hard deadline for one `/decide` call. Default 150 ms. */\n budgetMs?: number;\n /**\n * Where to read the manifest. Default: Xenia's `/edge/manifest` (with the token). A storefront\n * can serve its own CDN-cached copy instead (e.g. a Next.js ISR route) — then no token is sent.\n */\n manifestUrl?: string;\n /**\n * A manifest baked in at build time. An instance starts with it in memory, so a cold start never\n * waits: the first request uses it and triggers a background refresh. Empty version = none.\n */\n initialManifest?: Manifest | PublicManifest;\n /** How long a cold start waits for the first manifest (only without `initialManifest`). Default 300 ms. */\n manifestWaitMs?: number;\n /** Manifest refresh interval. Default 30 s. */\n manifestTtlMs?: number;\n /** How long a visitor keeps a decision. Default 30 min (a session). */\n decisionTtlMs?: number;\n /**\n * How long a provisional decision is kept (see `provisional`): Jev's answer, or the visitor's first\n * signals, usually land seconds later, so the next view should ask again. Default 15 s.\n */\n provisionalDecisionTtlMs?: number;\n cache?: DecisionCache;\n fetch?: typeof fetch;\n onError?: (error: unknown, context: string) => void;\n};\n\nexport type DecideInput = {\n host: string | null;\n path: string;\n visitorId: string;\n facts: EdgeFacts;\n /** Facts that identify this visitor's context for caching (e.g. landing UTM, device). */\n cacheFacts?: EdgeFacts;\n /** The visitor's recent page views, most recent first (see `TrailView`). */\n trail?: TrailView[];\n /** This call's time budget, instead of the client's (a decision that calls a back office). */\n budgetMs?: number;\n /** How long to keep this call's answer, from what it said, instead of the client's. */\n ttlMs?: (answer: CachedDecision) => number;\n};\n\nexport type DecideOutcome = {\n decisions: EdgeDecision[];\n /** Multi-level entry decisions per content key (see `DecideResult.entryDecisions`). */\n entryDecisions?: Record<string, EntryDecision[]>;\n /** The profile Xenia decided with (also on cache hits), when profiles are on. */\n profile?: ProfileFacts | null;\n source: DecisionSource;\n /** Time spent in Xenia, 0 on a cache hit. */\n ms: number;\n};\n\n/** Bounded in-memory LRU with per-entry expiry. */\nexport function memoryDecisionCache(maxEntries = 10_000): DecisionCache {\n const entries = new Map<string, { value: CachedDecision; expires: number }>();\n return {\n get(key) {\n const hit = entries.get(key);\n if (!hit) return undefined;\n entries.delete(key);\n if (hit.expires < Date.now()) return undefined;\n entries.set(key, hit); // most recently used last\n return hit.value;\n },\n set(key, value, ttlMs) {\n entries.delete(key);\n entries.set(key, { value, expires: Date.now() + ttlMs });\n while (entries.size > maxEntries) {\n entries.delete(entries.keys().next().value as string);\n }\n },\n };\n}\n\nasync function readCache(cache: DecisionCache, key: string): Promise<CachedDecision | undefined> {\n try {\n return await cache.get(key);\n } catch {\n return undefined;\n }\n}\n\nfunction writeCache(cache: DecisionCache, key: string, value: CachedDecision, ttlMs: number): void {\n try {\n void Promise.resolve(cache.set(key, value, ttlMs)).catch(() => {});\n } catch {\n /* a cache that can't store is a cache that missed */\n }\n}\n\n/**\n * A per-process memory cache in front of a shared store: a returning visitor is answered from memory\n * when this instance served them, else from `shared` (another instance did), and only then by Xenia.\n * Reads from `shared` give up after `timeoutMs` (default 50) — a slow store must not cost more than\n * the decision it saves.\n */\nexport function tieredDecisionCache(\n shared: DecisionCache,\n opts: { memory?: DecisionCache; timeoutMs?: number } = {},\n): DecisionCache {\n const memory = opts.memory ?? memoryDecisionCache();\n const timeoutMs = opts.timeoutMs ?? 50;\n return {\n async get(key) {\n const local = await readCache(memory, key);\n if (local) return local;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const hit = await Promise.race([\n readCache(shared, key),\n new Promise<undefined>((resolve) => {\n timer = setTimeout(() => resolve(undefined), timeoutMs);\n }),\n ]).finally(() => clearTimeout(timer));\n // ponytail: the remaining TTL isn't known here, so a shared hit is kept in memory for 15 s (the\n // shortest decision TTL); pass the TTL through the store if a longer memory copy matters.\n if (hit) writeCache(memory, key, hit, 15_000);\n return hit;\n },\n set(key, value, ttlMs) {\n writeCache(memory, key, value, ttlMs);\n writeCache(shared, key, value, ttlMs);\n },\n };\n}\n\nconst stableJson = (facts: EdgeFacts | undefined) =>\n JSON.stringify(\n Object.keys(facts ?? {})\n .sort()\n .map((k) => [k, facts![k]]),\n );\n\n/**\n * Xenia's manifest, or a storefront's trimmed public copy (`{version, pages}`). The trimmed form\n * becomes one key-less entry per page pattern: the proxy then decides by path, and Xenia picks the\n * content keys itself.\n */\nfunction normalizeManifest(m: Manifest | PublicManifest): Manifest {\n if (\"keys\" in m && Array.isArray(m.keys)) return m;\n const pages = (m as PublicManifest).pages ?? [];\n return {\n version: m.version,\n generatedAt: \"\",\n keys: pages.map((p) => ({ contentKey: \"\", mode: \"server\" as const, pages: [p] })),\n };\n}\n\n/**\n * A provisional decision, to ask again soon:\n * - a Visitor Intent step that refines with Jev answered from its signals because Jev hasn't yet;\n * - Visitor Intent steps with no answer at all: the visitor's first signals may land any moment —\n * and for a visitor who is the same in every browser from another browser,\n * which this browser's cache facts (trail, clicks) never see.\n * Steps answered from signals alone change with the visitor's own views, which the cache facts do see.\n */\nfunction provisional(profile: ProfileFacts | null | undefined): boolean {\n const answers = profile?.answers;\n if (!answers || typeof answers !== \"object\") return false;\n const steps = Object.values(answers as Record<string, { choice?: string | null; source?: string; jev?: boolean } | null>);\n return steps.some((a) => a?.source === \"signals\" && a.jev === true) || (steps.length > 0 && steps.every((a) => !a?.choice));\n}\n\nexport function createXeniaEdge(config: XeniaEdgeConfig) {\n const baseUrl = config.baseUrl.replace(/\\/+$/, \"\");\n const budgetMs = config.budgetMs ?? 150;\n const manifestWaitMs = config.manifestWaitMs ?? 300;\n const manifestTtlMs = config.manifestTtlMs ?? 30_000;\n const decisionTtlMs = config.decisionTtlMs ?? 30 * 60_000;\n const provisionalDecisionTtlMs = config.provisionalDecisionTtlMs ?? 15_000;\n const cache = config.cache ?? memoryDecisionCache();\n const doFetch = config.fetch ?? fetch;\n const onError = config.onError ?? (() => {});\n const auth = { Authorization: `Bearer ${config.token}` };\n\n // A bundled manifest is used right away; manifestAt 0 makes the first request refresh it in the\n // background (currentManifest only blocks when there is no manifest at all).\n let manifest: Manifest | null = config.initialManifest?.version\n ? normalizeManifest(config.initialManifest)\n : null;\n let manifestAt = 0;\n let refreshing: Promise<void> | null = null;\n const inflight = new Map<string, Promise<DecideOutcome>>();\n\n /** Fetch the manifest (304-aware); keeps the last good one on failure. */\n function refreshManifest(): Promise<void> {\n refreshing ??= (async () => {\n try {\n const res = await doFetch(config.manifestUrl ?? `${baseUrl}/api/personalization/edge/manifest`, {\n headers: config.manifestUrl\n ? {}\n : { ...auth, ...(manifest ? { \"If-None-Match\": `\"${manifest.version}\"` } : {}) },\n signal: AbortSignal.timeout(5_000),\n cache: \"no-store\",\n });\n if (res.status === 304) {\n manifestAt = Date.now();\n } else if (res.ok) {\n manifest = normalizeManifest((await res.json()) as Manifest | PublicManifest);\n manifestAt = Date.now();\n } else {\n onError(new Error(`manifest ${res.status}`), \"manifest\");\n manifestAt = Date.now(); // back off for a TTL before retrying\n }\n } catch (e) {\n onError(e, \"manifest\");\n manifestAt = Date.now();\n } finally {\n refreshing = null;\n }\n })();\n return refreshing;\n }\n\n /** The current manifest; stale-while-revalidate, a bounded wait on cold start. */\n async function currentManifest(): Promise<Manifest | null> {\n if (Date.now() - manifestAt > manifestTtlMs) {\n const pending = refreshManifest();\n if (!manifest) {\n await Promise.race([pending, new Promise((r) => setTimeout(r, manifestWaitMs))]);\n }\n }\n return manifest;\n }\n\n /**\n * The content keys targeting this page, or `null` when no manifest is\n * available yet. An empty array means \"don't call Xenia\".\n */\n async function match(host: string | null, path: string): Promise<ManifestKey[] | null> {\n const m = await currentManifest();\n if (!m) return null;\n return m.keys.filter((k) => k.mode !== \"off\" && matchesAny(k.pages, host, path));\n }\n\n async function decide(keys: ManifestKey[], input: DecideInput): Promise<DecideOutcome> {\n const version = manifest?.version ?? \"\";\n // Known keys are sent explicitly; with a trimmed manifest there are none, and Xenia selects\n // the keys whose pages match the path. Either way the cache is shared by every page that\n // matches the same keys / patterns (e.g. all of /women/*).\n const contentKeys = keys.map((k) => k.contentKey).filter(Boolean).sort();\n const scope = contentKeys.length ? contentKeys.join(\",\") : keys.flatMap((k) => k.pages).sort().join(\",\");\n const cacheKey = [version, input.visitorId, scope, stableJson(input.cacheFacts)].join(\"|\");\n\n const cached = await readCache(cache, cacheKey);\n if (cached) {\n return { decisions: cached.decisions, entryDecisions: cached.entryDecisions, profile: cached.profile, source: \"cache\", ms: 0 };\n }\n\n // Concurrent requests from one visitor (HTML + RSC prefetches) share a call.\n const pending = inflight.get(cacheKey);\n if (pending) return pending;\n\n const call = (async (): Promise<DecideOutcome> => {\n const started = Date.now();\n try {\n const res = await doFetch(`${baseUrl}/api/personalization/edge/decide`, {\n method: \"POST\",\n headers: { ...auth, \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n contentKeys,\n host: input.host,\n path: input.path,\n visitorId: input.visitorId,\n facts: input.facts,\n ...(input.trail?.length ? { trail: input.trail } : {}),\n }),\n signal: AbortSignal.timeout(input.budgetMs ?? budgetMs),\n cache: \"no-store\",\n });\n if (!res.ok) {\n onError(new Error(`decide ${res.status}`), \"decide\");\n return { decisions: [], source: \"error\", ms: Date.now() - started };\n }\n const result = (await res.json()) as DecideResult | null;\n // A reply in the wrong shape is an error, never cached: it would break every view for the TTL.\n if (!Array.isArray(result?.decisions)) throw new Error(\"decide: reply has no decisions list\");\n const decisions = result.decisions.filter((d) => d && Array.isArray(d.swaps));\n const answer = { decisions, entryDecisions: result.entryDecisions, profile: result.profile };\n writeCache(cache, cacheKey, answer, input.ttlMs?.(answer) ?? (provisional(result.profile) ? provisionalDecisionTtlMs : decisionTtlMs));\n if (manifest && result.version && result.version !== manifest.version) {\n void refreshManifest(); // journeys changed — pick up the new manifest\n }\n return { decisions, entryDecisions: result.entryDecisions, profile: result.profile, source: \"xenia\", ms: Date.now() - started };\n } catch (e) {\n const timedOut = e instanceof DOMException && e.name === \"TimeoutError\";\n if (!timedOut) onError(e, \"decide\");\n return { decisions: [], source: timedOut ? \"timeout\" : \"error\", ms: Date.now() - started };\n } finally {\n inflight.delete(cacheKey);\n }\n })();\n inflight.set(cacheKey, call);\n return call;\n }\n\n return { match, decide, refreshManifest };\n}\n\nexport type XeniaEdge = ReturnType<typeof createXeniaEdge>;\n","/**\n * The variant page of a CMS page journey served at the edge: the proxy decided every level of the\n * page with one `/decide` call and put the decisions in the variant token, so the page personalizes\n * itself from the token — no Xenia call, only CMS fetches — and can be cached per variant.\n */\n\nimport {\n personalizeContent,\n type PageRef,\n type PersonalizeContentOptions,\n type PersonalizeContentResult,\n} from \"../core/personalize-content\";\nimport { decodeVariant, variantEntryResolver, type DecodedVariant } from \"./variant\";\n\n/**\n * `personalizeContent` options for a variant page. `contentKey` is optional: without it, the page is\n * personalized for every content key the token decided (a page several journeys personalize).\n */\nexport type VariantPersonalizeOptions = Omit<PersonalizeContentOptions, \"resolve\" | \"contentKey\"> & {\n contentKey?: string;\n};\n\n/**\n * Personalize a page you fetched yourself from a decoded variant (`decodeVariant`). Never throws;\n * `complete` is false when the token had to leave decisions out or a CMS fetch failed.\n */\nexport async function personalizeVariantContent<T>(\n content: T,\n variant: Pick<DecodedVariant, \"entries\" | \"entriesDropped\"> | null | undefined,\n options: VariantPersonalizeOptions,\n): Promise<PersonalizeContentResult<T>> {\n const entries = variant?.entries ?? [];\n const keys = options.contentKey ? [options.contentKey] : [...new Set(entries.map((e) => e.contentKey))];\n let result: PersonalizeContentResult<T> = { content, applied: [], complete: true };\n for (const contentKey of keys) {\n const next = await personalizeContent(result.content, {\n ...options,\n contentKey,\n resolve: variantEntryResolver(entries, contentKey),\n // Each key's run copies the page unless asked not to; after the first, ours is already a copy.\n inPlace: result.content !== content || options.inPlace,\n });\n result = {\n content: next.content,\n applied: [...result.applied, ...next.applied],\n complete: result.complete && next.complete,\n ...(next.error !== undefined || result.error !== undefined ? { error: next.error ?? result.error } : {}),\n };\n }\n return variant?.entriesDropped ? { ...result, complete: false } : result;\n}\n\n/**\n * The whole variant page in one call: verify the token, fetch the page through `options.cms`, and\n * personalize it from the token's decisions. A token that doesn't verify renders the base page\n * (static swaps still apply). Never throws; `content` is null when the page doesn't exist.\n *\n * ```ts\n * // app/xv/[token]/[[...slug]]/page.tsx\n * const { content, variant } = await personalizeVariantPage(token, { contentType: \"page\", fields: { slug } }, {\n * secret: process.env.XENIA_VARIANT_SECRET!, cms, contentKey: \"home-main\",\n * });\n * ```\n */\nexport async function personalizeVariantPage<T, E = unknown>(\n token: string,\n ref: PageRef,\n options: VariantPersonalizeOptions & { secret: string },\n): Promise<PersonalizeContentResult<T | null> & { variant: DecodedVariant<E> | null }> {\n const { secret, ...rest } = options;\n const variant = await decodeVariant<E>(safeDecode(token), secret);\n let page: T | null;\n try {\n if (!rest.cms.fetchPage) throw new Error(\"This CMS adapter cannot fetch pages; use personalizeVariantContent(page)\");\n page = (await rest.cms.fetchPage(ref, rest.locale)) as T | null;\n } catch (error) {\n return { content: null, applied: [], complete: false, error, variant };\n }\n if (page == null) return { content: null, applied: [], complete: true, variant };\n return { ...(await personalizeVariantContent(page, variant, { ...rest, inPlace: true })), variant };\n}\n\n/** Next.js hands route params still percent-encoded in some runtimes. */\nfunction safeDecode(token: string): string {\n try {\n return decodeURIComponent(token);\n } catch {\n return token;\n }\n}\n"]}
@@ -1,6 +1,6 @@
1
- import { M as Manifest, P as PublicManifest, T as TrailView } from '../consent-BgAETCkL.cjs';
2
- export { C as CachedDecision, D as DecideInput, a as DecideOutcome, b as DecideResult, c as DecisionCache, d as DecisionSource, E as EdgeConsent, e as EdgeDecision, f as EdgeFacts, g as EdgeMode, G as GeoHeader, L as LocalTime, h as ManifestKey, i as ProfileFacts, S as SwapGroup, V as VariantSwap, j as ViewerGeo, X as XeniaEdge, k as XeniaEdgeConfig, l as createXeniaEdge, m as decodeVariant, n as deviceOf, o as edgeConsent, p as encodeVariant, q as isBot, r as isPageLoad, s as isPrefetch, t as landingContext, u as localTime, v as memoryDecisionCache, w as seasonOf, x as swapGroups, y as viewerGeo } from '../consent-BgAETCkL.cjs';
3
- import '../types-DgVHS4Qw.cjs';
1
+ import { M as Manifest, P as PublicManifest, D as DecodedVariant, T as TrailView } from '../consent-Dtiqzz2B.cjs';
2
+ export { C as CachedDecision, a as DecideInput, b as DecideOutcome, c as DecideResult, d as DecisionCache, e as DecisionSource, E as EdgeConsent, f as EdgeDecision, g as EdgeFacts, h as EdgeMode, G as GeoHeader, L as LocalTime, i as ManifestKey, j as ProfileFacts, S as SwapGroup, V as VariantSwap, k as ViewerGeo, X as XeniaEdge, l as XeniaEdgeConfig, m as createXeniaEdge, n as decodeVariant, o as deviceOf, p as edgeConsent, q as encodeVariant, r as isBot, s as isPageLoad, t as isPrefetch, u as landingContext, v as localTime, w as memoryDecisionCache, x as seasonOf, y as swapGroups, z as tieredDecisionCache, A as variantEntries, B as variantEntryResolver, F as viewerGeo } from '../consent-Dtiqzz2B.cjs';
3
+ import { PersonalizeContentOptions, PersonalizeContentResult, PageRef } from '../index.cjs';
4
4
 
5
5
  /**
6
6
  * Page matching for the Xenia manifest — a port of the backend's
@@ -22,7 +22,7 @@ interface FetchManifestOptions {
22
22
  /** Default 5000 ms. */
23
23
  timeoutMs?: number;
24
24
  fetch?: typeof fetch;
25
- /** Extra fetch options, e.g. Next.js `{ next: { revalidate } }`. */
25
+ /** Extra fetch options, e.g. Next.js `{ next: { revalidate } }`. Their headers are kept. */
26
26
  init?: RequestInit;
27
27
  }
28
28
  /** Xenia's manifest (`GET /api/personalization/edge/manifest`). Throws on a non-2xx answer. */
@@ -44,6 +44,42 @@ declare function verifyWebhook(input: {
44
44
  body: string;
45
45
  }, secret: string, toleranceS?: number): Promise<boolean>;
46
46
 
47
+ /**
48
+ * The variant page of a CMS page journey served at the edge: the proxy decided every level of the
49
+ * page with one `/decide` call and put the decisions in the variant token, so the page personalizes
50
+ * itself from the token — no Xenia call, only CMS fetches — and can be cached per variant.
51
+ */
52
+
53
+ /**
54
+ * `personalizeContent` options for a variant page. `contentKey` is optional: without it, the page is
55
+ * personalized for every content key the token decided (a page several journeys personalize).
56
+ */
57
+ type VariantPersonalizeOptions = Omit<PersonalizeContentOptions, "resolve" | "contentKey"> & {
58
+ contentKey?: string;
59
+ };
60
+ /**
61
+ * Personalize a page you fetched yourself from a decoded variant (`decodeVariant`). Never throws;
62
+ * `complete` is false when the token had to leave decisions out or a CMS fetch failed.
63
+ */
64
+ declare function personalizeVariantContent<T>(content: T, variant: Pick<DecodedVariant, "entries" | "entriesDropped"> | null | undefined, options: VariantPersonalizeOptions): Promise<PersonalizeContentResult<T>>;
65
+ /**
66
+ * The whole variant page in one call: verify the token, fetch the page through `options.cms`, and
67
+ * personalize it from the token's decisions. A token that doesn't verify renders the base page
68
+ * (static swaps still apply). Never throws; `content` is null when the page doesn't exist.
69
+ *
70
+ * ```ts
71
+ * // app/xv/[token]/[[...slug]]/page.tsx
72
+ * const { content, variant } = await personalizeVariantPage(token, { contentType: "page", fields: { slug } }, {
73
+ * secret: process.env.XENIA_VARIANT_SECRET!, cms, contentKey: "home-main",
74
+ * });
75
+ * ```
76
+ */
77
+ declare function personalizeVariantPage<T, E = unknown>(token: string, ref: PageRef, options: VariantPersonalizeOptions & {
78
+ secret: string;
79
+ }): Promise<PersonalizeContentResult<T | null> & {
80
+ variant: DecodedVariant<E> | null;
81
+ }>;
82
+
47
83
  declare const MAX_TRAIL = 8;
48
84
  /** Cookie lifetime: a visit ends after 30 idle minutes, like the SDK's session. */
49
85
  declare const TRAIL_MAX_AGE_S: number;
@@ -58,5 +94,5 @@ declare function withView(trail: TrailView[], path: string, now?: number): Trail
58
94
  /** The cookie value for a trail (not URI-encoded; cookie setters encode it). */
59
95
  declare function trailCookieValue(trail: TrailView[]): string;
60
96
 
61
- export { MAX_TRAIL, Manifest, PublicManifest, TRAIL_MAX_AGE_S, TrailView, fetchManifest, matchesAny, normalizePath, readTrail, trailCookieName, trailCookieValue, trimManifest, verifyWebhook, withView };
62
- export type { FetchManifestOptions };
97
+ export { DecodedVariant, MAX_TRAIL, Manifest, PublicManifest, TRAIL_MAX_AGE_S, TrailView, fetchManifest, matchesAny, normalizePath, personalizeVariantContent, personalizeVariantPage, readTrail, trailCookieName, trailCookieValue, trimManifest, verifyWebhook, withView };
98
+ export type { FetchManifestOptions, VariantPersonalizeOptions };
@@ -1,6 +1,6 @@
1
- import { M as Manifest, P as PublicManifest, T as TrailView } from '../consent-DRCV0N0d.js';
2
- export { C as CachedDecision, D as DecideInput, a as DecideOutcome, b as DecideResult, c as DecisionCache, d as DecisionSource, E as EdgeConsent, e as EdgeDecision, f as EdgeFacts, g as EdgeMode, G as GeoHeader, L as LocalTime, h as ManifestKey, i as ProfileFacts, S as SwapGroup, V as VariantSwap, j as ViewerGeo, X as XeniaEdge, k as XeniaEdgeConfig, l as createXeniaEdge, m as decodeVariant, n as deviceOf, o as edgeConsent, p as encodeVariant, q as isBot, r as isPageLoad, s as isPrefetch, t as landingContext, u as localTime, v as memoryDecisionCache, w as seasonOf, x as swapGroups, y as viewerGeo } from '../consent-DRCV0N0d.js';
3
- import '../types-DgVHS4Qw.js';
1
+ import { M as Manifest, P as PublicManifest, D as DecodedVariant, T as TrailView } from '../consent-byYiDMFr.js';
2
+ export { C as CachedDecision, a as DecideInput, b as DecideOutcome, c as DecideResult, d as DecisionCache, e as DecisionSource, E as EdgeConsent, f as EdgeDecision, g as EdgeFacts, h as EdgeMode, G as GeoHeader, L as LocalTime, i as ManifestKey, j as ProfileFacts, S as SwapGroup, V as VariantSwap, k as ViewerGeo, X as XeniaEdge, l as XeniaEdgeConfig, m as createXeniaEdge, n as decodeVariant, o as deviceOf, p as edgeConsent, q as encodeVariant, r as isBot, s as isPageLoad, t as isPrefetch, u as landingContext, v as localTime, w as memoryDecisionCache, x as seasonOf, y as swapGroups, z as tieredDecisionCache, A as variantEntries, B as variantEntryResolver, F as viewerGeo } from '../consent-byYiDMFr.js';
3
+ import { PersonalizeContentOptions, PersonalizeContentResult, PageRef } from '../index.js';
4
4
 
5
5
  /**
6
6
  * Page matching for the Xenia manifest — a port of the backend's
@@ -22,7 +22,7 @@ interface FetchManifestOptions {
22
22
  /** Default 5000 ms. */
23
23
  timeoutMs?: number;
24
24
  fetch?: typeof fetch;
25
- /** Extra fetch options, e.g. Next.js `{ next: { revalidate } }`. */
25
+ /** Extra fetch options, e.g. Next.js `{ next: { revalidate } }`. Their headers are kept. */
26
26
  init?: RequestInit;
27
27
  }
28
28
  /** Xenia's manifest (`GET /api/personalization/edge/manifest`). Throws on a non-2xx answer. */
@@ -44,6 +44,42 @@ declare function verifyWebhook(input: {
44
44
  body: string;
45
45
  }, secret: string, toleranceS?: number): Promise<boolean>;
46
46
 
47
+ /**
48
+ * The variant page of a CMS page journey served at the edge: the proxy decided every level of the
49
+ * page with one `/decide` call and put the decisions in the variant token, so the page personalizes
50
+ * itself from the token — no Xenia call, only CMS fetches — and can be cached per variant.
51
+ */
52
+
53
+ /**
54
+ * `personalizeContent` options for a variant page. `contentKey` is optional: without it, the page is
55
+ * personalized for every content key the token decided (a page several journeys personalize).
56
+ */
57
+ type VariantPersonalizeOptions = Omit<PersonalizeContentOptions, "resolve" | "contentKey"> & {
58
+ contentKey?: string;
59
+ };
60
+ /**
61
+ * Personalize a page you fetched yourself from a decoded variant (`decodeVariant`). Never throws;
62
+ * `complete` is false when the token had to leave decisions out or a CMS fetch failed.
63
+ */
64
+ declare function personalizeVariantContent<T>(content: T, variant: Pick<DecodedVariant, "entries" | "entriesDropped"> | null | undefined, options: VariantPersonalizeOptions): Promise<PersonalizeContentResult<T>>;
65
+ /**
66
+ * The whole variant page in one call: verify the token, fetch the page through `options.cms`, and
67
+ * personalize it from the token's decisions. A token that doesn't verify renders the base page
68
+ * (static swaps still apply). Never throws; `content` is null when the page doesn't exist.
69
+ *
70
+ * ```ts
71
+ * // app/xv/[token]/[[...slug]]/page.tsx
72
+ * const { content, variant } = await personalizeVariantPage(token, { contentType: "page", fields: { slug } }, {
73
+ * secret: process.env.XENIA_VARIANT_SECRET!, cms, contentKey: "home-main",
74
+ * });
75
+ * ```
76
+ */
77
+ declare function personalizeVariantPage<T, E = unknown>(token: string, ref: PageRef, options: VariantPersonalizeOptions & {
78
+ secret: string;
79
+ }): Promise<PersonalizeContentResult<T | null> & {
80
+ variant: DecodedVariant<E> | null;
81
+ }>;
82
+
47
83
  declare const MAX_TRAIL = 8;
48
84
  /** Cookie lifetime: a visit ends after 30 idle minutes, like the SDK's session. */
49
85
  declare const TRAIL_MAX_AGE_S: number;
@@ -58,5 +94,5 @@ declare function withView(trail: TrailView[], path: string, now?: number): Trail
58
94
  /** The cookie value for a trail (not URI-encoded; cookie setters encode it). */
59
95
  declare function trailCookieValue(trail: TrailView[]): string;
60
96
 
61
- export { MAX_TRAIL, Manifest, PublicManifest, TRAIL_MAX_AGE_S, TrailView, fetchManifest, matchesAny, normalizePath, readTrail, trailCookieName, trailCookieValue, trimManifest, verifyWebhook, withView };
62
- export type { FetchManifestOptions };
97
+ export { DecodedVariant, MAX_TRAIL, Manifest, PublicManifest, TRAIL_MAX_AGE_S, TrailView, fetchManifest, matchesAny, normalizePath, personalizeVariantContent, personalizeVariantPage, readTrail, trailCookieName, trailCookieValue, trimManifest, verifyWebhook, withView };
98
+ export type { FetchManifestOptions, VariantPersonalizeOptions };
@@ -1,6 +1,7 @@
1
- export { MAX_TRAIL, TRAIL_MAX_AGE_S, decodeVariant, deviceOf, edgeConsent, encodeVariant, fetchManifest, isBot, isPageLoad, isPrefetch, landingContext, localTime, readTrail, seasonOf, swapGroups, trailCookieName, trailCookieValue, trimManifest, verifyWebhook, viewerGeo, withView } from '../chunk-OMMIRSPK.js';
2
- import '../chunk-QEYONKWY.js';
3
- import '../chunk-BQ5PJCZ7.js';
1
+ import { variantEntryResolver, decodeVariant } from '../chunk-V4EGU3PM.js';
2
+ export { MAX_TRAIL, TRAIL_MAX_AGE_S, decodeVariant, deviceOf, edgeConsent, encodeVariant, fetchManifest, isBot, isPageLoad, isPrefetch, landingContext, localTime, readTrail, seasonOf, swapGroups, trailCookieName, trailCookieValue, trimManifest, variantEntries, variantEntryResolver, verifyWebhook, viewerGeo, withView } from '../chunk-V4EGU3PM.js';
3
+ import '../chunk-UVTFGQR6.js';
4
+ import { personalizeContent } from '../chunk-FSJMHXS6.js';
4
5
 
5
6
  // src/edge/matcher.ts
6
7
  function normalizePath(path) {
@@ -59,6 +60,43 @@ function memoryDecisionCache(maxEntries = 1e4) {
59
60
  }
60
61
  };
61
62
  }
63
+ async function readCache(cache, key) {
64
+ try {
65
+ return await cache.get(key);
66
+ } catch {
67
+ return void 0;
68
+ }
69
+ }
70
+ function writeCache(cache, key, value, ttlMs) {
71
+ try {
72
+ void Promise.resolve(cache.set(key, value, ttlMs)).catch(() => {
73
+ });
74
+ } catch {
75
+ }
76
+ }
77
+ function tieredDecisionCache(shared, opts = {}) {
78
+ const memory = opts.memory ?? memoryDecisionCache();
79
+ const timeoutMs = opts.timeoutMs ?? 50;
80
+ return {
81
+ async get(key) {
82
+ const local = await readCache(memory, key);
83
+ if (local) return local;
84
+ let timer;
85
+ const hit = await Promise.race([
86
+ readCache(shared, key),
87
+ new Promise((resolve) => {
88
+ timer = setTimeout(() => resolve(void 0), timeoutMs);
89
+ })
90
+ ]).finally(() => clearTimeout(timer));
91
+ if (hit) writeCache(memory, key, hit, 15e3);
92
+ return hit;
93
+ },
94
+ set(key, value, ttlMs) {
95
+ writeCache(memory, key, value, ttlMs);
96
+ writeCache(shared, key, value, ttlMs);
97
+ }
98
+ };
99
+ }
62
100
  var stableJson = (facts) => JSON.stringify(
63
101
  Object.keys(facts ?? {}).sort().map((k) => [k, facts[k]])
64
102
  );
@@ -138,8 +176,10 @@ function createXeniaEdge(config) {
138
176
  const contentKeys = keys.map((k) => k.contentKey).filter(Boolean).sort();
139
177
  const scope = contentKeys.length ? contentKeys.join(",") : keys.flatMap((k) => k.pages).sort().join(",");
140
178
  const cacheKey = [version, input.visitorId, scope, stableJson(input.cacheFacts)].join("|");
141
- const cached = cache.get(cacheKey);
142
- if (cached) return { decisions: cached.decisions, profile: cached.profile, source: "cache", ms: 0 };
179
+ const cached = await readCache(cache, cacheKey);
180
+ if (cached) {
181
+ return { decisions: cached.decisions, entryDecisions: cached.entryDecisions, profile: cached.profile, source: "cache", ms: 0 };
182
+ }
143
183
  const pending = inflight.get(cacheKey);
144
184
  if (pending) return pending;
145
185
  const call = (async () => {
@@ -166,12 +206,12 @@ function createXeniaEdge(config) {
166
206
  const result = await res.json();
167
207
  if (!Array.isArray(result?.decisions)) throw new Error("decide: reply has no decisions list");
168
208
  const decisions = result.decisions.filter((d) => d && Array.isArray(d.swaps));
169
- const answer = { decisions, profile: result.profile };
170
- cache.set(cacheKey, answer, input.ttlMs?.(answer) ?? (provisional(result.profile) ? provisionalDecisionTtlMs : decisionTtlMs));
209
+ const answer = { decisions, entryDecisions: result.entryDecisions, profile: result.profile };
210
+ writeCache(cache, cacheKey, answer, input.ttlMs?.(answer) ?? (provisional(result.profile) ? provisionalDecisionTtlMs : decisionTtlMs));
171
211
  if (manifest && result.version && result.version !== manifest.version) {
172
212
  void refreshManifest();
173
213
  }
174
- return { decisions, profile: result.profile, source: "xenia", ms: Date.now() - started };
214
+ return { decisions, entryDecisions: result.entryDecisions, profile: result.profile, source: "xenia", ms: Date.now() - started };
175
215
  } catch (e) {
176
216
  const timedOut = e instanceof DOMException && e.name === "TimeoutError";
177
217
  if (!timedOut) onError(e, "decide");
@@ -186,6 +226,49 @@ function createXeniaEdge(config) {
186
226
  return { match, decide, refreshManifest };
187
227
  }
188
228
 
189
- export { createXeniaEdge, matchesAny, memoryDecisionCache, normalizePath };
229
+ // src/edge/variant-page.ts
230
+ async function personalizeVariantContent(content, variant, options) {
231
+ const entries = variant?.entries ?? [];
232
+ const keys = options.contentKey ? [options.contentKey] : [...new Set(entries.map((e) => e.contentKey))];
233
+ let result = { content, applied: [], complete: true };
234
+ for (const contentKey of keys) {
235
+ const next = await personalizeContent(result.content, {
236
+ ...options,
237
+ contentKey,
238
+ resolve: variantEntryResolver(entries, contentKey),
239
+ // Each key's run copies the page unless asked not to; after the first, ours is already a copy.
240
+ inPlace: result.content !== content || options.inPlace
241
+ });
242
+ result = {
243
+ content: next.content,
244
+ applied: [...result.applied, ...next.applied],
245
+ complete: result.complete && next.complete,
246
+ ...next.error !== void 0 || result.error !== void 0 ? { error: next.error ?? result.error } : {}
247
+ };
248
+ }
249
+ return variant?.entriesDropped ? { ...result, complete: false } : result;
250
+ }
251
+ async function personalizeVariantPage(token, ref, options) {
252
+ const { secret, ...rest } = options;
253
+ const variant = await decodeVariant(safeDecode(token), secret);
254
+ let page;
255
+ try {
256
+ if (!rest.cms.fetchPage) throw new Error("This CMS adapter cannot fetch pages; use personalizeVariantContent(page)");
257
+ page = await rest.cms.fetchPage(ref, rest.locale);
258
+ } catch (error) {
259
+ return { content: null, applied: [], complete: false, error, variant };
260
+ }
261
+ if (page == null) return { content: null, applied: [], complete: true, variant };
262
+ return { ...await personalizeVariantContent(page, variant, { ...rest, inPlace: true }), variant };
263
+ }
264
+ function safeDecode(token) {
265
+ try {
266
+ return decodeURIComponent(token);
267
+ } catch {
268
+ return token;
269
+ }
270
+ }
271
+
272
+ export { createXeniaEdge, matchesAny, memoryDecisionCache, normalizePath, personalizeVariantContent, personalizeVariantPage, tieredDecisionCache };
190
273
  //# sourceMappingURL=index.js.map
191
274
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/edge/matcher.ts","../../src/edge/client.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Page matching for the Xenia manifest — a port of the backend's\n * `EdgePageMatcher`, which is the reference:\n *\n * - `/women` exactly that path (case, trailing slash, query ignored)\n * - `/women/*` `/women` and everything below it, not `/womenswear`\n * - `/*` every path\n * - `uk.example.com/women` that path on that host only; hostless patterns match any host\n */\n\nexport function normalizePath(path: string | null | undefined): string {\n if (!path || !path.trim()) return \"/\";\n let p = path.trim().toLowerCase();\n const cut = Math.min(\n ...[\"?\", \"#\"].map((c) => (p.includes(c) ? p.indexOf(c) : p.length)),\n );\n p = p.slice(0, cut);\n if (!p.startsWith(\"/\")) p = \"/\" + p;\n while (p.length > 1 && p.endsWith(\"/\")) p = p.slice(0, -1);\n return p;\n}\n\nconst normalizeHost = (host: string | null | undefined) =>\n (host ?? \"\").trim().toLowerCase().replace(/:\\d+$/, \"\");\n\nfunction matches(pattern: string, host: string, path: string): boolean {\n if (!pattern) return false;\n let patternPath = pattern;\n if (!pattern.startsWith(\"/\")) {\n const slash = pattern.indexOf(\"/\");\n const patternHost = slash < 0 ? pattern : pattern.slice(0, slash);\n if (patternHost !== host) return false;\n patternPath = slash < 0 ? \"/\" : pattern.slice(slash);\n }\n if (patternPath === \"/*\") return true;\n if (patternPath.endsWith(\"/*\")) {\n const prefix = patternPath.slice(0, -2);\n return path === prefix || path.startsWith(prefix + \"/\");\n }\n return path === patternPath;\n}\n\nexport function matchesAny(\n patterns: readonly string[] | null | undefined,\n host: string | null | undefined,\n path: string | null | undefined,\n): boolean {\n if (!patterns?.length) return false;\n const h = normalizeHost(host);\n const p = normalizePath(path);\n return patterns.some((pattern) => matches(pattern, h, p));\n}\n","/**\n * Proxy-side Xenia client for the hybrid personalization flow:\n *\n * 1. `match()` — synchronous, in-memory: does any journey target this page?\n * Answered from the manifest, refreshed in the background every\n * `manifestTtlMs`. Pages no journey targets never call Xenia.\n * 2. `decide()` — for targeted pages only: a per-visitor decision cache hit\n * costs nothing; a miss calls Xenia's `/decide` with a hard `budgetMs`\n * deadline. Timeouts and errors resolve to \"no decision\" (base content),\n * never an exception — Xenia can't slow down or break a page.\n *\n * Runtime-agnostic (only `fetch`, `AbortSignal`, `Map`). The decision cache is\n * per process; swap in `cache` for a shared store (Redis, edge KV).\n */\nimport { matchesAny } from \"./matcher\";\nimport type {\n DecideResult,\n DecisionSource,\n EdgeDecision,\n EdgeFacts,\n Manifest,\n ManifestKey,\n ProfileFacts,\n PublicManifest,\n TrailView,\n} from \"./types\";\n\n/** What a visitor's cached decision holds: the decisions and the profile they were made with. */\nexport type CachedDecision = { decisions: EdgeDecision[]; profile?: ProfileFacts | null };\n\nexport type DecisionCache = {\n get(key: string): CachedDecision | undefined;\n set(key: string, value: CachedDecision, ttlMs: number): void;\n};\n\nexport type XeniaEdgeConfig = {\n /** Xenia engine base URL, e.g. `https://engine.xeniamartech.com/xenia`. */\n baseUrl: string;\n /** A server-type SDK token. Never expose it to the browser. */\n token: string;\n /** Hard deadline for one `/decide` call. Default 150 ms. */\n budgetMs?: number;\n /**\n * Where to read the manifest. Default: Xenia's `/edge/manifest` (with the token). A storefront\n * can serve its own CDN-cached copy instead (e.g. a Next.js ISR route) — then no token is sent.\n */\n manifestUrl?: string;\n /**\n * A manifest baked in at build time. An instance starts with it in memory, so a cold start never\n * waits: the first request uses it and triggers a background refresh. Empty version = none.\n */\n initialManifest?: Manifest | PublicManifest;\n /** How long a cold start waits for the first manifest (only without `initialManifest`). Default 300 ms. */\n manifestWaitMs?: number;\n /** Manifest refresh interval. Default 30 s. */\n manifestTtlMs?: number;\n /** How long a visitor keeps a decision. Default 30 min (a session). */\n decisionTtlMs?: number;\n /**\n * How long a provisional decision is kept (see `provisional`): Jev's answer, or the visitor's first\n * signals, usually land seconds later, so the next view should ask again. Default 15 s.\n */\n provisionalDecisionTtlMs?: number;\n cache?: DecisionCache;\n fetch?: typeof fetch;\n onError?: (error: unknown, context: string) => void;\n};\n\nexport type DecideInput = {\n host: string | null;\n path: string;\n visitorId: string;\n facts: EdgeFacts;\n /** Facts that identify this visitor's context for caching (e.g. landing UTM, device). */\n cacheFacts?: EdgeFacts;\n /** The visitor's recent page views, most recent first (see `TrailView`). */\n trail?: TrailView[];\n /** This call's time budget, instead of the client's (a decision that calls a back office). */\n budgetMs?: number;\n /** How long to keep this call's answer, from what it said, instead of the client's. */\n ttlMs?: (answer: CachedDecision) => number;\n};\n\nexport type DecideOutcome = {\n decisions: EdgeDecision[];\n /** The profile Xenia decided with (also on cache hits), when profiles are on. */\n profile?: ProfileFacts | null;\n source: DecisionSource;\n /** Time spent in Xenia, 0 on a cache hit. */\n ms: number;\n};\n\n/** Bounded in-memory LRU with per-entry expiry. */\nexport function memoryDecisionCache(maxEntries = 10_000): DecisionCache {\n const entries = new Map<string, { value: CachedDecision; expires: number }>();\n return {\n get(key) {\n const hit = entries.get(key);\n if (!hit) return undefined;\n entries.delete(key);\n if (hit.expires < Date.now()) return undefined;\n entries.set(key, hit); // most recently used last\n return hit.value;\n },\n set(key, value, ttlMs) {\n entries.delete(key);\n entries.set(key, { value, expires: Date.now() + ttlMs });\n while (entries.size > maxEntries) {\n entries.delete(entries.keys().next().value as string);\n }\n },\n };\n}\n\nconst stableJson = (facts: EdgeFacts | undefined) =>\n JSON.stringify(\n Object.keys(facts ?? {})\n .sort()\n .map((k) => [k, facts![k]]),\n );\n\n/**\n * Xenia's manifest, or a storefront's trimmed public copy (`{version, pages}`). The trimmed form\n * becomes one key-less entry per page pattern: the proxy then decides by path, and Xenia picks the\n * content keys itself.\n */\nfunction normalizeManifest(m: Manifest | PublicManifest): Manifest {\n if (\"keys\" in m && Array.isArray(m.keys)) return m;\n const pages = (m as PublicManifest).pages ?? [];\n return {\n version: m.version,\n generatedAt: \"\",\n keys: pages.map((p) => ({ contentKey: \"\", mode: \"server\" as const, pages: [p] })),\n };\n}\n\n/**\n * A provisional decision, to ask again soon:\n * - a Visitor Intent step that refines with Jev answered from its signals because Jev hasn't yet;\n * - Visitor Intent steps with no answer at all: the visitor's first signals may land any moment —\n * and for a visitor who is the same in every browser from another browser,\n * which this browser's cache facts (trail, clicks) never see.\n * Steps answered from signals alone change with the visitor's own views, which the cache facts do see.\n */\nfunction provisional(profile: ProfileFacts | null | undefined): boolean {\n const answers = profile?.answers;\n if (!answers || typeof answers !== \"object\") return false;\n const steps = Object.values(answers as Record<string, { choice?: string | null; source?: string; jev?: boolean } | null>);\n return steps.some((a) => a?.source === \"signals\" && a.jev === true) || (steps.length > 0 && steps.every((a) => !a?.choice));\n}\n\nexport function createXeniaEdge(config: XeniaEdgeConfig) {\n const baseUrl = config.baseUrl.replace(/\\/+$/, \"\");\n const budgetMs = config.budgetMs ?? 150;\n const manifestWaitMs = config.manifestWaitMs ?? 300;\n const manifestTtlMs = config.manifestTtlMs ?? 30_000;\n const decisionTtlMs = config.decisionTtlMs ?? 30 * 60_000;\n const provisionalDecisionTtlMs = config.provisionalDecisionTtlMs ?? 15_000;\n const cache = config.cache ?? memoryDecisionCache();\n const doFetch = config.fetch ?? fetch;\n const onError = config.onError ?? (() => {});\n const auth = { Authorization: `Bearer ${config.token}` };\n\n // A bundled manifest is used right away; manifestAt 0 makes the first request refresh it in the\n // background (currentManifest only blocks when there is no manifest at all).\n let manifest: Manifest | null = config.initialManifest?.version\n ? normalizeManifest(config.initialManifest)\n : null;\n let manifestAt = 0;\n let refreshing: Promise<void> | null = null;\n const inflight = new Map<string, Promise<DecideOutcome>>();\n\n /** Fetch the manifest (304-aware); keeps the last good one on failure. */\n function refreshManifest(): Promise<void> {\n refreshing ??= (async () => {\n try {\n const res = await doFetch(config.manifestUrl ?? `${baseUrl}/api/personalization/edge/manifest`, {\n headers: config.manifestUrl\n ? {}\n : { ...auth, ...(manifest ? { \"If-None-Match\": `\"${manifest.version}\"` } : {}) },\n signal: AbortSignal.timeout(5_000),\n cache: \"no-store\",\n });\n if (res.status === 304) {\n manifestAt = Date.now();\n } else if (res.ok) {\n manifest = normalizeManifest((await res.json()) as Manifest | PublicManifest);\n manifestAt = Date.now();\n } else {\n onError(new Error(`manifest ${res.status}`), \"manifest\");\n manifestAt = Date.now(); // back off for a TTL before retrying\n }\n } catch (e) {\n onError(e, \"manifest\");\n manifestAt = Date.now();\n } finally {\n refreshing = null;\n }\n })();\n return refreshing;\n }\n\n /** The current manifest; stale-while-revalidate, a bounded wait on cold start. */\n async function currentManifest(): Promise<Manifest | null> {\n if (Date.now() - manifestAt > manifestTtlMs) {\n const pending = refreshManifest();\n if (!manifest) {\n await Promise.race([pending, new Promise((r) => setTimeout(r, manifestWaitMs))]);\n }\n }\n return manifest;\n }\n\n /**\n * The content keys targeting this page, or `null` when no manifest is\n * available yet. An empty array means \"don't call Xenia\".\n */\n async function match(host: string | null, path: string): Promise<ManifestKey[] | null> {\n const m = await currentManifest();\n if (!m) return null;\n return m.keys.filter((k) => k.mode !== \"off\" && matchesAny(k.pages, host, path));\n }\n\n async function decide(keys: ManifestKey[], input: DecideInput): Promise<DecideOutcome> {\n const version = manifest?.version ?? \"\";\n // Known keys are sent explicitly; with a trimmed manifest there are none, and Xenia selects\n // the keys whose pages match the path. Either way the cache is shared by every page that\n // matches the same keys / patterns (e.g. all of /women/*).\n const contentKeys = keys.map((k) => k.contentKey).filter(Boolean).sort();\n const scope = contentKeys.length ? contentKeys.join(\",\") : keys.flatMap((k) => k.pages).sort().join(\",\");\n const cacheKey = [version, input.visitorId, scope, stableJson(input.cacheFacts)].join(\"|\");\n\n const cached = cache.get(cacheKey);\n if (cached) return { decisions: cached.decisions, profile: cached.profile, source: \"cache\", ms: 0 };\n\n // Concurrent requests from one visitor (HTML + RSC prefetches) share a call.\n const pending = inflight.get(cacheKey);\n if (pending) return pending;\n\n const call = (async (): Promise<DecideOutcome> => {\n const started = Date.now();\n try {\n const res = await doFetch(`${baseUrl}/api/personalization/edge/decide`, {\n method: \"POST\",\n headers: { ...auth, \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n contentKeys,\n host: input.host,\n path: input.path,\n visitorId: input.visitorId,\n facts: input.facts,\n ...(input.trail?.length ? { trail: input.trail } : {}),\n }),\n signal: AbortSignal.timeout(input.budgetMs ?? budgetMs),\n cache: \"no-store\",\n });\n if (!res.ok) {\n onError(new Error(`decide ${res.status}`), \"decide\");\n return { decisions: [], source: \"error\", ms: Date.now() - started };\n }\n const result = (await res.json()) as DecideResult | null;\n // A reply in the wrong shape is an error, never cached: it would break every view for the TTL.\n if (!Array.isArray(result?.decisions)) throw new Error(\"decide: reply has no decisions list\");\n const decisions = result.decisions.filter((d) => d && Array.isArray(d.swaps));\n const answer = { decisions, profile: result.profile };\n cache.set(cacheKey, answer, input.ttlMs?.(answer) ?? (provisional(result.profile) ? provisionalDecisionTtlMs : decisionTtlMs));\n if (manifest && result.version && result.version !== manifest.version) {\n void refreshManifest(); // journeys changed — pick up the new manifest\n }\n return { decisions, profile: result.profile, source: \"xenia\", ms: Date.now() - started };\n } catch (e) {\n const timedOut = e instanceof DOMException && e.name === \"TimeoutError\";\n if (!timedOut) onError(e, \"decide\");\n return { decisions: [], source: timedOut ? \"timeout\" : \"error\", ms: Date.now() - started };\n } finally {\n inflight.delete(cacheKey);\n }\n })();\n inflight.set(cacheKey, call);\n return call;\n }\n\n return { match, decide, refreshManifest };\n}\n\nexport type XeniaEdge = ReturnType<typeof createXeniaEdge>;\n"]}
1
+ {"version":3,"sources":["../../src/edge/matcher.ts","../../src/edge/client.ts","../../src/edge/variant-page.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Page matching for the Xenia manifest — a port of the backend's\n * `EdgePageMatcher`, which is the reference:\n *\n * - `/women` exactly that path (case, trailing slash, query ignored)\n * - `/women/*` `/women` and everything below it, not `/womenswear`\n * - `/*` every path\n * - `uk.example.com/women` that path on that host only; hostless patterns match any host\n */\n\nexport function normalizePath(path: string | null | undefined): string {\n if (!path || !path.trim()) return \"/\";\n let p = path.trim().toLowerCase();\n const cut = Math.min(\n ...[\"?\", \"#\"].map((c) => (p.includes(c) ? p.indexOf(c) : p.length)),\n );\n p = p.slice(0, cut);\n if (!p.startsWith(\"/\")) p = \"/\" + p;\n while (p.length > 1 && p.endsWith(\"/\")) p = p.slice(0, -1);\n return p;\n}\n\nconst normalizeHost = (host: string | null | undefined) =>\n (host ?? \"\").trim().toLowerCase().replace(/:\\d+$/, \"\");\n\nfunction matches(pattern: string, host: string, path: string): boolean {\n if (!pattern) return false;\n let patternPath = pattern;\n if (!pattern.startsWith(\"/\")) {\n const slash = pattern.indexOf(\"/\");\n const patternHost = slash < 0 ? pattern : pattern.slice(0, slash);\n if (patternHost !== host) return false;\n patternPath = slash < 0 ? \"/\" : pattern.slice(slash);\n }\n if (patternPath === \"/*\") return true;\n if (patternPath.endsWith(\"/*\")) {\n const prefix = patternPath.slice(0, -2);\n return path === prefix || path.startsWith(prefix + \"/\");\n }\n return path === patternPath;\n}\n\nexport function matchesAny(\n patterns: readonly string[] | null | undefined,\n host: string | null | undefined,\n path: string | null | undefined,\n): boolean {\n if (!patterns?.length) return false;\n const h = normalizeHost(host);\n const p = normalizePath(path);\n return patterns.some((pattern) => matches(pattern, h, p));\n}\n","/**\n * Proxy-side Xenia client for the hybrid personalization flow:\n *\n * 1. `match()` — synchronous, in-memory: does any journey target this page?\n * Answered from the manifest, refreshed in the background every\n * `manifestTtlMs`. Pages no journey targets never call Xenia.\n * 2. `decide()` — for targeted pages only: a per-visitor decision cache hit\n * costs nothing; a miss calls Xenia's `/decide` with a hard `budgetMs`\n * deadline. Timeouts and errors resolve to \"no decision\" (base content),\n * never an exception — Xenia can't slow down or break a page.\n *\n * Runtime-agnostic (only `fetch`, `AbortSignal`, `Map`). The decision cache is\n * per process; swap in `cache` for a shared store (Redis, edge KV).\n */\nimport type { EntryDecision } from \"../core/types\";\nimport { matchesAny } from \"./matcher\";\nimport type {\n DecideResult,\n DecisionSource,\n EdgeDecision,\n EdgeFacts,\n Manifest,\n ManifestKey,\n ProfileFacts,\n PublicManifest,\n TrailView,\n} from \"./types\";\n\n/** What a visitor's cached decision holds: the decisions and the profile they were made with. */\nexport type CachedDecision = {\n decisions: EdgeDecision[];\n entryDecisions?: Record<string, EntryDecision[]>;\n profile?: ProfileFacts | null;\n};\n\n/**\n * Where visitors' decisions are kept between page views. In memory per process by default; a shared\n * store (Redis, edge KV) may answer asynchronously — a store that fails or throws reads as a miss and\n * never breaks a page view, and writes never hold up the response.\n */\nexport type DecisionCache = {\n get(key: string): CachedDecision | undefined | Promise<CachedDecision | undefined>;\n set(key: string, value: CachedDecision, ttlMs: number): void | Promise<void>;\n};\n\nexport type XeniaEdgeConfig = {\n /** Xenia engine base URL, e.g. `https://engine.xeniamartech.com/xenia`. */\n baseUrl: string;\n /** A server-type SDK token. Never expose it to the browser. */\n token: string;\n /** Hard deadline for one `/decide` call. Default 150 ms. */\n budgetMs?: number;\n /**\n * Where to read the manifest. Default: Xenia's `/edge/manifest` (with the token). A storefront\n * can serve its own CDN-cached copy instead (e.g. a Next.js ISR route) — then no token is sent.\n */\n manifestUrl?: string;\n /**\n * A manifest baked in at build time. An instance starts with it in memory, so a cold start never\n * waits: the first request uses it and triggers a background refresh. Empty version = none.\n */\n initialManifest?: Manifest | PublicManifest;\n /** How long a cold start waits for the first manifest (only without `initialManifest`). Default 300 ms. */\n manifestWaitMs?: number;\n /** Manifest refresh interval. Default 30 s. */\n manifestTtlMs?: number;\n /** How long a visitor keeps a decision. Default 30 min (a session). */\n decisionTtlMs?: number;\n /**\n * How long a provisional decision is kept (see `provisional`): Jev's answer, or the visitor's first\n * signals, usually land seconds later, so the next view should ask again. Default 15 s.\n */\n provisionalDecisionTtlMs?: number;\n cache?: DecisionCache;\n fetch?: typeof fetch;\n onError?: (error: unknown, context: string) => void;\n};\n\nexport type DecideInput = {\n host: string | null;\n path: string;\n visitorId: string;\n facts: EdgeFacts;\n /** Facts that identify this visitor's context for caching (e.g. landing UTM, device). */\n cacheFacts?: EdgeFacts;\n /** The visitor's recent page views, most recent first (see `TrailView`). */\n trail?: TrailView[];\n /** This call's time budget, instead of the client's (a decision that calls a back office). */\n budgetMs?: number;\n /** How long to keep this call's answer, from what it said, instead of the client's. */\n ttlMs?: (answer: CachedDecision) => number;\n};\n\nexport type DecideOutcome = {\n decisions: EdgeDecision[];\n /** Multi-level entry decisions per content key (see `DecideResult.entryDecisions`). */\n entryDecisions?: Record<string, EntryDecision[]>;\n /** The profile Xenia decided with (also on cache hits), when profiles are on. */\n profile?: ProfileFacts | null;\n source: DecisionSource;\n /** Time spent in Xenia, 0 on a cache hit. */\n ms: number;\n};\n\n/** Bounded in-memory LRU with per-entry expiry. */\nexport function memoryDecisionCache(maxEntries = 10_000): DecisionCache {\n const entries = new Map<string, { value: CachedDecision; expires: number }>();\n return {\n get(key) {\n const hit = entries.get(key);\n if (!hit) return undefined;\n entries.delete(key);\n if (hit.expires < Date.now()) return undefined;\n entries.set(key, hit); // most recently used last\n return hit.value;\n },\n set(key, value, ttlMs) {\n entries.delete(key);\n entries.set(key, { value, expires: Date.now() + ttlMs });\n while (entries.size > maxEntries) {\n entries.delete(entries.keys().next().value as string);\n }\n },\n };\n}\n\nasync function readCache(cache: DecisionCache, key: string): Promise<CachedDecision | undefined> {\n try {\n return await cache.get(key);\n } catch {\n return undefined;\n }\n}\n\nfunction writeCache(cache: DecisionCache, key: string, value: CachedDecision, ttlMs: number): void {\n try {\n void Promise.resolve(cache.set(key, value, ttlMs)).catch(() => {});\n } catch {\n /* a cache that can't store is a cache that missed */\n }\n}\n\n/**\n * A per-process memory cache in front of a shared store: a returning visitor is answered from memory\n * when this instance served them, else from `shared` (another instance did), and only then by Xenia.\n * Reads from `shared` give up after `timeoutMs` (default 50) — a slow store must not cost more than\n * the decision it saves.\n */\nexport function tieredDecisionCache(\n shared: DecisionCache,\n opts: { memory?: DecisionCache; timeoutMs?: number } = {},\n): DecisionCache {\n const memory = opts.memory ?? memoryDecisionCache();\n const timeoutMs = opts.timeoutMs ?? 50;\n return {\n async get(key) {\n const local = await readCache(memory, key);\n if (local) return local;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const hit = await Promise.race([\n readCache(shared, key),\n new Promise<undefined>((resolve) => {\n timer = setTimeout(() => resolve(undefined), timeoutMs);\n }),\n ]).finally(() => clearTimeout(timer));\n // ponytail: the remaining TTL isn't known here, so a shared hit is kept in memory for 15 s (the\n // shortest decision TTL); pass the TTL through the store if a longer memory copy matters.\n if (hit) writeCache(memory, key, hit, 15_000);\n return hit;\n },\n set(key, value, ttlMs) {\n writeCache(memory, key, value, ttlMs);\n writeCache(shared, key, value, ttlMs);\n },\n };\n}\n\nconst stableJson = (facts: EdgeFacts | undefined) =>\n JSON.stringify(\n Object.keys(facts ?? {})\n .sort()\n .map((k) => [k, facts![k]]),\n );\n\n/**\n * Xenia's manifest, or a storefront's trimmed public copy (`{version, pages}`). The trimmed form\n * becomes one key-less entry per page pattern: the proxy then decides by path, and Xenia picks the\n * content keys itself.\n */\nfunction normalizeManifest(m: Manifest | PublicManifest): Manifest {\n if (\"keys\" in m && Array.isArray(m.keys)) return m;\n const pages = (m as PublicManifest).pages ?? [];\n return {\n version: m.version,\n generatedAt: \"\",\n keys: pages.map((p) => ({ contentKey: \"\", mode: \"server\" as const, pages: [p] })),\n };\n}\n\n/**\n * A provisional decision, to ask again soon:\n * - a Visitor Intent step that refines with Jev answered from its signals because Jev hasn't yet;\n * - Visitor Intent steps with no answer at all: the visitor's first signals may land any moment —\n * and for a visitor who is the same in every browser from another browser,\n * which this browser's cache facts (trail, clicks) never see.\n * Steps answered from signals alone change with the visitor's own views, which the cache facts do see.\n */\nfunction provisional(profile: ProfileFacts | null | undefined): boolean {\n const answers = profile?.answers;\n if (!answers || typeof answers !== \"object\") return false;\n const steps = Object.values(answers as Record<string, { choice?: string | null; source?: string; jev?: boolean } | null>);\n return steps.some((a) => a?.source === \"signals\" && a.jev === true) || (steps.length > 0 && steps.every((a) => !a?.choice));\n}\n\nexport function createXeniaEdge(config: XeniaEdgeConfig) {\n const baseUrl = config.baseUrl.replace(/\\/+$/, \"\");\n const budgetMs = config.budgetMs ?? 150;\n const manifestWaitMs = config.manifestWaitMs ?? 300;\n const manifestTtlMs = config.manifestTtlMs ?? 30_000;\n const decisionTtlMs = config.decisionTtlMs ?? 30 * 60_000;\n const provisionalDecisionTtlMs = config.provisionalDecisionTtlMs ?? 15_000;\n const cache = config.cache ?? memoryDecisionCache();\n const doFetch = config.fetch ?? fetch;\n const onError = config.onError ?? (() => {});\n const auth = { Authorization: `Bearer ${config.token}` };\n\n // A bundled manifest is used right away; manifestAt 0 makes the first request refresh it in the\n // background (currentManifest only blocks when there is no manifest at all).\n let manifest: Manifest | null = config.initialManifest?.version\n ? normalizeManifest(config.initialManifest)\n : null;\n let manifestAt = 0;\n let refreshing: Promise<void> | null = null;\n const inflight = new Map<string, Promise<DecideOutcome>>();\n\n /** Fetch the manifest (304-aware); keeps the last good one on failure. */\n function refreshManifest(): Promise<void> {\n refreshing ??= (async () => {\n try {\n const res = await doFetch(config.manifestUrl ?? `${baseUrl}/api/personalization/edge/manifest`, {\n headers: config.manifestUrl\n ? {}\n : { ...auth, ...(manifest ? { \"If-None-Match\": `\"${manifest.version}\"` } : {}) },\n signal: AbortSignal.timeout(5_000),\n cache: \"no-store\",\n });\n if (res.status === 304) {\n manifestAt = Date.now();\n } else if (res.ok) {\n manifest = normalizeManifest((await res.json()) as Manifest | PublicManifest);\n manifestAt = Date.now();\n } else {\n onError(new Error(`manifest ${res.status}`), \"manifest\");\n manifestAt = Date.now(); // back off for a TTL before retrying\n }\n } catch (e) {\n onError(e, \"manifest\");\n manifestAt = Date.now();\n } finally {\n refreshing = null;\n }\n })();\n return refreshing;\n }\n\n /** The current manifest; stale-while-revalidate, a bounded wait on cold start. */\n async function currentManifest(): Promise<Manifest | null> {\n if (Date.now() - manifestAt > manifestTtlMs) {\n const pending = refreshManifest();\n if (!manifest) {\n await Promise.race([pending, new Promise((r) => setTimeout(r, manifestWaitMs))]);\n }\n }\n return manifest;\n }\n\n /**\n * The content keys targeting this page, or `null` when no manifest is\n * available yet. An empty array means \"don't call Xenia\".\n */\n async function match(host: string | null, path: string): Promise<ManifestKey[] | null> {\n const m = await currentManifest();\n if (!m) return null;\n return m.keys.filter((k) => k.mode !== \"off\" && matchesAny(k.pages, host, path));\n }\n\n async function decide(keys: ManifestKey[], input: DecideInput): Promise<DecideOutcome> {\n const version = manifest?.version ?? \"\";\n // Known keys are sent explicitly; with a trimmed manifest there are none, and Xenia selects\n // the keys whose pages match the path. Either way the cache is shared by every page that\n // matches the same keys / patterns (e.g. all of /women/*).\n const contentKeys = keys.map((k) => k.contentKey).filter(Boolean).sort();\n const scope = contentKeys.length ? contentKeys.join(\",\") : keys.flatMap((k) => k.pages).sort().join(\",\");\n const cacheKey = [version, input.visitorId, scope, stableJson(input.cacheFacts)].join(\"|\");\n\n const cached = await readCache(cache, cacheKey);\n if (cached) {\n return { decisions: cached.decisions, entryDecisions: cached.entryDecisions, profile: cached.profile, source: \"cache\", ms: 0 };\n }\n\n // Concurrent requests from one visitor (HTML + RSC prefetches) share a call.\n const pending = inflight.get(cacheKey);\n if (pending) return pending;\n\n const call = (async (): Promise<DecideOutcome> => {\n const started = Date.now();\n try {\n const res = await doFetch(`${baseUrl}/api/personalization/edge/decide`, {\n method: \"POST\",\n headers: { ...auth, \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n contentKeys,\n host: input.host,\n path: input.path,\n visitorId: input.visitorId,\n facts: input.facts,\n ...(input.trail?.length ? { trail: input.trail } : {}),\n }),\n signal: AbortSignal.timeout(input.budgetMs ?? budgetMs),\n cache: \"no-store\",\n });\n if (!res.ok) {\n onError(new Error(`decide ${res.status}`), \"decide\");\n return { decisions: [], source: \"error\", ms: Date.now() - started };\n }\n const result = (await res.json()) as DecideResult | null;\n // A reply in the wrong shape is an error, never cached: it would break every view for the TTL.\n if (!Array.isArray(result?.decisions)) throw new Error(\"decide: reply has no decisions list\");\n const decisions = result.decisions.filter((d) => d && Array.isArray(d.swaps));\n const answer = { decisions, entryDecisions: result.entryDecisions, profile: result.profile };\n writeCache(cache, cacheKey, answer, input.ttlMs?.(answer) ?? (provisional(result.profile) ? provisionalDecisionTtlMs : decisionTtlMs));\n if (manifest && result.version && result.version !== manifest.version) {\n void refreshManifest(); // journeys changed — pick up the new manifest\n }\n return { decisions, entryDecisions: result.entryDecisions, profile: result.profile, source: \"xenia\", ms: Date.now() - started };\n } catch (e) {\n const timedOut = e instanceof DOMException && e.name === \"TimeoutError\";\n if (!timedOut) onError(e, \"decide\");\n return { decisions: [], source: timedOut ? \"timeout\" : \"error\", ms: Date.now() - started };\n } finally {\n inflight.delete(cacheKey);\n }\n })();\n inflight.set(cacheKey, call);\n return call;\n }\n\n return { match, decide, refreshManifest };\n}\n\nexport type XeniaEdge = ReturnType<typeof createXeniaEdge>;\n","/**\n * The variant page of a CMS page journey served at the edge: the proxy decided every level of the\n * page with one `/decide` call and put the decisions in the variant token, so the page personalizes\n * itself from the token — no Xenia call, only CMS fetches — and can be cached per variant.\n */\n\nimport {\n personalizeContent,\n type PageRef,\n type PersonalizeContentOptions,\n type PersonalizeContentResult,\n} from \"../core/personalize-content\";\nimport { decodeVariant, variantEntryResolver, type DecodedVariant } from \"./variant\";\n\n/**\n * `personalizeContent` options for a variant page. `contentKey` is optional: without it, the page is\n * personalized for every content key the token decided (a page several journeys personalize).\n */\nexport type VariantPersonalizeOptions = Omit<PersonalizeContentOptions, \"resolve\" | \"contentKey\"> & {\n contentKey?: string;\n};\n\n/**\n * Personalize a page you fetched yourself from a decoded variant (`decodeVariant`). Never throws;\n * `complete` is false when the token had to leave decisions out or a CMS fetch failed.\n */\nexport async function personalizeVariantContent<T>(\n content: T,\n variant: Pick<DecodedVariant, \"entries\" | \"entriesDropped\"> | null | undefined,\n options: VariantPersonalizeOptions,\n): Promise<PersonalizeContentResult<T>> {\n const entries = variant?.entries ?? [];\n const keys = options.contentKey ? [options.contentKey] : [...new Set(entries.map((e) => e.contentKey))];\n let result: PersonalizeContentResult<T> = { content, applied: [], complete: true };\n for (const contentKey of keys) {\n const next = await personalizeContent(result.content, {\n ...options,\n contentKey,\n resolve: variantEntryResolver(entries, contentKey),\n // Each key's run copies the page unless asked not to; after the first, ours is already a copy.\n inPlace: result.content !== content || options.inPlace,\n });\n result = {\n content: next.content,\n applied: [...result.applied, ...next.applied],\n complete: result.complete && next.complete,\n ...(next.error !== undefined || result.error !== undefined ? { error: next.error ?? result.error } : {}),\n };\n }\n return variant?.entriesDropped ? { ...result, complete: false } : result;\n}\n\n/**\n * The whole variant page in one call: verify the token, fetch the page through `options.cms`, and\n * personalize it from the token's decisions. A token that doesn't verify renders the base page\n * (static swaps still apply). Never throws; `content` is null when the page doesn't exist.\n *\n * ```ts\n * // app/xv/[token]/[[...slug]]/page.tsx\n * const { content, variant } = await personalizeVariantPage(token, { contentType: \"page\", fields: { slug } }, {\n * secret: process.env.XENIA_VARIANT_SECRET!, cms, contentKey: \"home-main\",\n * });\n * ```\n */\nexport async function personalizeVariantPage<T, E = unknown>(\n token: string,\n ref: PageRef,\n options: VariantPersonalizeOptions & { secret: string },\n): Promise<PersonalizeContentResult<T | null> & { variant: DecodedVariant<E> | null }> {\n const { secret, ...rest } = options;\n const variant = await decodeVariant<E>(safeDecode(token), secret);\n let page: T | null;\n try {\n if (!rest.cms.fetchPage) throw new Error(\"This CMS adapter cannot fetch pages; use personalizeVariantContent(page)\");\n page = (await rest.cms.fetchPage(ref, rest.locale)) as T | null;\n } catch (error) {\n return { content: null, applied: [], complete: false, error, variant };\n }\n if (page == null) return { content: null, applied: [], complete: true, variant };\n return { ...(await personalizeVariantContent(page, variant, { ...rest, inPlace: true })), variant };\n}\n\n/** Next.js hands route params still percent-encoded in some runtimes. */\nfunction safeDecode(token: string): string {\n try {\n return decodeURIComponent(token);\n } catch {\n return token;\n }\n}\n"]}