@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.
- package/README.md +70 -6
- package/dist/{actions-35V37IT0.d.cts → actions--1_9Z8ig.d.cts} +13 -2
- package/dist/{actions-35V37IT0.d.ts → actions--1_9Z8ig.d.ts} +13 -2
- package/dist/{api-0IycTYJR.d.cts → api-DbUxT38T.d.ts} +1 -1
- package/dist/{api-COXWLL5L.d.ts → api-DgzJ5s-k.d.cts} +1 -1
- package/dist/chunk-DPXINHQR.cjs +34 -0
- package/dist/chunk-DPXINHQR.cjs.map +1 -0
- package/dist/chunk-FSJMHXS6.js +432 -0
- package/dist/chunk-FSJMHXS6.js.map +1 -0
- package/dist/chunk-GXJLU3RE.cjs +448 -0
- package/dist/chunk-GXJLU3RE.cjs.map +1 -0
- package/dist/{chunk-2DR5AVPX.cjs → chunk-I5EHLRSR.cjs} +11 -10
- package/dist/chunk-I5EHLRSR.cjs.map +1 -0
- package/dist/{chunk-LW5AEJ6K.js → chunk-MEULT5X6.js} +4 -241
- package/dist/chunk-MEULT5X6.js.map +1 -0
- package/dist/{chunk-GWRDZIVA.js → chunk-NDRRSRTU.js} +4 -3
- package/dist/chunk-NDRRSRTU.js.map +1 -0
- package/dist/chunk-RC46KXDM.js +32 -0
- package/dist/chunk-RC46KXDM.js.map +1 -0
- package/dist/{chunk-RWE2OCQL.cjs → chunk-SU25SELD.cjs} +4 -244
- package/dist/chunk-SU25SELD.cjs.map +1 -0
- package/dist/{chunk-WHGK55JP.cjs → chunk-UUIKUU5W.cjs} +5 -5
- package/dist/{chunk-WHGK55JP.cjs.map → chunk-UUIKUU5W.cjs.map} +1 -1
- package/dist/{chunk-QEYONKWY.js → chunk-UVTFGQR6.js} +3 -3
- package/dist/{chunk-QEYONKWY.js.map → chunk-UVTFGQR6.js.map} +1 -1
- package/dist/{chunk-OMMIRSPK.js → chunk-V4EGU3PM.js} +48 -11
- package/dist/chunk-V4EGU3PM.js.map +1 -0
- package/dist/{chunk-YLMMTKQL.cjs → chunk-WMGCQPHY.cjs} +52 -13
- package/dist/chunk-WMGCQPHY.cjs.map +1 -0
- package/dist/client/index.cjs +26 -10
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +2 -2
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.js +26 -10
- package/dist/client/index.js.map +1 -1
- package/dist/client/react/index.cjs +73 -15
- package/dist/client/react/index.cjs.map +1 -1
- package/dist/client/react/index.d.cts +9 -3
- package/dist/client/react/index.d.ts +9 -3
- package/dist/client/react/index.js +73 -15
- package/dist/client/react/index.js.map +1 -1
- package/dist/cms/contentful.cjs +7 -24
- package/dist/cms/contentful.cjs.map +1 -1
- package/dist/cms/contentful.d.cts +5 -4
- package/dist/cms/contentful.d.ts +5 -4
- package/dist/cms/contentful.js +4 -23
- package/dist/cms/contentful.js.map +1 -1
- package/dist/cms/contentstack.cjs +120 -0
- package/dist/cms/contentstack.cjs.map +1 -0
- package/dist/cms/contentstack.d.cts +41 -0
- package/dist/cms/contentstack.d.ts +41 -0
- package/dist/cms/contentstack.js +117 -0
- package/dist/cms/contentstack.js.map +1 -0
- package/dist/{consent-DRCV0N0d.d.ts → consent-Dtiqzz2B.d.cts} +71 -11
- package/dist/{consent-BgAETCkL.d.cts → consent-byYiDMFr.d.ts} +71 -11
- package/dist/{cookie-names-DU3MtcNG.d.ts → cookie-names-B4hda2rX.d.cts} +1 -1
- package/dist/{cookie-names-DFItnSLK.d.cts → cookie-names-DioZXuV0.d.ts} +1 -1
- package/dist/edge/index.cjs +122 -29
- package/dist/edge/index.cjs.map +1 -1
- package/dist/edge/index.d.cts +42 -6
- package/dist/edge/index.d.ts +42 -6
- package/dist/edge/index.js +92 -9
- package/dist/edge/index.js.map +1 -1
- package/dist/index.cjs +31 -31
- package/dist/index.d.cts +4 -5
- package/dist/index.d.ts +4 -5
- package/dist/index.js +2 -2
- package/dist/next/index.cjs +39 -34
- package/dist/next/index.cjs.map +1 -1
- package/dist/next/index.d.cts +2 -3
- package/dist/next/index.d.ts +2 -3
- package/dist/next/index.js +13 -8
- package/dist/next/index.js.map +1 -1
- package/dist/{types-DgVHS4Qw.d.ts → personalize-content-D6xeBI7-.d.cts} +150 -1
- package/dist/{types-DgVHS4Qw.d.cts → personalize-content-D6xeBI7-.d.ts} +150 -1
- package/dist/server/index.cjs +30 -30
- package/dist/server/index.d.cts +5 -6
- package/dist/server/index.d.ts +5 -6
- package/dist/server/index.js +5 -5
- package/dist/xenia-sdk.iife.js +3 -3
- package/dist/xenia-sdk.iife.js.map +1 -1
- package/dist/{xenia-server-Cf24mL0c.d.ts → xenia-server-BcgeUHX4.d.ts} +2 -3
- package/dist/{xenia-server-D-bx14lw.d.cts → xenia-server-lv6LtJZP.d.cts} +2 -3
- package/package.json +14 -1
- package/dist/chunk-2DR5AVPX.cjs.map +0 -1
- package/dist/chunk-BQ5PJCZ7.js +0 -179
- package/dist/chunk-BQ5PJCZ7.js.map +0 -1
- package/dist/chunk-GWRDZIVA.js.map +0 -1
- package/dist/chunk-LW5AEJ6K.js.map +0 -1
- package/dist/chunk-N5M4PI7C.cjs +0 -192
- package/dist/chunk-N5M4PI7C.cjs.map +0 -1
- package/dist/chunk-OMMIRSPK.js.map +0 -1
- package/dist/chunk-RWE2OCQL.cjs.map +0 -1
- package/dist/chunk-YLMMTKQL.cjs.map +0 -1
- package/dist/personalize-content-Cwc2AomC.d.ts +0 -141
- package/dist/personalize-content-CxSdplMV.d.cts +0 -141
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
// src/core/http-client.ts
|
|
2
|
+
var DEFAULT_BASE_URL = "https://engine.xeniamartech.com/xenia";
|
|
3
|
+
function isBrowser() {
|
|
4
|
+
return typeof window !== "undefined" && typeof document !== "undefined";
|
|
5
|
+
}
|
|
6
|
+
function buildUrl(url, queryParams) {
|
|
7
|
+
if (!queryParams || Object.keys(queryParams).length === 0) return url;
|
|
8
|
+
const params = new URLSearchParams(queryParams);
|
|
9
|
+
const sep = url.includes("?") ? "&" : "?";
|
|
10
|
+
return `${url}${sep}${params.toString()}`;
|
|
11
|
+
}
|
|
12
|
+
function delay(ms) {
|
|
13
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
14
|
+
}
|
|
15
|
+
var HttpError = class extends Error {
|
|
16
|
+
constructor(status, data, message) {
|
|
17
|
+
super(message ?? `Request failed with status ${status}`);
|
|
18
|
+
this.status = status;
|
|
19
|
+
this.data = data;
|
|
20
|
+
this.name = "HttpError";
|
|
21
|
+
}
|
|
22
|
+
status;
|
|
23
|
+
data;
|
|
24
|
+
};
|
|
25
|
+
var CircuitOpenError = class extends Error {
|
|
26
|
+
constructor(endpoint) {
|
|
27
|
+
super(`Circuit open for ${endpoint}: failing fast until the cooldown passes`);
|
|
28
|
+
this.endpoint = endpoint;
|
|
29
|
+
this.name = "CircuitOpenError";
|
|
30
|
+
}
|
|
31
|
+
endpoint;
|
|
32
|
+
};
|
|
33
|
+
var breakers = /* @__PURE__ */ new Map();
|
|
34
|
+
var HttpClient = class {
|
|
35
|
+
baseUrl;
|
|
36
|
+
defaultHeaders;
|
|
37
|
+
timeout;
|
|
38
|
+
retry;
|
|
39
|
+
debug;
|
|
40
|
+
breaker;
|
|
41
|
+
constructor(opts) {
|
|
42
|
+
this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
43
|
+
this.defaultHeaders = opts.defaultHeaders ?? {};
|
|
44
|
+
this.timeout = opts.timeout ?? 8e3;
|
|
45
|
+
this.retry = {
|
|
46
|
+
attempts: opts.retry?.attempts ?? 2,
|
|
47
|
+
baseDelayMs: opts.retry?.baseDelayMs ?? 300
|
|
48
|
+
};
|
|
49
|
+
this.debug = opts.debug ?? false;
|
|
50
|
+
const cb = opts.circuitBreaker === true ? {} : opts.circuitBreaker || null;
|
|
51
|
+
this.breaker = cb && { failureThreshold: cb.failureThreshold ?? 3, cooldownMs: cb.cooldownMs ?? 3e4 };
|
|
52
|
+
}
|
|
53
|
+
async request(opts) {
|
|
54
|
+
if (!this.breaker) return this.send(opts);
|
|
55
|
+
const key = buildUrl(this.baseUrl + opts.path, opts.query);
|
|
56
|
+
const b = breakers.get(key) ?? { failures: 0, openedAt: 0 };
|
|
57
|
+
if (b.openedAt) {
|
|
58
|
+
if (Date.now() - b.openedAt < this.breaker.cooldownMs) throw new CircuitOpenError(key);
|
|
59
|
+
b.openedAt = 0;
|
|
60
|
+
b.failures = this.breaker.failureThreshold - 1;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const result = await this.send(opts);
|
|
64
|
+
breakers.delete(key);
|
|
65
|
+
return result;
|
|
66
|
+
} catch (err) {
|
|
67
|
+
if (err instanceof HttpError && !this.isRetryableStatus(err.status)) {
|
|
68
|
+
breakers.delete(key);
|
|
69
|
+
throw err;
|
|
70
|
+
}
|
|
71
|
+
b.failures += 1;
|
|
72
|
+
if (b.failures >= this.breaker.failureThreshold) {
|
|
73
|
+
b.openedAt = Date.now();
|
|
74
|
+
this.log(`circuit opened for ${opts.path} after ${b.failures} failures`);
|
|
75
|
+
}
|
|
76
|
+
breakers.set(key, b);
|
|
77
|
+
throw err;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async send(opts) {
|
|
81
|
+
const url = buildUrl(this.baseUrl + opts.path, opts.query);
|
|
82
|
+
const method = opts.method ?? "GET";
|
|
83
|
+
const headers = {
|
|
84
|
+
"Content-Type": "application/json",
|
|
85
|
+
...this.defaultHeaders,
|
|
86
|
+
...opts.headers
|
|
87
|
+
};
|
|
88
|
+
const body = opts.body !== void 0 ? JSON.stringify(opts.body) : void 0;
|
|
89
|
+
const timeout = opts.timeout ?? this.timeout;
|
|
90
|
+
const maxAttempts = opts.noRetry ? 1 : (opts.retry?.attempts ?? this.retry.attempts) + 1;
|
|
91
|
+
const baseDelay = opts.retry?.baseDelayMs ?? this.retry.baseDelayMs;
|
|
92
|
+
let lastError;
|
|
93
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
94
|
+
try {
|
|
95
|
+
const response = await this.fetchWithTimeout(
|
|
96
|
+
url,
|
|
97
|
+
{ method, headers, body, ...opts.keepalive ? { keepalive: true } : {} },
|
|
98
|
+
timeout
|
|
99
|
+
);
|
|
100
|
+
const data = await this.parseBody(response);
|
|
101
|
+
if (response.ok) {
|
|
102
|
+
return { response, data };
|
|
103
|
+
}
|
|
104
|
+
if (this.isRetryableStatus(response.status) && attempt < maxAttempts - 1) {
|
|
105
|
+
const wait = this.retryAfterMs(response, baseDelay, attempt);
|
|
106
|
+
this.log(`retrying ${opts.path} after ${wait}ms (status ${response.status})`);
|
|
107
|
+
await delay(wait);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
throw new HttpError(response.status, data);
|
|
111
|
+
} catch (err) {
|
|
112
|
+
lastError = err;
|
|
113
|
+
if (err instanceof HttpError) throw err;
|
|
114
|
+
if (attempt < maxAttempts - 1) {
|
|
115
|
+
const wait = baseDelay * 2 ** attempt;
|
|
116
|
+
this.log(`retrying ${opts.path} after ${wait}ms (network error)`);
|
|
117
|
+
await delay(wait);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
throw lastError instanceof Error ? lastError : new Error("Request failed");
|
|
123
|
+
}
|
|
124
|
+
async fetchWithTimeout(url, init, timeout) {
|
|
125
|
+
const controller = new AbortController();
|
|
126
|
+
const timer = setTimeout(() => controller.abort(), timeout);
|
|
127
|
+
try {
|
|
128
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
129
|
+
} catch (err) {
|
|
130
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
131
|
+
throw new Error(`Request timeout after ${timeout}ms`, { cause: err });
|
|
132
|
+
}
|
|
133
|
+
throw err;
|
|
134
|
+
} finally {
|
|
135
|
+
clearTimeout(timer);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async parseBody(response) {
|
|
139
|
+
const text = await response.text().catch(() => "");
|
|
140
|
+
if (!text) return null;
|
|
141
|
+
try {
|
|
142
|
+
return JSON.parse(text);
|
|
143
|
+
} catch {
|
|
144
|
+
return text;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
isRetryableStatus(status) {
|
|
148
|
+
return status === 429 || status >= 500 && status <= 599;
|
|
149
|
+
}
|
|
150
|
+
retryAfterMs(response, baseDelay, attempt) {
|
|
151
|
+
const header = response.headers.get("Retry-After");
|
|
152
|
+
if (header) {
|
|
153
|
+
const seconds = Number(header);
|
|
154
|
+
if (!Number.isNaN(seconds)) return seconds * 1e3;
|
|
155
|
+
}
|
|
156
|
+
return baseDelay * 2 ** attempt;
|
|
157
|
+
}
|
|
158
|
+
log(msg) {
|
|
159
|
+
if (this.debug) console.warn(`[xenia] ${msg}`);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// src/core/cookie-names.ts
|
|
164
|
+
var DEFAULT_PREFIX = "xenia";
|
|
165
|
+
function visitorCookieName(prefix = DEFAULT_PREFIX) {
|
|
166
|
+
return `${prefix || DEFAULT_PREFIX}_vid`;
|
|
167
|
+
}
|
|
168
|
+
function consentCookieName(prefix = DEFAULT_PREFIX) {
|
|
169
|
+
return `${prefix || DEFAULT_PREFIX}_consent`;
|
|
170
|
+
}
|
|
171
|
+
var VISITOR_COOKIE = visitorCookieName();
|
|
172
|
+
var CONSENT_COOKIE = consentCookieName();
|
|
173
|
+
function consentCookie(state, prefix, maxAgeDays = 365) {
|
|
174
|
+
return `${consentCookieName(prefix)}=${encodeURIComponent(JSON.stringify(state))}; path=/; max-age=${Math.floor(maxAgeDays * 86400)}; SameSite=Lax`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// src/core/personalize-content.ts
|
|
178
|
+
var DEFAULT_TIMEOUT_MS = 300;
|
|
179
|
+
var DEFAULT_MAX_ROUNDS = 3;
|
|
180
|
+
var copyOf = (v) => v && typeof v === "object" && typeof structuredClone === "function" ? structuredClone(v) : v;
|
|
181
|
+
async function personalizeContent(payload, options) {
|
|
182
|
+
const cms = scopedAdapter(options.cms, options.contentKey);
|
|
183
|
+
const maxRounds = options.maxRounds ?? DEFAULT_MAX_ROUNDS;
|
|
184
|
+
const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
185
|
+
const applied = [];
|
|
186
|
+
const report = (swap) => {
|
|
187
|
+
applied.push(swap);
|
|
188
|
+
try {
|
|
189
|
+
options.onApplied?.(swap);
|
|
190
|
+
} catch {
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
let content = payload;
|
|
194
|
+
try {
|
|
195
|
+
if (!options.inPlace && typeof structuredClone === "function") content = structuredClone(content);
|
|
196
|
+
content = cms.prepare ? cms.prepare(content) : content;
|
|
197
|
+
} catch (error) {
|
|
198
|
+
return { content: payload, applied, complete: false, error };
|
|
199
|
+
}
|
|
200
|
+
const box = { root: content };
|
|
201
|
+
const settled = /* @__PURE__ */ new WeakSet();
|
|
202
|
+
const decided = /* @__PURE__ */ new Map();
|
|
203
|
+
let frontier = [{ parent: box, key: "root", value: box.root }];
|
|
204
|
+
const settle = (target, d, round) => {
|
|
205
|
+
settled.add(target.entry);
|
|
206
|
+
cms.clearMarker?.(target.entry);
|
|
207
|
+
const items = d.items.map(copyOf);
|
|
208
|
+
if (!d.decision || items.length === 0) {
|
|
209
|
+
return deferredSlots(target, cms);
|
|
210
|
+
}
|
|
211
|
+
for (const item of items) if (item && typeof item === "object") settled.add(item);
|
|
212
|
+
const op = d.decision.op ?? target.marker.op ?? "replace";
|
|
213
|
+
const next = apply(target, op, d.decision.field ?? target.marker.field, items, cms);
|
|
214
|
+
report({
|
|
215
|
+
target: target.id,
|
|
216
|
+
kind: target.local ? "static" : "runtime",
|
|
217
|
+
op,
|
|
218
|
+
entryIds: items.map((e) => cms.entryId(e)).filter((id) => !!id),
|
|
219
|
+
contentKey: d.decision.contentKey,
|
|
220
|
+
bucket: d.decision.bucket,
|
|
221
|
+
round
|
|
222
|
+
});
|
|
223
|
+
return next;
|
|
224
|
+
};
|
|
225
|
+
for (let round = 0; ; round++) {
|
|
226
|
+
const pending = walk(frontier, cms, settled, round, report, options.contentKey, (t) => {
|
|
227
|
+
const d = decided.get(t.id);
|
|
228
|
+
return d ? settle(t, d, round) : void 0;
|
|
229
|
+
});
|
|
230
|
+
if (pending.size === 0) return { content: box.root, applied, complete: true };
|
|
231
|
+
const remote = [...pending.values()].filter((p) => !p.local).map((p) => p.id);
|
|
232
|
+
const local = [...pending.values()].flatMap((p) => p.local ? [p.local] : []);
|
|
233
|
+
if (!options.resolve && local.length === 0 || round >= maxRounds) {
|
|
234
|
+
return { content: box.root, applied, complete: !options.resolve && round < maxRounds };
|
|
235
|
+
}
|
|
236
|
+
progress(options, { content: box.root, round, pending: [...pending.keys()] });
|
|
237
|
+
let decisions;
|
|
238
|
+
let entriesById;
|
|
239
|
+
let queried;
|
|
240
|
+
try {
|
|
241
|
+
decisions = options.resolve && remote.length > 0 ? await withDeadline(options.resolve(remote), deadline) : [];
|
|
242
|
+
decisions = [...decisions, ...local];
|
|
243
|
+
({ entriesById, queried } = await withDeadline(loadReplacements(decisions, cms, options.locale), deadline));
|
|
244
|
+
} catch (error) {
|
|
245
|
+
return { content: box.root, applied, complete: false, error: error === TIMEOUT ? void 0 : error };
|
|
246
|
+
}
|
|
247
|
+
const byTarget = new Map(decisions.map((d) => [d.target, d]));
|
|
248
|
+
frontier = [];
|
|
249
|
+
for (const target of pending.values()) {
|
|
250
|
+
const decision = byTarget.get(target.id);
|
|
251
|
+
const items = decision ? decision.ids ? decision.ids.map((id) => entriesById.get(id)).filter((e) => e !== void 0) : queried.get(decision) ?? [] : [];
|
|
252
|
+
const d = { decision, items };
|
|
253
|
+
decided.set(target.id, d);
|
|
254
|
+
frontier.push(...settle(target, d, round));
|
|
255
|
+
}
|
|
256
|
+
progress(options, { content: box.root, round, pending: [] });
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function progress(options, p) {
|
|
260
|
+
try {
|
|
261
|
+
options.onProgress?.(p);
|
|
262
|
+
} catch {
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
function walk(frontier, cms, settled, round, report, contentKey, reuse) {
|
|
266
|
+
const pending = /* @__PURE__ */ new Map();
|
|
267
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
268
|
+
const deferred = /* @__PURE__ */ new WeakSet();
|
|
269
|
+
const queue = [...frontier];
|
|
270
|
+
while (queue.length > 0) {
|
|
271
|
+
const slot = queue.shift();
|
|
272
|
+
const value = slot.value;
|
|
273
|
+
if (!value || typeof value !== "object" || deferred.has(value)) continue;
|
|
274
|
+
const id = settled.has(value) ? void 0 : cms.entryId(value);
|
|
275
|
+
const marker = id ? cms.marker(value) : null;
|
|
276
|
+
const local = id && marker?.kind === "static" && marker.ids?.length && cms.staticVariants(value).length === 0 ? localDecision(id, marker, contentKey) : void 0;
|
|
277
|
+
if (id && marker && (marker.kind === "runtime" || local)) {
|
|
278
|
+
const again = reuse({ id, marker, entry: value, slots: [slot] });
|
|
279
|
+
if (again) {
|
|
280
|
+
queue.push(...again);
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
const existing = pending.get(id);
|
|
284
|
+
if (existing) {
|
|
285
|
+
existing.slots.push(slot);
|
|
286
|
+
} else {
|
|
287
|
+
pending.set(id, { id, marker, entry: value, slots: [slot], ...local ? { local } : {} });
|
|
288
|
+
if (marker.op === "items" && marker.field) {
|
|
289
|
+
const held = cms.getField(value, marker.field);
|
|
290
|
+
if (held && typeof held === "object") deferred.add(held);
|
|
291
|
+
queue.push(...children(value));
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
if (id && marker?.kind === "static") {
|
|
297
|
+
const variants = cms.staticVariants(value).filter((v) => v && typeof v === "object");
|
|
298
|
+
settled.add(value);
|
|
299
|
+
cms.clearMarker?.(value);
|
|
300
|
+
if (variants.length > 0) {
|
|
301
|
+
for (const v of variants) settled.add(v);
|
|
302
|
+
const op = marker.op ?? "replace";
|
|
303
|
+
const target = { entry: value, slots: [slot] };
|
|
304
|
+
queue.push(...apply(target, op, marker.field, variants, cms));
|
|
305
|
+
report({
|
|
306
|
+
target: id,
|
|
307
|
+
kind: "static",
|
|
308
|
+
op,
|
|
309
|
+
entryIds: variants.map((v) => cms.entryId(v)).filter((x) => !!x),
|
|
310
|
+
round
|
|
311
|
+
});
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (seen.has(value)) continue;
|
|
316
|
+
seen.add(value);
|
|
317
|
+
queue.push(...children(value));
|
|
318
|
+
}
|
|
319
|
+
return pending;
|
|
320
|
+
}
|
|
321
|
+
function localDecision(id, marker, contentKey) {
|
|
322
|
+
return {
|
|
323
|
+
target: id,
|
|
324
|
+
op: marker.op ?? "replace",
|
|
325
|
+
...marker.field ? { field: marker.field } : {},
|
|
326
|
+
ids: marker.ids,
|
|
327
|
+
...marker.ct ? { contentType: marker.ct } : {},
|
|
328
|
+
contentKey
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
function apply(target, op, field, items, cms) {
|
|
332
|
+
if (op === "items") {
|
|
333
|
+
if (field) {
|
|
334
|
+
cms.setField(target.entry, field, items);
|
|
335
|
+
return [{ parent: target.entry, key: field, value: cms.getField(target.entry, field) }];
|
|
336
|
+
}
|
|
337
|
+
if (cms.mergeItems) {
|
|
338
|
+
cms.mergeItems(target.entry, items[0]);
|
|
339
|
+
return target.slots.map((s) => ({ ...s, value: target.entry }));
|
|
340
|
+
}
|
|
341
|
+
op = "replace";
|
|
342
|
+
}
|
|
343
|
+
const next = [];
|
|
344
|
+
for (const slot of target.slots) {
|
|
345
|
+
const placed = op === "expand" && Array.isArray(slot.parent) ? items : [items[0]];
|
|
346
|
+
if (Array.isArray(slot.parent)) {
|
|
347
|
+
const index = slot.parent.indexOf(target.entry);
|
|
348
|
+
if (index < 0) continue;
|
|
349
|
+
slot.parent.splice(index, 1, ...placed);
|
|
350
|
+
} else {
|
|
351
|
+
slot.parent[slot.key] = placed[0];
|
|
352
|
+
}
|
|
353
|
+
for (const value of placed) next.push({ parent: slot.parent, key: slot.key, value });
|
|
354
|
+
}
|
|
355
|
+
return next;
|
|
356
|
+
}
|
|
357
|
+
function deferredSlots(target, cms) {
|
|
358
|
+
if (target.marker.op === "items" && target.marker.field) {
|
|
359
|
+
return [{ parent: target.entry, key: target.marker.field, value: cms.getField(target.entry, target.marker.field) }];
|
|
360
|
+
}
|
|
361
|
+
return target.slots.map((s) => ({ ...s, value: target.entry }));
|
|
362
|
+
}
|
|
363
|
+
function children(value) {
|
|
364
|
+
if (Array.isArray(value)) return value.map((v, i) => ({ parent: value, key: i, value: v }));
|
|
365
|
+
return Object.keys(value).map((k) => ({ parent: value, key: k, value: value[k] }));
|
|
366
|
+
}
|
|
367
|
+
async function loadReplacements(decisions, cms, locale) {
|
|
368
|
+
const ids = [...new Set(decisions.flatMap((d) => d.ids ?? []))];
|
|
369
|
+
const types = {};
|
|
370
|
+
for (const d of decisions) if (d.contentType) for (const id of d.ids ?? []) types[id] = d.contentType;
|
|
371
|
+
const queries = decisions.filter((d) => !d.ids && d.query);
|
|
372
|
+
const [fetched, ...results] = await Promise.all([
|
|
373
|
+
ids.length > 0 ? cms.fetchEntries(ids, locale, types) : Promise.resolve([]),
|
|
374
|
+
...queries.map((d) => cms.queryEntries(d.query, locale).catch(() => []))
|
|
375
|
+
]);
|
|
376
|
+
const entriesById = /* @__PURE__ */ new Map();
|
|
377
|
+
for (const entry of fetched) {
|
|
378
|
+
const id = cms.entryId(entry);
|
|
379
|
+
if (id) entriesById.set(id, entry);
|
|
380
|
+
}
|
|
381
|
+
const queried = /* @__PURE__ */ new Map();
|
|
382
|
+
queries.forEach((d, i) => queried.set(d, results[i]));
|
|
383
|
+
return { entriesById, queried };
|
|
384
|
+
}
|
|
385
|
+
var TIMEOUT = /* @__PURE__ */ Symbol("timeout");
|
|
386
|
+
function withDeadline(promise, deadline) {
|
|
387
|
+
const remaining = deadline - Date.now();
|
|
388
|
+
if (remaining <= 0) return Promise.reject(TIMEOUT);
|
|
389
|
+
let timer;
|
|
390
|
+
return Promise.race([
|
|
391
|
+
promise,
|
|
392
|
+
new Promise((_, reject) => {
|
|
393
|
+
timer = setTimeout(() => reject(TIMEOUT), remaining);
|
|
394
|
+
})
|
|
395
|
+
]).finally(() => clearTimeout(timer));
|
|
396
|
+
}
|
|
397
|
+
function pendingTargets(payload, adapter, contentKey) {
|
|
398
|
+
const cms = scopedAdapter(adapter, contentKey);
|
|
399
|
+
try {
|
|
400
|
+
let content = typeof structuredClone === "function" ? structuredClone(payload) : payload;
|
|
401
|
+
content = cms.prepare ? cms.prepare(content) : content;
|
|
402
|
+
const root = { parent: { root: content }, key: "root", value: content };
|
|
403
|
+
return [...walk([root], cms, /* @__PURE__ */ new WeakSet(), 0, () => {
|
|
404
|
+
}, contentKey, () => void 0).keys()];
|
|
405
|
+
} catch {
|
|
406
|
+
return [];
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
function scopedAdapter(cms, contentKey) {
|
|
410
|
+
return {
|
|
411
|
+
...cms,
|
|
412
|
+
marker(entry) {
|
|
413
|
+
const m = cms.marker(entry);
|
|
414
|
+
return m && contentKey && m.keys?.includes(contentKey) ? m : null;
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
async function personalizePage(ref, options) {
|
|
419
|
+
let page;
|
|
420
|
+
try {
|
|
421
|
+
if (!options.cms.fetchPage) throw new Error("This CMS adapter cannot fetch pages; use personalizeContent(page)");
|
|
422
|
+
page = await options.cms.fetchPage(ref, options.locale);
|
|
423
|
+
} catch (error) {
|
|
424
|
+
return { content: null, applied: [], complete: false, error };
|
|
425
|
+
}
|
|
426
|
+
if (page == null) return { content: null, applied: [], complete: true };
|
|
427
|
+
return personalizeContent(page, options);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
export { CONSENT_COOKIE, CircuitOpenError, DEFAULT_BASE_URL, DEFAULT_PREFIX, HttpClient, HttpError, VISITOR_COOKIE, buildUrl, consentCookie, consentCookieName, isBrowser, pendingTargets, personalizeContent, personalizePage, visitorCookieName };
|
|
431
|
+
//# sourceMappingURL=chunk-FSJMHXS6.js.map
|
|
432
|
+
//# sourceMappingURL=chunk-FSJMHXS6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/http-client.ts","../src/core/cookie-names.ts","../src/core/personalize-content.ts"],"names":[],"mappings":"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","file":"chunk-FSJMHXS6.js","sourcesContent":["import type { CircuitBreakerConfig, HttpMethod, JsonValue, RetryConfig } from \"./types\";\n\n/**\n * Default Xenia backend base URL, used whenever `baseUrl` is omitted from\n * config. Points at the hosted engine — override only for a self-hosted or\n * non-default environment.\n */\nexport const DEFAULT_BASE_URL = \"https://engine.xeniamartech.com/xenia\";\n\n/** Returns true when running in a browser environment (SSR-safe). */\nexport function isBrowser(): boolean {\n return typeof window !== \"undefined\" && typeof document !== \"undefined\";\n}\n\n/** Appends query parameters to a URL. Returns the URL unchanged when empty. */\nexport function buildUrl(url: string, queryParams?: Record<string, string>): string {\n if (!queryParams || Object.keys(queryParams).length === 0) return url;\n const params = new URLSearchParams(queryParams);\n const sep = url.includes(\"?\") ? \"&\" : \"?\";\n return `${url}${sep}${params.toString()}`;\n}\n\n/** Sleep helper used by the retry backoff. */\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/** Options for a single request. */\nexport interface RequestOptions {\n method?: HttpMethod;\n /** Path appended to `baseUrl` (should start with `/`). */\n path: string;\n headers?: Record<string, string>;\n query?: Record<string, string>;\n body?: JsonValue;\n /** Per-request timeout override in ms. */\n timeout?: number;\n /** Disable retry for this request (e.g. long-polling webhook). */\n noRetry?: boolean;\n /** Per-request retry override. */\n retry?: RetryConfig;\n /**\n * Keep the request alive past page unload (`fetch`'s `keepalive` flag).\n * Used for best-effort flushes on `pagehide`, where `sendBeacon` can't be\n * used instead because it doesn't support custom headers (tenant scope).\n */\n keepalive?: boolean;\n}\n\n/** Result of a request: the raw Response plus the parsed JSON (or null). */\nexport interface HttpResult {\n response: Response;\n data: JsonValue;\n}\n\n/** Error thrown for non-2xx responses (after retries are exhausted). */\nexport class HttpError extends Error {\n constructor(\n public readonly status: number,\n public readonly data: JsonValue,\n message?: string,\n ) {\n super(message ?? `Request failed with status ${status}`);\n this.name = \"HttpError\";\n }\n}\n\n/** Thrown without a network call while an endpoint's circuit is open (see {@link CircuitBreakerConfig}). */\nexport class CircuitOpenError extends Error {\n constructor(public readonly endpoint: string) {\n super(`Circuit open for ${endpoint}: failing fast until the cooldown passes`);\n this.name = \"CircuitOpenError\";\n }\n}\n\n/**\n * Per-endpoint breaker state, module-wide so it outlives a single client.\n * ponytail: after the cooldown every concurrent caller probes, not just one; fine for a fail-fast\n * guard, add a probe lock if a thundering herd on recovery ever matters.\n */\nconst breakers = new Map<string, { failures: number; openedAt: number }>();\n\nexport interface HttpClientOptions {\n /** Defaults to {@link DEFAULT_BASE_URL} when omitted. */\n baseUrl?: string;\n defaultHeaders?: Record<string, string>;\n timeout?: number;\n retry?: RetryConfig;\n circuitBreaker?: boolean | CircuitBreakerConfig;\n debug?: boolean;\n}\n\n/**\n * Minimal fetch wrapper shared by all runtimes.\n *\n * - Injects default headers (tenant scope, content-type).\n * - Applies an `AbortController` timeout.\n * - Retries transient failures (network error, `429`, `5xx`) with exponential\n * backoff, honoring a `Retry-After` header when present.\n * - Parses JSON, falling back to `null` on empty/invalid bodies.\n */\nexport class HttpClient {\n private readonly baseUrl: string;\n private readonly defaultHeaders: Record<string, string>;\n private readonly timeout: number;\n private readonly retry: Required<RetryConfig>;\n private readonly debug: boolean;\n private readonly breaker: Required<CircuitBreakerConfig> | null;\n\n constructor(opts: HttpClientOptions) {\n this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n this.defaultHeaders = opts.defaultHeaders ?? {};\n this.timeout = opts.timeout ?? 8000;\n this.retry = {\n attempts: opts.retry?.attempts ?? 2,\n baseDelayMs: opts.retry?.baseDelayMs ?? 300,\n };\n this.debug = opts.debug ?? false;\n const cb = opts.circuitBreaker === true ? {} : opts.circuitBreaker || null;\n this.breaker = cb && { failureThreshold: cb.failureThreshold ?? 3, cooldownMs: cb.cooldownMs ?? 30_000 };\n }\n\n async request(opts: RequestOptions): Promise<HttpResult> {\n if (!this.breaker) return this.send(opts);\n // Keyed by full URL, query included: every webhook shares `/flow`, and one slow journey\n // (`?id=`) must not short-circuit the others.\n const key = buildUrl(this.baseUrl + opts.path, opts.query);\n const b = breakers.get(key) ?? { failures: 0, openedAt: 0 };\n if (b.openedAt) {\n if (Date.now() - b.openedAt < this.breaker.cooldownMs) throw new CircuitOpenError(key);\n // Half-open: let this call probe; one more failure re-opens.\n b.openedAt = 0;\n b.failures = this.breaker.failureThreshold - 1;\n }\n try {\n const result = await this.send(opts);\n breakers.delete(key);\n return result;\n } catch (err) {\n if (err instanceof HttpError && !this.isRetryableStatus(err.status)) {\n breakers.delete(key); // the backend answered: it's up\n throw err;\n }\n b.failures += 1;\n if (b.failures >= this.breaker.failureThreshold) {\n b.openedAt = Date.now();\n this.log(`circuit opened for ${opts.path} after ${b.failures} failures`);\n }\n breakers.set(key, b);\n throw err;\n }\n }\n\n private async send(opts: RequestOptions): Promise<HttpResult> {\n const url = buildUrl(this.baseUrl + opts.path, opts.query);\n const method = opts.method ?? \"GET\";\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n ...this.defaultHeaders,\n ...opts.headers,\n };\n const body = opts.body !== undefined ? JSON.stringify(opts.body) : undefined;\n const timeout = opts.timeout ?? this.timeout;\n const maxAttempts = opts.noRetry ? 1 : (opts.retry?.attempts ?? this.retry.attempts) + 1;\n const baseDelay = opts.retry?.baseDelayMs ?? this.retry.baseDelayMs;\n\n let lastError: unknown;\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n try {\n const response = await this.fetchWithTimeout(\n url,\n { method, headers, body, ...(opts.keepalive ? { keepalive: true } : {}) },\n timeout,\n );\n const data = await this.parseBody(response);\n\n if (response.ok) {\n return { response, data };\n }\n\n // Retry on 429/5xx if attempts remain; otherwise throw.\n if (this.isRetryableStatus(response.status) && attempt < maxAttempts - 1) {\n const wait = this.retryAfterMs(response, baseDelay, attempt);\n this.log(`retrying ${opts.path} after ${wait}ms (status ${response.status})`);\n await delay(wait);\n continue;\n }\n throw new HttpError(response.status, data);\n } catch (err) {\n lastError = err;\n // Non-retryable HttpError → rethrow immediately.\n if (err instanceof HttpError) throw err;\n // Network/timeout error → retry if attempts remain.\n if (attempt < maxAttempts - 1) {\n const wait = baseDelay * 2 ** attempt;\n this.log(`retrying ${opts.path} after ${wait}ms (network error)`);\n await delay(wait);\n continue;\n }\n }\n }\n throw lastError instanceof Error ? lastError : new Error(\"Request failed\");\n }\n\n private async fetchWithTimeout(\n url: string,\n init: RequestInit,\n timeout: number,\n ): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeout);\n try {\n return await fetch(url, { ...init, signal: controller.signal });\n } catch (err) {\n if (err instanceof Error && err.name === \"AbortError\") {\n throw new Error(`Request timeout after ${timeout}ms`, { cause: err });\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n }\n\n private async parseBody(response: Response): Promise<JsonValue> {\n const text = await response.text().catch(() => \"\");\n if (!text) return null;\n try {\n return JSON.parse(text) as JsonValue;\n } catch {\n return text;\n }\n }\n\n private isRetryableStatus(status: number): boolean {\n return status === 429 || (status >= 500 && status <= 599);\n }\n\n private retryAfterMs(response: Response, baseDelay: number, attempt: number): number {\n const header = response.headers.get(\"Retry-After\");\n if (header) {\n const seconds = Number(header);\n if (!Number.isNaN(seconds)) return seconds * 1000;\n }\n return baseDelay * 2 ** attempt;\n }\n\n private log(msg: string): void {\n if (this.debug) console.warn(`[xenia] ${msg}`);\n }\n}\n","/**\n * Cookie names shared by the browser and server halves of the SDK.\n *\n * The visitor cookie is the one channel where two runtimes must agree on a literal string: the\n * browser writes it, a same-origin server reads it back, and that match is what lets a server-side\n * conversion attribute to a browser-entered journey. `config.prefix` namespaces it — so two Xenia\n * apps on one domain keep separate visitors — which means **both halves must derive the name the\n * same way**. Deriving it on one side and hardcoding it on the other silently breaks the link for\n * anyone using a custom prefix: the server just finds no cookie and falls back to no visitor.\n *\n * Always build the name with these helpers rather than interpolating it by hand.\n */\n\nimport type { ConsentState } from \"./types\";\n\n/** Default storage/cookie prefix, matching `XeniaConfig.prefix`. */\nexport const DEFAULT_PREFIX = \"xenia\";\n\n/** Name of the first-party cookie carrying the SDK-owned visitor id, for a given prefix. */\nexport function visitorCookieName(prefix: string = DEFAULT_PREFIX): string {\n return `${prefix || DEFAULT_PREFIX}_vid`;\n}\n\n/** Name of the first-party cookie carrying consent scopes, for a given prefix. */\nexport function consentCookieName(prefix: string = DEFAULT_PREFIX): string {\n return `${prefix || DEFAULT_PREFIX}_consent`;\n}\n\n/**\n * The visitor cookie name for the DEFAULT prefix (`\"xenia_vid\"`).\n *\n * Correct for the common case, and kept so existing code (e.g. reading the cookie straight out of a\n * framework's cookie jar) keeps working. If you set `config.prefix`, use {@link visitorCookieName}\n * instead — this constant will not match what the browser wrote.\n */\nexport const VISITOR_COOKIE = visitorCookieName();\n\n/**\n * The consent cookie name for the DEFAULT prefix (`\"xenia_consent\"`). Same custom-prefix caveat as\n * {@link VISITOR_COOKIE}.\n */\nexport const CONSENT_COOKIE = consentCookieName();\n\n/**\n * A `document.cookie` string carrying consent, in the JSON form the server SDK's `getConsent()`\n * reads back. The browser client's `consent()` writes it; a page with its own consent banner and no\n * browser client can write it directly, so server-side calls see the visitor's choice.\n */\nexport function consentCookie(state: ConsentState, prefix?: string, maxAgeDays = 365): string {\n return `${consentCookieName(prefix)}=${encodeURIComponent(JSON.stringify(state))}; path=/; max-age=${Math.floor(maxAgeDays * 86400)}; SameSite=Lax`;\n}\n","/**\n * CMS page personalization — walk a fetched CMS page breadth-first, swap the\n * entries Xenia marked as personalized, and do it in as few round trips as\n * the page's nesting allows.\n *\n * Rules (see the \"Rounds\" section of the feature plan):\n * - **Static** targets carry their replacement in the payload (`xeniaVariants`)\n * and are swapped inline — no network at all.\n * - **Runtime** targets are a barrier: their id is collected, their subtree is\n * NOT walked (it may be replaced). One `resolve-entries` call decides every\n * target found in a round, one parallel CMS fetch loads every replacement,\n * and the next round walks whatever now sits at each position.\n * - Never throws: on error or when `timeoutMs` runs out, whatever is not yet\n * decided keeps its base content.\n *\n * CMS specifics live behind {@link CmsAdapter}; built-in adapters ship as\n * `@xenia-martech/sdk-js/cms/contentful` (and later `/cms/contentstack`). Any\n * other CMS works by implementing the interface.\n */\n\nimport type { EntryDecision, EntryQuery, EntrySwapOp, ResolveOptions } from \"./types\";\n\n/** Any value inside a CMS payload — the walker is shape-agnostic; adapters give it meaning. */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype Node = any;\n\n/** The parsed `xenia` marker field of a personalized entry. */\nexport interface XeniaMarker {\n kind: \"static\" | \"runtime\";\n op?: EntrySwapOp;\n /** Array field replaced by `op: \"items\"`. */\n field?: string;\n limit?: number;\n /**\n * The content keys (Personalization Resolve identifiers) that personalize this entry. A page only\n * acts on markers that list its `contentKey`; a marker without keys is ignored.\n */\n keys?: string[];\n /**\n * Static replacements carried in the marker itself (CMSs without a reference field for them, e.g.\n * Contentstack) and their content type. Fetched from the CMS in the round's fetch — no Xenia call.\n */\n ids?: string[];\n ct?: string;\n}\n\n/** What the page resolver needs to know about a CMS's entries. `N` is the entry shape. */\nexport interface CmsAdapter<N extends object = Node> {\n /** The entry id when `value` is a CMS entry (not a link stub, asset or plain object). */\n entryId(value: unknown): string | undefined;\n /** The entry's Xenia marker, or null when it is not personalized. */\n marker(entry: N): XeniaMarker | null;\n /** Static replacements pinned on the entry (its resolved `xeniaVariants`). */\n staticVariants(entry: N): N[];\n getField(entry: N, field: string): unknown;\n setField(entry: N, field: string, value: unknown): void;\n /** Legacy `items` without a field: copy the replacement's array fields onto the entry. */\n mergeItems?(entry: N, replacement: N): void;\n /**\n * Remove the marker from an entry that has been dealt with (kept base, or had its items replaced),\n * so personalizing the output again — e.g. the browser continuing a server render — never decides\n * it twice. Optional; without it a re-run re-asks Xenia about those entries.\n */\n clearMarker?(entry: N): void;\n /** Normalize a fetched payload before walking (e.g. resolve links). Defaults to identity. */\n prepare?(payload: unknown): unknown;\n /**\n * Load entries by id in ONE round trip (parallel calls are fine); order does not matter. `types`\n * maps an id to its content type when Xenia knows it — needed by CMSs that fetch per type.\n */\n fetchEntries(ids: string[], locale?: string, types?: Record<string, string>): Promise<N[]>;\n /** Run a content-type query in ONE call, in the query's order. */\n queryEntries(query: EntryQuery, locale?: string): Promise<N[]>;\n /**\n * Load one page with its whole tree (links resolved), or null when there is none. Optional; it\n * is what lets `personalizePage(ref)` do the fetch, so apps never fetch the page themselves.\n */\n fetchPage?(ref: PageRef, locale?: string): Promise<N | null>;\n}\n\n/** Identifies a page in the CMS: its entry id, or a content type plus exact field values (a slug). */\nexport type PageRef = string | { contentType: string; fields: Record<string, string | number | boolean> };\n\n/** Resolves a round's runtime targets — `XeniaApi.resolveEntries` bound to the visitor. */\nexport type EntryResolver = (entryIds: string[]) => Promise<EntryDecision[]>;\n\n/** Reported for every swap actually applied (for impressions / analytics). */\nexport interface AppliedSwap {\n target: string;\n kind: \"static\" | \"runtime\";\n op: EntrySwapOp;\n /** Ids of the entries now at the target's position (or in its field). */\n entryIds: string[];\n contentKey?: string;\n bucket?: string;\n round: number;\n}\n\nexport interface PersonalizeContentOptions {\n cms: CmsAdapter;\n /** Decides runtime targets. Omit to apply static swaps only (zero network). */\n resolve?: EntryResolver;\n locale?: string;\n /**\n * The page's Personalization Resolve identifier (required). Only that journey personalizes the\n * page: a marker that doesn't list this key is plain content, and the backend decides for this key\n * alone — another journey targeting the same section is neither consulted nor run.\n */\n contentKey: string;\n /** Max network rounds (nesting depth of runtime targets). Default 3. */\n maxRounds?: number;\n /** Total budget for all rounds, in ms. Undecided targets keep their base. Default 300. */\n timeoutMs?: number;\n /** Mutate the payload in place instead of personalizing a copy. Default false. */\n inPlace?: boolean;\n onApplied?(swap: AppliedSwap): void;\n /**\n * Called when a round starts waiting on the network (`pending` = targets being decided) and when\n * it has been applied (`pending` empty). `content` is the live, partially personalized tree —\n * lets a UI render level by level instead of waiting for every round.\n */\n onProgress?(progress: { content: unknown; round: number; pending: string[] }): void;\n}\n\n/** `personalizeContent` options as taken by `XeniaClient` / `XeniaServer`, which supply `resolve`. */\nexport type PagePersonalizeOptions = Omit<PersonalizeContentOptions, \"resolve\"> &\n Pick<ResolveOptions, \"data\" | \"identity\" | \"context\">;\n\nexport interface PersonalizeContentResult<T> {\n content: T;\n applied: AppliedSwap[];\n /** False when the budget ran out, a call failed, or `maxRounds` was hit with targets left. */\n complete: boolean;\n error?: unknown;\n}\n\nconst DEFAULT_TIMEOUT_MS = 300;\nconst DEFAULT_MAX_ROUNDS = 3;\n\n/** One place a value sits in the tree: `parent[key]`. Arrays are re-indexed by identity on write. */\ninterface Slot {\n parent: Node;\n key: string | number;\n value: Node;\n}\n\n/** What Xenia decided for an entry id, with the fetched replacements (copied per use). */\ninterface Decided {\n decision?: EntryDecision;\n items: Node[];\n}\n\nconst copyOf = (v: Node): Node =>\n v && typeof v === \"object\" && typeof structuredClone === \"function\" ? structuredClone(v) : v;\n\n/**\n * A target found in a round, at every place it appears (an entry may be shared). `local` is set for\n * a static target whose replacements are named in its marker: decided here, only fetched.\n */\ninterface Pending {\n id: string;\n marker: XeniaMarker;\n entry: Node;\n slots: Slot[];\n local?: EntryDecision;\n}\n\nexport async function personalizeContent<T>(\n payload: T,\n options: PersonalizeContentOptions,\n): Promise<PersonalizeContentResult<T>> {\n const cms = scopedAdapter(options.cms, options.contentKey);\n const maxRounds = options.maxRounds ?? DEFAULT_MAX_ROUNDS;\n const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);\n const applied: AppliedSwap[] = [];\n const report = (swap: AppliedSwap) => {\n applied.push(swap);\n try {\n options.onApplied?.(swap);\n } catch {\n /* a broken analytics callback must never break rendering */\n }\n };\n\n let content: Node = payload;\n try {\n if (!options.inPlace && typeof structuredClone === \"function\") content = structuredClone(content);\n content = cms.prepare ? cms.prepare(content) : content;\n } catch (error) {\n return { content: payload, applied, complete: false, error };\n }\n\n const box = { root: content };\n // Entries whose own marker is already dealt with this call (kept base / swapped in) — walked as\n // plain content from now on so a target is never decided twice.\n const settled = new WeakSet<object>();\n // Decisions made in this call, by entry id. An entry that turns up again in a later round (a\n // shared entry inside a replacement) gets the same decision — consistent, and no second request.\n const decided = new Map<string, Decided>();\n let frontier: Slot[] = [{ parent: box, key: \"root\", value: box.root }];\n\n /** Apply a decision at a target's slots; returns what to walk next. */\n const settle = (target: Pending, d: Decided, round: number): Slot[] => {\n settled.add(target.entry);\n cms.clearMarker?.(target.entry);\n // A copy per use: fetched entries share objects (one CDA include graph), and a shared object\n // would be \"already settled\" wherever else it appears, hiding entries that are targets there.\n const items = d.items.map(copyOf);\n if (!d.decision || items.length === 0) {\n // Kept base: its subtree was never walked (barrier), so it is next round's frontier.\n return deferredSlots(target, cms);\n }\n for (const item of items) if (item && typeof item === \"object\") settled.add(item);\n const op = d.decision.op ?? target.marker.op ?? \"replace\";\n const next = apply(target, op, d.decision.field ?? target.marker.field, items, cms);\n report({\n target: target.id,\n kind: target.local ? \"static\" : \"runtime\",\n op,\n entryIds: items.map((e) => cms.entryId(e)).filter((id): id is string => !!id),\n contentKey: d.decision.contentKey,\n bucket: d.decision.bucket,\n round,\n });\n return next;\n };\n\n for (let round = 0; ; round++) {\n const pending = walk(frontier, cms, settled, round, report, options.contentKey, (t) => {\n const d = decided.get(t.id);\n return d ? settle(t, d, round) : undefined;\n });\n if (pending.size === 0) return { content: box.root, applied, complete: true };\n const remote = [...pending.values()].filter((p) => !p.local).map((p) => p.id);\n const local = [...pending.values()].flatMap((p) => (p.local ? [p.local] : []));\n if ((!options.resolve && local.length === 0) || round >= maxRounds) {\n return { content: box.root, applied, complete: !options.resolve && round < maxRounds };\n }\n progress(options, { content: box.root, round, pending: [...pending.keys()] });\n\n let decisions: EntryDecision[];\n let entriesById: Map<string, Node>;\n let queried: Map<EntryDecision, Node[]>;\n try {\n // Without a resolver, runtime targets keep their base; static ones in markers still apply.\n decisions = options.resolve && remote.length > 0 ? await withDeadline(options.resolve(remote), deadline) : [];\n decisions = [...decisions, ...local];\n ({ entriesById, queried } = await withDeadline(loadReplacements(decisions, cms, options.locale), deadline));\n } catch (error) {\n return { content: box.root, applied, complete: false, error: error === TIMEOUT ? undefined : error };\n }\n\n const byTarget = new Map(decisions.map((d) => [d.target, d]));\n frontier = [];\n for (const target of pending.values()) {\n const decision = byTarget.get(target.id);\n const items = decision\n ? decision.ids\n ? decision.ids.map((id) => entriesById.get(id)).filter((e) => e !== undefined)\n : queried.get(decision) ?? []\n : [];\n const d: Decided = { decision, items };\n decided.set(target.id, d);\n frontier.push(...settle(target, d, round));\n }\n progress(options, { content: box.root, round, pending: [] });\n }\n}\n\nfunction progress(options: PersonalizeContentOptions, p: { content: unknown; round: number; pending: string[] }) {\n try {\n options.onProgress?.(p);\n } catch {\n /* UI callbacks must never break personalization */\n }\n}\n\n/**\n * Breadth-first walk of `frontier`: applies static swaps inline, collects runtime targets (without\n * descending into them) and returns them keyed by entry id.\n */\nfunction walk(\n frontier: Slot[],\n cms: CmsAdapter,\n settled: WeakSet<object>,\n round: number,\n report: (swap: AppliedSwap) => void,\n contentKey: string,\n /** Applies an earlier decision for this entry id inline; undefined when it hasn't been decided. */\n reuse: (target: Pending) => Slot[] | undefined,\n): Map<string, Pending> {\n const pending = new Map<string, Pending>();\n const seen = new WeakSet<object>();\n // Fields held back by a runtime `items` target, wherever the CMS nests them (Contentful: `fields.x`).\n const deferred = new WeakSet<object>();\n const queue: Slot[] = [...frontier];\n\n while (queue.length > 0) {\n const slot = queue.shift()!;\n const value = slot.value;\n if (!value || typeof value !== \"object\" || deferred.has(value)) continue;\n\n const id = settled.has(value) ? undefined : cms.entryId(value);\n const marker = id ? cms.marker(value) : null;\n\n // Static replacements named in the marker itself (no pinned links): decided here, but loaded like\n // a runtime target's — fetched with the round's other replacements, no Xenia call.\n const local =\n id && marker?.kind === \"static\" && marker.ids?.length && cms.staticVariants(value).length === 0\n ? localDecision(id, marker, contentKey)\n : undefined;\n\n if (id && marker && (marker.kind === \"runtime\" || local)) {\n const again = reuse({ id, marker, entry: value, slots: [slot] });\n if (again) {\n queue.push(...again);\n continue;\n }\n const existing = pending.get(id);\n if (existing) {\n existing.slots.push(slot);\n } else {\n pending.set(id, { id, marker, entry: value, slots: [slot], ...(local ? { local } : {}) });\n // `items` only defers the replaced field; the entry's other fields are walked now.\n if (marker.op === \"items\" && marker.field) {\n const held = cms.getField(value, marker.field);\n if (held && typeof held === \"object\") deferred.add(held);\n queue.push(...children(value));\n }\n }\n continue;\n }\n\n if (id && marker?.kind === \"static\") {\n const variants = cms.staticVariants(value).filter((v) => v && typeof v === \"object\");\n settled.add(value);\n cms.clearMarker?.(value);\n if (variants.length > 0) {\n for (const v of variants) settled.add(v);\n const op = marker.op ?? \"replace\";\n const target: Pending = { id, marker, entry: value, slots: [slot] };\n queue.push(...apply(target, op, marker.field, variants, cms));\n report({\n target: id,\n kind: \"static\",\n op,\n entryIds: variants.map((v) => cms.entryId(v)).filter((x): x is string => !!x),\n round,\n });\n continue;\n }\n // A static marker with nothing pinned (write-back pending) is just base content.\n }\n\n if (seen.has(value)) continue;\n seen.add(value);\n queue.push(...children(value));\n }\n return pending;\n}\n\nfunction localDecision(id: string, marker: XeniaMarker, contentKey: string): EntryDecision {\n return {\n target: id,\n op: marker.op ?? \"replace\",\n ...(marker.field ? { field: marker.field } : {}),\n ids: marker.ids,\n ...(marker.ct ? { contentType: marker.ct } : {}),\n contentKey,\n };\n}\n\n/** Splice `items` in for `target` at all its slots; returns the new content to walk next. */\nfunction apply(target: Pending, op: EntrySwapOp, field: string | undefined, items: Node[], cms: CmsAdapter): Slot[] {\n if (op === \"items\") {\n if (field) {\n cms.setField(target.entry, field, items);\n return [{ parent: target.entry, key: field, value: cms.getField(target.entry, field) }];\n }\n if (cms.mergeItems) {\n cms.mergeItems(target.entry, items[0]);\n return target.slots.map((s) => ({ ...s, value: target.entry }));\n }\n op = \"replace\"; // no merge support → closest meaning\n }\n\n const next: Slot[] = [];\n for (const slot of target.slots) {\n const placed = op === \"expand\" && Array.isArray(slot.parent) ? items : [items[0]];\n if (Array.isArray(slot.parent)) {\n const index = slot.parent.indexOf(target.entry);\n if (index < 0) continue;\n slot.parent.splice(index, 1, ...placed);\n } else {\n slot.parent[slot.key] = placed[0];\n }\n for (const value of placed) next.push({ parent: slot.parent, key: slot.key, value });\n }\n return next;\n}\n\n/** What still needs walking under a target that kept its base content. */\nfunction deferredSlots(target: Pending, cms: CmsAdapter): Slot[] {\n if (target.marker.op === \"items\" && target.marker.field) {\n return [{ parent: target.entry, key: target.marker.field, value: cms.getField(target.entry, target.marker.field) }];\n }\n return target.slots.map((s) => ({ ...s, value: target.entry }));\n}\n\nfunction children(value: Node): Slot[] {\n if (Array.isArray(value)) return value.map((v, i) => ({ parent: value, key: i, value: v }));\n return Object.keys(value).map((k) => ({ parent: value, key: k, value: value[k] }));\n}\n\n/** One `fetchEntries` call for every explicit id, plus one query per content-type decision — all in parallel. */\nasync function loadReplacements(decisions: EntryDecision[], cms: CmsAdapter, locale?: string) {\n const ids = [...new Set(decisions.flatMap((d) => d.ids ?? []))];\n const types: Record<string, string> = {};\n for (const d of decisions) if (d.contentType) for (const id of d.ids ?? []) types[id] = d.contentType;\n const queries = decisions.filter((d) => !d.ids && d.query);\n const [fetched, ...results] = await Promise.all([\n ids.length > 0 ? cms.fetchEntries(ids, locale, types) : Promise.resolve([]),\n ...queries.map((d) => cms.queryEntries(d.query!, locale).catch(() => [] as Node[])),\n ]);\n const entriesById = new Map<string, Node>();\n for (const entry of fetched) {\n const id = cms.entryId(entry);\n if (id) entriesById.set(id, entry);\n }\n const queried = new Map<EntryDecision, Node[]>();\n queries.forEach((d, i) => queried.set(d, results[i]!));\n return { entriesById, queried };\n}\n\nconst TIMEOUT = Symbol(\"timeout\");\n\nfunction withDeadline<T>(promise: Promise<T>, deadline: number): Promise<T> {\n const remaining = deadline - Date.now();\n if (remaining <= 0) return Promise.reject(TIMEOUT);\n let timer: ReturnType<typeof setTimeout>;\n return Promise.race([\n promise,\n new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(TIMEOUT), remaining);\n }),\n ]).finally(() => clearTimeout(timer));\n}\n\n/**\n * The entries Xenia still has to decide on this page — runtime targets reachable without crossing\n * another undecided one. Synchronous and network-free (it reads the `xenia` markers), so a page can\n * render a skeleton in exactly those places from the first frame instead of flashing base content.\n * Nested targets inside a pending one are found once it is decided.\n */\nexport function pendingTargets(payload: unknown, adapter: CmsAdapter, contentKey: string): string[] {\n const cms = scopedAdapter(adapter, contentKey);\n try {\n let content: Node = typeof structuredClone === \"function\" ? structuredClone(payload) : payload;\n content = cms.prepare ? cms.prepare(content) : content;\n const root = { parent: { root: content }, key: \"root\", value: content };\n // On a copy: the walk applies static swaps inline, which must not touch the caller's page.\n return [...walk([root], cms, new WeakSet(), 0, () => {}, contentKey, () => undefined).keys()];\n } catch {\n return [];\n }\n}\n\n/**\n * The adapter as the page's journey sees it: only markers that list `contentKey` count. Any other\n * marker — another journey's, or one without keys — is plain content: no static swap, no request,\n * no pending skeleton, and its children are walked as usual.\n */\nfunction scopedAdapter(cms: CmsAdapter, contentKey: string | undefined): CmsAdapter {\n return {\n ...cms,\n marker(entry) {\n const m = cms.marker(entry);\n return m && contentKey && m.keys?.includes(contentKey) ? m : null;\n },\n };\n}\n\n/**\n * Fetch a page through `options.cms` and personalize it — {@link personalizeContent} without the\n * fetch in app code. Never throws: a failed fetch comes back as `content: null` with `error`; a page\n * that does not exist as `content: null`, `complete: true`. The page fetch is not counted against\n * `timeoutMs` — that budget is for personalization only.\n */\nexport async function personalizePage<T>(\n ref: PageRef,\n options: PersonalizeContentOptions,\n): Promise<PersonalizeContentResult<T | null>> {\n let page: unknown;\n try {\n if (!options.cms.fetchPage) throw new Error(\"This CMS adapter cannot fetch pages; use personalizeContent(page)\");\n page = await options.cms.fetchPage(ref, options.locale);\n } catch (error) {\n return { content: null, applied: [], complete: false, error };\n }\n if (page == null) return { content: null, applied: [], complete: true };\n return personalizeContent(page as T, options);\n}\n"]}
|