@viu/emporix-sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/CHANGELOG.md +407 -0
  2. package/LICENSE +21 -0
  3. package/README.md +115 -0
  4. package/dist/cart.cjs +214 -0
  5. package/dist/cart.cjs.map +1 -0
  6. package/dist/cart.d.cts +1807 -0
  7. package/dist/cart.d.ts +1807 -0
  8. package/dist/cart.js +4 -0
  9. package/dist/cart.js.map +1 -0
  10. package/dist/category.cjs +97 -0
  11. package/dist/category.cjs.map +1 -0
  12. package/dist/category.d.cts +313 -0
  13. package/dist/category.d.ts +313 -0
  14. package/dist/category.js +4 -0
  15. package/dist/category.js.map +1 -0
  16. package/dist/checkout.cjs +98 -0
  17. package/dist/checkout.cjs.map +1 -0
  18. package/dist/checkout.d.cts +252 -0
  19. package/dist/checkout.d.ts +252 -0
  20. package/dist/checkout.js +4 -0
  21. package/dist/checkout.js.map +1 -0
  22. package/dist/chunk-4BJUCCKJ.js +86 -0
  23. package/dist/chunk-4BJUCCKJ.js.map +1 -0
  24. package/dist/chunk-6UNFMCHZ.js +95 -0
  25. package/dist/chunk-6UNFMCHZ.js.map +1 -0
  26. package/dist/chunk-B56JZNI7.js +49 -0
  27. package/dist/chunk-B56JZNI7.js.map +1 -0
  28. package/dist/chunk-BFQ732VZ.js +11 -0
  29. package/dist/chunk-BFQ732VZ.js.map +1 -0
  30. package/dist/chunk-F3WAQH2E.js +120 -0
  31. package/dist/chunk-F3WAQH2E.js.map +1 -0
  32. package/dist/chunk-GWJ2U2CP.js +14 -0
  33. package/dist/chunk-GWJ2U2CP.js.map +1 -0
  34. package/dist/chunk-HEO34WLO.js +47 -0
  35. package/dist/chunk-HEO34WLO.js.map +1 -0
  36. package/dist/chunk-HKPZI7Y5.js +30 -0
  37. package/dist/chunk-HKPZI7Y5.js.map +1 -0
  38. package/dist/chunk-IEZHGU7F.js +171 -0
  39. package/dist/chunk-IEZHGU7F.js.map +1 -0
  40. package/dist/chunk-KTODT6DV.js +86 -0
  41. package/dist/chunk-KTODT6DV.js.map +1 -0
  42. package/dist/chunk-NM7VDXBR.js +57 -0
  43. package/dist/chunk-NM7VDXBR.js.map +1 -0
  44. package/dist/chunk-OMYVOOSX.js +57 -0
  45. package/dist/chunk-OMYVOOSX.js.map +1 -0
  46. package/dist/chunk-QDABAMVN.js +89 -0
  47. package/dist/chunk-QDABAMVN.js.map +1 -0
  48. package/dist/chunk-RXVVFNYN.js +56 -0
  49. package/dist/chunk-RXVVFNYN.js.map +1 -0
  50. package/dist/chunk-SKX7KCMH.js +23 -0
  51. package/dist/chunk-SKX7KCMH.js.map +1 -0
  52. package/dist/chunk-TLH6SLAF.js +215 -0
  53. package/dist/chunk-TLH6SLAF.js.map +1 -0
  54. package/dist/chunk-Z6PNI5VT.js +142 -0
  55. package/dist/chunk-Z6PNI5VT.js.map +1 -0
  56. package/dist/companies.cjs +58 -0
  57. package/dist/companies.cjs.map +1 -0
  58. package/dist/companies.d.cts +31 -0
  59. package/dist/companies.d.ts +31 -0
  60. package/dist/companies.js +3 -0
  61. package/dist/companies.js.map +1 -0
  62. package/dist/contacts.cjs +51 -0
  63. package/dist/contacts.cjs.map +1 -0
  64. package/dist/contacts.d.cts +28 -0
  65. package/dist/contacts.d.ts +28 -0
  66. package/dist/contacts.js +3 -0
  67. package/dist/contacts.js.map +1 -0
  68. package/dist/context-vKgzZ-5f.d.cts +329 -0
  69. package/dist/context-vKgzZ-5f.d.ts +329 -0
  70. package/dist/customer-groups.cjs +25 -0
  71. package/dist/customer-groups.cjs.map +1 -0
  72. package/dist/customer-groups.d.cts +36 -0
  73. package/dist/customer-groups.d.ts +36 -0
  74. package/dist/customer-groups.js +3 -0
  75. package/dist/customer-groups.js.map +1 -0
  76. package/dist/customer.cjs +262 -0
  77. package/dist/customer.cjs.map +1 -0
  78. package/dist/customer.d.cts +386 -0
  79. package/dist/customer.d.ts +386 -0
  80. package/dist/customer.js +5 -0
  81. package/dist/customer.js.map +1 -0
  82. package/dist/index.cjs +2075 -0
  83. package/dist/index.cjs.map +1 -0
  84. package/dist/index.d.cts +218 -0
  85. package/dist/index.d.ts +218 -0
  86. package/dist/index.js +791 -0
  87. package/dist/index.js.map +1 -0
  88. package/dist/locations.cjs +59 -0
  89. package/dist/locations.cjs.map +1 -0
  90. package/dist/locations.d.cts +30 -0
  91. package/dist/locations.d.ts +30 -0
  92. package/dist/locations.js +3 -0
  93. package/dist/locations.js.map +1 -0
  94. package/dist/media.cjs +123 -0
  95. package/dist/media.cjs.map +1 -0
  96. package/dist/media.d.cts +219 -0
  97. package/dist/media.d.ts +219 -0
  98. package/dist/media.js +3 -0
  99. package/dist/media.js.map +1 -0
  100. package/dist/orders.cjs +98 -0
  101. package/dist/orders.cjs.map +1 -0
  102. package/dist/orders.d.cts +167 -0
  103. package/dist/orders.d.ts +167 -0
  104. package/dist/orders.js +3 -0
  105. package/dist/orders.js.map +1 -0
  106. package/dist/payment.cjs +77 -0
  107. package/dist/payment.cjs.map +1 -0
  108. package/dist/payment.d.cts +110 -0
  109. package/dist/payment.d.ts +110 -0
  110. package/dist/payment.js +5 -0
  111. package/dist/payment.js.map +1 -0
  112. package/dist/price.cjs +88 -0
  113. package/dist/price.cjs.map +1 -0
  114. package/dist/price.d.cts +445 -0
  115. package/dist/price.d.ts +445 -0
  116. package/dist/price.js +4 -0
  117. package/dist/price.js.map +1 -0
  118. package/dist/product.cjs +100 -0
  119. package/dist/product.cjs.map +1 -0
  120. package/dist/product.d.cts +400 -0
  121. package/dist/product.d.ts +400 -0
  122. package/dist/product.js +4 -0
  123. package/dist/product.js.map +1 -0
  124. package/dist/segment.cjs +189 -0
  125. package/dist/segment.cjs.map +1 -0
  126. package/dist/segment.d.cts +241 -0
  127. package/dist/segment.d.ts +241 -0
  128. package/dist/segment.js +5 -0
  129. package/dist/segment.js.map +1 -0
  130. package/dist/types.gen-D1QuqPLs.d.cts +166 -0
  131. package/dist/types.gen-D1QuqPLs.d.ts +166 -0
  132. package/package.json +141 -0
package/dist/index.cjs ADDED
@@ -0,0 +1,2075 @@
1
+ 'use strict';
2
+
3
+ // src/core/errors.ts
4
+ var REDACTED_BODY_KEYS = /* @__PURE__ */ new Set([
5
+ "access_token",
6
+ "refresh_token",
7
+ "token",
8
+ "customertoken",
9
+ "saastoken",
10
+ "secret",
11
+ "client_secret",
12
+ "authorization",
13
+ "password"
14
+ ]);
15
+ function scrub(body) {
16
+ if (Array.isArray(body)) return body.map(scrub);
17
+ if (body && typeof body === "object") {
18
+ const out = {};
19
+ for (const [k, v] of Object.entries(body)) {
20
+ out[k] = REDACTED_BODY_KEYS.has(k.toLowerCase()) ? "***redacted***" : scrub(v);
21
+ }
22
+ return out;
23
+ }
24
+ return body;
25
+ }
26
+ var EmporixError = class extends Error {
27
+ status;
28
+ body;
29
+ constructor(message, status, body) {
30
+ super(message);
31
+ this.name = new.target.name;
32
+ this.status = status;
33
+ this.body = body;
34
+ Object.setPrototypeOf(this, new.target.prototype);
35
+ }
36
+ /** Safe serialisation — token-like body fields are redacted. */
37
+ toJSON() {
38
+ return { name: this.name, message: this.message, status: this.status, body: scrub(this.body) };
39
+ }
40
+ };
41
+ var EmporixAuthError = class extends EmporixError {
42
+ };
43
+ var EmporixForbiddenError = class extends EmporixError {
44
+ };
45
+ var EmporixNotFoundError = class extends EmporixError {
46
+ };
47
+ var EmporixValidationError = class extends EmporixError {
48
+ };
49
+ var EmporixServerError = class extends EmporixError {
50
+ };
51
+ var EmporixInsufficientScopeError = class extends EmporixForbiddenError {
52
+ requiredScope;
53
+ constructor(message, status, body, requiredScope) {
54
+ super(message, status, body);
55
+ this.requiredScope = requiredScope;
56
+ }
57
+ };
58
+ var SCOPE_HINT_RE = /missing scope[: ]+([a-z0-9._-]+)/i;
59
+ function extractRequiredScope(body) {
60
+ if (body && typeof body === "object" && "details" in body) {
61
+ const details = body.details;
62
+ if (Array.isArray(details)) {
63
+ for (const d of details) {
64
+ if (typeof d === "string") {
65
+ const m = d.match(SCOPE_HINT_RE);
66
+ if (m) return m[1];
67
+ }
68
+ }
69
+ }
70
+ }
71
+ return void 0;
72
+ }
73
+ function errorFromResponse(status, message, body) {
74
+ if (status === 401) return new EmporixAuthError(message, status, body);
75
+ if (status === 403) {
76
+ const scope = extractRequiredScope(body);
77
+ if (scope) return new EmporixInsufficientScopeError(message, status, body, scope);
78
+ return new EmporixForbiddenError(message, status, body);
79
+ }
80
+ if (status === 404) return new EmporixNotFoundError(message, status, body);
81
+ if (status === 400 || status === 422) return new EmporixValidationError(message, status, body);
82
+ if (status >= 500) return new EmporixServerError(message, status, body);
83
+ return new EmporixError(message, status, body);
84
+ }
85
+
86
+ // src/core/config.ts
87
+ var DEFAULT_HOST = "https://api.emporix.io";
88
+ var TENANT_RE = /^[a-z][a-z0-9]{2,15}$/;
89
+ function validateConfig(input) {
90
+ if (!TENANT_RE.test(input.tenant)) {
91
+ throw new Error(
92
+ `Invalid tenant "${input.tenant}": must be lowercase, 3\u201316 chars, match ^[a-z][a-z0-9]+$`
93
+ );
94
+ }
95
+ if (!input.credentials) {
96
+ throw new Error("credentials is required (provide at least one of backend/storefront/custom)");
97
+ }
98
+ return {
99
+ tenant: input.tenant,
100
+ host: input.host ?? DEFAULT_HOST,
101
+ credentials: input.credentials,
102
+ tokenProvider: input.tokenProvider,
103
+ timeouts: {
104
+ connectMs: input.timeouts?.connectMs ?? 1e4,
105
+ readMs: input.timeouts?.readMs ?? 6e4
106
+ },
107
+ retry: { maxAttempts: input.retry?.maxAttempts ?? 3 },
108
+ cache: {
109
+ expirationBufferSeconds: input.cache?.expirationBufferSeconds ?? 60,
110
+ maxLifetimeSeconds: input.cache?.maxLifetimeSeconds ?? 3600
111
+ },
112
+ logger: input.logger
113
+ };
114
+ }
115
+
116
+ // src/core/logger.ts
117
+ var LEVEL = {
118
+ trace: 10,
119
+ debug: 20,
120
+ info: 30,
121
+ warn: 40,
122
+ error: 50,
123
+ silent: 60
124
+ };
125
+ function isValidLevel(v) {
126
+ return v !== void 0 && v in LEVEL;
127
+ }
128
+ function readEnv(name) {
129
+ try {
130
+ return typeof process !== "undefined" && process.env ? process.env[name] : void 0;
131
+ } catch {
132
+ return void 0;
133
+ }
134
+ }
135
+ var LevelResolver = class {
136
+ constructor(cfg, warn = () => {
137
+ }) {
138
+ this.warn = warn;
139
+ this.cfgLevel = cfg.level ?? "warn";
140
+ this.cfgServices = { ...cfg.services };
141
+ }
142
+ warn;
143
+ cfgLevel;
144
+ cfgServices;
145
+ forcedServices = {};
146
+ forcedGlobal;
147
+ warned = false;
148
+ envFor(svc) {
149
+ const raw = readEnv(`EMPORIX_LOG_LEVEL_${svc.toUpperCase()}`);
150
+ if (raw === void 0) return void 0;
151
+ if (isValidLevel(raw)) return raw;
152
+ if (!this.warned) {
153
+ this.warned = true;
154
+ this.warn(`Invalid EMPORIX_LOG_LEVEL_${svc.toUpperCase()}="${raw}" ignored`);
155
+ }
156
+ return void 0;
157
+ }
158
+ envGlobal() {
159
+ const raw = readEnv("EMPORIX_LOG_LEVEL");
160
+ if (raw === void 0) return void 0;
161
+ if (isValidLevel(raw)) return raw;
162
+ if (!this.warned) {
163
+ this.warned = true;
164
+ this.warn(`Invalid EMPORIX_LOG_LEVEL="${raw}" ignored`);
165
+ }
166
+ return void 0;
167
+ }
168
+ /**
169
+ * Effective level for a service. Forced overrides (from `set(..., force)`)
170
+ * take precedence over env vars; otherwise env > programmatic config.
171
+ */
172
+ get(svc) {
173
+ return this.forcedServices[svc] ?? this.envFor(svc) ?? this.forcedGlobal ?? this.envGlobal() ?? this.cfgServices[svc] ?? this.cfgLevel ?? "warn";
174
+ }
175
+ /** Numeric effective level for a service. */
176
+ numericLevel(svc) {
177
+ return LEVEL[this.get(svc)];
178
+ }
179
+ /** True if `svc`'s effective level allows emitting at `at`. */
180
+ isAtLeast(svc, at) {
181
+ return this.numericLevel(svc) <= LEVEL[at];
182
+ }
183
+ /** Mutates programmatic level (global or one service). Env-set levels are sticky unless `force`. */
184
+ set(level, svc, force = false) {
185
+ if (svc) {
186
+ const envBound = readEnv(`EMPORIX_LOG_LEVEL_${svc.toUpperCase()}`) !== void 0;
187
+ if (envBound && !force) {
188
+ this.warn(`Level for "${svc}" is env-controlled; pass force to override`);
189
+ return;
190
+ }
191
+ if (force) this.forcedServices[svc] = level;
192
+ else this.cfgServices[svc] = level;
193
+ } else {
194
+ const envBound = readEnv("EMPORIX_LOG_LEVEL") !== void 0;
195
+ if (envBound && !force) {
196
+ this.warn("Global level is env-controlled; pass force to override");
197
+ return;
198
+ }
199
+ if (force) this.forcedGlobal = level;
200
+ else this.cfgLevel = level;
201
+ }
202
+ }
203
+ };
204
+ var DEFAULT_REDACT = /* @__PURE__ */ new Set([
205
+ "authorization",
206
+ "password",
207
+ "oldpassword",
208
+ "newpassword",
209
+ "clientsecret",
210
+ "secret",
211
+ "access_token",
212
+ "refresh_token",
213
+ "customertoken",
214
+ "saastoken",
215
+ "saas-token",
216
+ "bearertoken",
217
+ "apikey",
218
+ "token"
219
+ ]);
220
+ function redact(value, extra = []) {
221
+ const keys = new Set(DEFAULT_REDACT);
222
+ for (const k of extra) keys.add(k.toLowerCase());
223
+ const walk = (v) => {
224
+ if (Array.isArray(v)) return v.map(walk);
225
+ if (v && typeof v === "object") {
226
+ const src = v;
227
+ if (typeof src.kind === "string" && "token" in src) return { kind: src.kind };
228
+ const out = {};
229
+ for (const [k, val] of Object.entries(src)) {
230
+ if (keys.has(k.toLowerCase())) {
231
+ out[k] = k.toLowerCase() === "authorization" && typeof val === "string" ? "Bearer ***redacted***" : "***redacted***";
232
+ } else {
233
+ out[k] = walk(val);
234
+ }
235
+ }
236
+ return out;
237
+ }
238
+ return v;
239
+ };
240
+ return walk(value);
241
+ }
242
+ var EMIT = {
243
+ trace: LEVEL.trace,
244
+ debug: LEVEL.debug,
245
+ info: LEVEL.info,
246
+ warn: LEVEL.warn,
247
+ error: LEVEL.error
248
+ };
249
+ var BaseLogger = class {
250
+ constructor(resolver, bindings, extraRedact) {
251
+ this.resolver = resolver;
252
+ this.bindings = bindings;
253
+ this.extraRedact = extraRedact;
254
+ }
255
+ resolver;
256
+ bindings;
257
+ extraRedact;
258
+ svc() {
259
+ return this.bindings.service ?? "http";
260
+ }
261
+ get level() {
262
+ return this.resolver.get(this.svc());
263
+ }
264
+ isLevelEnabled(level) {
265
+ return this.resolver.isAtLeast(this.svc(), level);
266
+ }
267
+ at(level, msg, fields) {
268
+ if (this.resolver.numericLevel(this.svc()) > EMIT[level]) return;
269
+ const merged = { ...this.bindings, ...fields ?? {} };
270
+ this.emit(level, msg, redact(merged, this.extraRedact));
271
+ }
272
+ trace(m, f) {
273
+ this.at("trace", m, f);
274
+ }
275
+ debug(m, f) {
276
+ this.at("debug", m, f);
277
+ }
278
+ info(m, f) {
279
+ this.at("info", m, f);
280
+ }
281
+ warn(m, f) {
282
+ this.at("warn", m, f);
283
+ }
284
+ error(m, f) {
285
+ this.at("error", m, f);
286
+ }
287
+ };
288
+ var ConsoleLogger = class _ConsoleLogger extends BaseLogger {
289
+ constructor(resolver, bindings, extra, sink, pretty) {
290
+ super(resolver, bindings, extra);
291
+ this.sink = sink;
292
+ this.pretty = pretty;
293
+ }
294
+ sink;
295
+ pretty;
296
+ emit(level, msg, fields) {
297
+ if (this.pretty) {
298
+ const fn = level === "error" ? this.sink.error : level === "warn" ? this.sink.warn : level === "info" ? this.sink.info : this.sink.log;
299
+ fn(`[${level}] ${msg}`, fields);
300
+ } else {
301
+ this.sink.log(JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), level, msg, ...fields }));
302
+ }
303
+ }
304
+ child(bindings) {
305
+ return new _ConsoleLogger(
306
+ this.resolver,
307
+ { ...this.bindings, ...bindings },
308
+ this.extraRedact,
309
+ this.sink,
310
+ this.pretty
311
+ );
312
+ }
313
+ };
314
+ var NoopLogger = class {
315
+ level = "silent";
316
+ isLevelEnabled() {
317
+ return false;
318
+ }
319
+ trace() {
320
+ }
321
+ debug() {
322
+ }
323
+ info() {
324
+ }
325
+ warn() {
326
+ }
327
+ error() {
328
+ }
329
+ child() {
330
+ return this;
331
+ }
332
+ };
333
+ function createConsoleLogger(resolver, bindings = {}, opts = {}) {
334
+ const pretty = opts.pretty ?? readEnv("NODE_ENV") !== "production";
335
+ const sink = opts.sink ?? {
336
+ log: console.log,
337
+ info: console.info,
338
+ warn: console.warn,
339
+ error: console.error
340
+ };
341
+ return new ConsoleLogger(resolver, bindings, opts.redact ?? [], sink, pretty);
342
+ }
343
+ function createNoopLogger() {
344
+ return new NoopLogger();
345
+ }
346
+
347
+ // src/core/auth.ts
348
+ var auth = {
349
+ /** Service/custom credential set (default `backend`). */
350
+ service: (credentials) => credentials === void 0 ? { kind: "service" } : { kind: "service", credentials },
351
+ /** Cached anonymous storefront token. */
352
+ anonymous: () => ({ kind: "anonymous" }),
353
+ /** Caller-owned customer bearer token. */
354
+ customer: (token) => ({ kind: "customer", token }),
355
+ /** Exact token, no transformation (SSO / token-exchange). */
356
+ raw: (token) => ({ kind: "raw", token })
357
+ };
358
+ async function resolveToken(ctx, provider) {
359
+ switch (ctx.kind) {
360
+ case "service":
361
+ return provider.getToken(ctx.credentials ?? "backend");
362
+ case "anonymous":
363
+ return (await provider.getAnonymousToken()).accessToken;
364
+ case "customer":
365
+ case "raw":
366
+ return ctx.token;
367
+ }
368
+ }
369
+ var DefaultTokenProvider = class {
370
+ constructor(cfg) {
371
+ this.cfg = cfg;
372
+ }
373
+ cfg;
374
+ serviceCache = /* @__PURE__ */ new Map();
375
+ serviceLocks = /* @__PURE__ */ new Map();
376
+ anon;
377
+ anonLock;
378
+ anonStore;
379
+ refreshListeners = /* @__PURE__ */ new Set();
380
+ onRefresh(listener) {
381
+ this.refreshListeners.add(listener);
382
+ return () => {
383
+ this.refreshListeners.delete(listener);
384
+ };
385
+ }
386
+ notifyRefresh(kind, success) {
387
+ for (const l of this.refreshListeners) {
388
+ try {
389
+ l({ kind, success });
390
+ } catch {
391
+ }
392
+ }
393
+ }
394
+ attachAnonymousStore(store) {
395
+ this.anonStore = store;
396
+ if (!this.anon) {
397
+ const persisted = store.read();
398
+ if (persisted) {
399
+ this.anon = {
400
+ accessToken: "",
401
+ refreshToken: persisted.refreshToken,
402
+ sessionId: persisted.sessionId,
403
+ expiresIn: 0,
404
+ expiresAt: 0
405
+ };
406
+ }
407
+ }
408
+ }
409
+ creds(set) {
410
+ if (set === "backend") {
411
+ const b = this.cfg.credentials.backend;
412
+ if (!b?.clientId || !b.secret) {
413
+ throw new EmporixAuthError(
414
+ "A 'service' AuthContext was used but credentials.backend is not configured"
415
+ );
416
+ }
417
+ return b;
418
+ }
419
+ const c = this.cfg.credentials.custom?.[set];
420
+ if (!c) throw new Error(`Unknown credential set "${set}"`);
421
+ return c;
422
+ }
423
+ fresh(e) {
424
+ if (!e) return false;
425
+ const now = Date.now();
426
+ if (now - e.obtainedAt >= this.cfg.cache.maxLifetimeSeconds * 1e3) return false;
427
+ return now < e.expiresAt;
428
+ }
429
+ async getToken(set) {
430
+ this.creds(set);
431
+ const cached = this.serviceCache.get(set);
432
+ if (this.fresh(cached)) return cached.token;
433
+ const inflight = this.serviceLocks.get(set);
434
+ if (inflight) return inflight;
435
+ const p = this.requestServiceToken(set).finally(() => this.serviceLocks.delete(set));
436
+ this.serviceLocks.set(set, p);
437
+ return p;
438
+ }
439
+ async requestServiceToken(set) {
440
+ const c = this.creds(set);
441
+ const body = new URLSearchParams({
442
+ grant_type: "client_credentials",
443
+ client_id: c.clientId,
444
+ client_secret: c.secret
445
+ });
446
+ if (c.scope) body.set("scope", c.scope);
447
+ const res = await fetch(`${this.cfg.host}/oauth/token`, {
448
+ method: "POST",
449
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
450
+ body
451
+ });
452
+ const json = await res.json().catch(() => ({}));
453
+ if (!res.ok) {
454
+ throw new EmporixAuthError(`Token request failed for "${set}"`, res.status, json);
455
+ }
456
+ const obtainedAt = Date.now();
457
+ const ttl = Number(json.expires_in ?? 3600);
458
+ this.serviceCache.set(set, {
459
+ token: json.access_token,
460
+ obtainedAt,
461
+ expiresAt: obtainedAt + (ttl - this.cfg.cache.expirationBufferSeconds) * 1e3
462
+ });
463
+ return this.serviceCache.get(set).token;
464
+ }
465
+ invalidate(set) {
466
+ this.serviceCache.delete(set);
467
+ }
468
+ anonFresh() {
469
+ return !!this.anon && Date.now() < this.anon.expiresAt;
470
+ }
471
+ async getAnonymousToken() {
472
+ if (this.anonFresh()) return this.stripExpiry(this.anon);
473
+ if (this.anonLock) return this.anonLock;
474
+ const canRefresh = !!this.anon?.refreshToken;
475
+ const obtain = canRefresh ? this.fetchAnonymous("refresh").catch(() => this.fetchAnonymous("login")) : this.fetchAnonymous("login");
476
+ const p = obtain.finally(() => {
477
+ this.anonLock = void 0;
478
+ });
479
+ this.anonLock = p;
480
+ return p;
481
+ }
482
+ /** Refreshes the anonymous session, preserving its sessionId. */
483
+ async refreshAnonymous() {
484
+ if (!this.anon) return this.getAnonymousToken();
485
+ return this.fetchAnonymous("refresh");
486
+ }
487
+ invalidateAnonymous() {
488
+ this.anon = void 0;
489
+ this.anonStore?.write(null);
490
+ }
491
+ /** Force a stale access token while keeping the refresh token + sessionId. */
492
+ expireAnonymous() {
493
+ if (this.anon) this.anon = { ...this.anon, expiresAt: 0 };
494
+ }
495
+ stripExpiry(s) {
496
+ const { expiresAt: _expiresAt, ...rest } = s;
497
+ return rest;
498
+ }
499
+ async fetchAnonymous(mode) {
500
+ const sf = this.cfg.credentials.storefront;
501
+ if (!sf?.clientId) {
502
+ throw new Error("credentials.storefront.clientId is required for anonymous tokens");
503
+ }
504
+ const url = new URL(`${this.cfg.host}/customerlogin/auth/anonymous/${mode}`);
505
+ url.searchParams.set("tenant", this.cfg.tenant);
506
+ url.searchParams.set("client_id", sf.clientId);
507
+ const c = sf.context;
508
+ if (c?.currency) url.searchParams.set("currency", c.currency);
509
+ if (c?.siteCode) url.searchParams.set("siteCode", c.siteCode);
510
+ if (c?.targetLocation) url.searchParams.set("targetLocation", c.targetLocation);
511
+ if (mode === "refresh" && this.anon) {
512
+ url.searchParams.set("refresh_token", this.anon.refreshToken);
513
+ }
514
+ try {
515
+ const res = await fetch(url, { method: "GET" });
516
+ const json = await res.json().catch(() => ({}));
517
+ if (!res.ok) {
518
+ this.notifyRefresh("anonymous", false);
519
+ throw new EmporixAuthError(`Anonymous token ${mode} failed`, res.status, json);
520
+ }
521
+ const obtainedAt = Date.now();
522
+ this.anon = {
523
+ accessToken: json.access_token,
524
+ refreshToken: json.refresh_token,
525
+ sessionId: json.sessionId,
526
+ expiresIn: json.expires_in,
527
+ expiresAt: obtainedAt + (json.expires_in - this.cfg.cache.expirationBufferSeconds) * 1e3
528
+ };
529
+ this.anonStore?.write({
530
+ refreshToken: this.anon.refreshToken,
531
+ sessionId: this.anon.sessionId
532
+ });
533
+ this.notifyRefresh("anonymous", true);
534
+ return this.stripExpiry(this.anon);
535
+ } catch (err) {
536
+ if (!(err instanceof EmporixAuthError)) {
537
+ this.notifyRefresh("anonymous", false);
538
+ }
539
+ throw err;
540
+ }
541
+ }
542
+ };
543
+
544
+ // src/core/http.ts
545
+ var requestSeq = 0;
546
+ function safeJson(text) {
547
+ try {
548
+ return JSON.parse(text);
549
+ } catch {
550
+ return text;
551
+ }
552
+ }
553
+ var HttpClient = class {
554
+ constructor(opts) {
555
+ this.opts = opts;
556
+ this.sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
557
+ }
558
+ opts;
559
+ sleep;
560
+ async request(o) {
561
+ const requestId = `req-${++requestSeq}`;
562
+ const log = this.opts.logger.child({ requestId });
563
+ const url = new URL(this.opts.host + o.path);
564
+ for (const [k, v] of Object.entries(o.query ?? {})) {
565
+ if (v !== void 0) url.searchParams.set(k, String(v));
566
+ }
567
+ const sdkManaged = o.auth.kind === "service" || o.auth.kind === "anonymous";
568
+ const maxAttempts = this.opts.retry.maxAttempts;
569
+ let reauthed = false;
570
+ for (let attempt = 1; ; attempt++) {
571
+ const token = await resolveToken(o.auth, this.opts.provider);
572
+ log.debug("http request", {
573
+ authKind: o.auth.kind,
574
+ method: o.method,
575
+ url: url.pathname,
576
+ attempt
577
+ });
578
+ const controller = new AbortController();
579
+ const timer = setTimeout(
580
+ () => controller.abort(),
581
+ o.timeoutMs ?? this.opts.timeouts.readMs
582
+ );
583
+ const isFormData = typeof FormData !== "undefined" && o.body instanceof FormData;
584
+ const init = {
585
+ method: o.method,
586
+ headers: {
587
+ ...o.headers ?? {},
588
+ Authorization: `Bearer ${token}`,
589
+ // JSON bodies: set Content-Type. FormData bodies: let `fetch`
590
+ // emit `multipart/form-data; boundary=...` itself.
591
+ ...o.body !== void 0 && !isFormData ? { "Content-Type": "application/json" } : {}
592
+ },
593
+ signal: controller.signal
594
+ };
595
+ if (o.body !== void 0) {
596
+ init.body = isFormData ? o.body : JSON.stringify(o.body);
597
+ }
598
+ let res;
599
+ try {
600
+ res = await fetch(url, init);
601
+ } finally {
602
+ clearTimeout(timer);
603
+ }
604
+ const text = await res.text();
605
+ const parsed = text ? safeJson(text) : void 0;
606
+ if (res.ok) {
607
+ log.debug("http ok", { status: res.status });
608
+ return parsed;
609
+ }
610
+ if (res.status === 401) {
611
+ if (sdkManaged && !reauthed) {
612
+ reauthed = true;
613
+ if (o.auth.kind === "service") {
614
+ this.opts.provider.invalidate?.(o.auth.credentials ?? "backend");
615
+ } else if (this.opts.provider.expireAnonymous) {
616
+ this.opts.provider.expireAnonymous();
617
+ } else {
618
+ this.opts.provider.invalidateAnonymous?.();
619
+ }
620
+ log.warn("sdk-managed 401, re-authing once", { authKind: o.auth.kind });
621
+ continue;
622
+ }
623
+ throw errorFromResponse(res.status, `${o.method} ${o.path} \u2192 401`, parsed);
624
+ }
625
+ const retryable = res.status >= 500 || res.status === 429;
626
+ if (retryable && attempt < maxAttempts) {
627
+ const retryAfter = Number(res.headers.get("Retry-After"));
628
+ const backoff = Number.isFinite(retryAfter) && retryAfter >= 0 ? retryAfter * 1e3 : Math.min(1e3 * 2 ** (attempt - 1), 8e3) + Math.random() * 100;
629
+ log.warn("retryable failure", { status: res.status, attempt, backoffMs: backoff });
630
+ await this.sleep(backoff);
631
+ continue;
632
+ }
633
+ log.error("http error (final)", { status: res.status, attempt });
634
+ throw errorFromResponse(res.status, `${o.method} ${o.path} \u2192 ${res.status}`, parsed);
635
+ }
636
+ }
637
+ };
638
+
639
+ // src/core/require-customer.ts
640
+ function requireCustomer(auth2) {
641
+ if (auth2 && (auth2.kind === "customer" || auth2.kind === "raw")) return auth2;
642
+ throw new EmporixAuthError("This operation requires a customer or raw AuthContext");
643
+ }
644
+
645
+ // src/services/customer.ts
646
+ var CustomerService = class {
647
+ constructor(ctx) {
648
+ this.ctx = ctx;
649
+ }
650
+ ctx;
651
+ /** Obtains an anonymous storefront session (accessToken + sessionId + refreshToken). */
652
+ async anonymous() {
653
+ return this.ctx.tokenProvider.getAnonymousToken();
654
+ }
655
+ /** Registers a customer. Default auth: anonymous. */
656
+ async signup(input, auth2 = { kind: "anonymous" }) {
657
+ return this.ctx.http.request({
658
+ method: "POST",
659
+ path: `/customer/${this.ctx.tenant}/signup`,
660
+ auth: auth2,
661
+ body: input
662
+ });
663
+ }
664
+ /**
665
+ * Logs a customer in. Threads the anonymous token so the session (and its
666
+ * cart) survives — losing it silently creates a new session per Emporix docs.
667
+ * Wire `accessToken` is mapped to `customerToken`.
668
+ */
669
+ async login(creds, opts = {}, auth2 = { kind: "anonymous" }) {
670
+ const effective = opts.anonymousToken ? { kind: "raw", token: opts.anonymousToken } : auth2;
671
+ const wire = await this.ctx.http.request({
672
+ method: "POST",
673
+ path: `/customer/${this.ctx.tenant}/login`,
674
+ auth: effective,
675
+ body: creds
676
+ });
677
+ return {
678
+ customerToken: wire.access_token ?? wire.accessToken ?? "",
679
+ saasToken: wire.saas_token ?? wire.saasToken ?? "",
680
+ refreshToken: wire.refresh_token ?? wire.refreshToken ?? "",
681
+ sessionId: wire.session_id,
682
+ expiresIn: wire.expires_in
683
+ };
684
+ }
685
+ /**
686
+ * Refreshes an authenticated customer session via its refresh token,
687
+ * preserving the same `sessionId`. Calls
688
+ * `GET /customer/{tenant}/refreshauthtoken?refreshToken=…`, which **must be
689
+ * authorized with an anonymous token** (default auth: anonymous) — not the
690
+ * expired customer token. The refresh response does **not** include a
691
+ * `saas_token`; pass the original `saasToken` to carry it forward (it has
692
+ * its own, independent lifetime).
693
+ */
694
+ async refresh(input, auth2 = { kind: "anonymous" }) {
695
+ const query = { refreshToken: input.refreshToken };
696
+ if (input.legalEntityId) query.legalEntityId = input.legalEntityId;
697
+ const wire = await this.ctx.http.request({
698
+ method: "GET",
699
+ path: `/customer/${this.ctx.tenant}/refreshauthtoken`,
700
+ query,
701
+ auth: auth2
702
+ });
703
+ return {
704
+ customerToken: wire.access_token ?? wire.accessToken ?? "",
705
+ // Refresh does not return a saas_token — carry the original forward.
706
+ saasToken: input.saasToken ?? "",
707
+ refreshToken: wire.refresh_token ?? wire.refreshToken ?? "",
708
+ sessionId: wire.session_id,
709
+ expiresIn: wire.expires_in
710
+ };
711
+ }
712
+ /**
713
+ * Logs the customer out server-side: `GET /customer/{tenant}/logout?
714
+ * accessToken=…` authorized with the customer token. Invalidates the
715
+ * access token (204 No Content). Requires customer/raw auth — the token is
716
+ * sent both as the bearer and the `accessToken` query param (per Emporix).
717
+ */
718
+ async logout(auth2) {
719
+ const ctx = requireCustomer(auth2);
720
+ const token = ctx.token;
721
+ await this.ctx.http.request({
722
+ method: "GET",
723
+ path: `/customer/${this.ctx.tenant}/logout`,
724
+ query: { accessToken: token },
725
+ auth: ctx
726
+ });
727
+ }
728
+ /**
729
+ * Authorization-Code SSO: exchanges the IdP `code` for an Emporix customer
730
+ * session via `POST /customer/{tenant}/socialLogin`. The browser performs
731
+ * the IdP redirect itself; the SDK only does this Emporix exchange.
732
+ * Default auth: anonymous (Emporix requires an anonymous Bearer). The
733
+ * response has no `session_id` and returns `expires_in` as a string —
734
+ * normalized to a number here.
735
+ */
736
+ async socialLogin(input, auth2 = { kind: "anonymous" }) {
737
+ const query = {
738
+ code: input.code,
739
+ redirect_uri: input.redirectUri
740
+ };
741
+ if (input.codeVerifier) query.code_verifier = input.codeVerifier;
742
+ const wire = await this.ctx.http.request({
743
+ method: "POST",
744
+ path: `/customer/${this.ctx.tenant}/socialLogin`,
745
+ query,
746
+ auth: auth2,
747
+ ...input.sessionId ? { headers: { "session-id": input.sessionId } } : {}
748
+ });
749
+ return {
750
+ customerToken: wire.access_token ?? wire.accessToken ?? "",
751
+ saasToken: wire.saas_token ?? wire.saasToken ?? "",
752
+ refreshToken: wire.refresh_token ?? wire.refreshToken ?? "",
753
+ sessionId: wire.session_id,
754
+ expiresIn: wire.expires_in != null ? Number(wire.expires_in) : void 0,
755
+ ...wire.social_access_token ? { socialAccessToken: wire.social_access_token } : {},
756
+ ...wire.social_id_token ? { socialIdToken: wire.social_id_token } : {}
757
+ };
758
+ }
759
+ /**
760
+ * RFC 8693 token exchange: exchanges an external IdP JWT for an Emporix
761
+ * customer session via `POST /customer/{tenant}/exchangeauthtoken`.
762
+ * Emporix uses a proprietary query-param wire (not the RFC form body).
763
+ * Default auth: anonymous. `config` selects a site-specific IdP config
764
+ * (multi-site); omit for the tenant default. Returns `session_id` and a
765
+ * fresh `saas_token`; `expires_in` is an integer (normalized anyway).
766
+ */
767
+ async exchangeToken(input, auth2 = { kind: "anonymous" }) {
768
+ const query = { subjectAccessToken: input.subjectToken };
769
+ if (input.config) query.config = input.config;
770
+ const wire = await this.ctx.http.request({
771
+ method: "POST",
772
+ path: `/customer/${this.ctx.tenant}/exchangeauthtoken`,
773
+ query,
774
+ auth: auth2
775
+ });
776
+ return {
777
+ customerToken: wire.access_token ?? wire.accessToken ?? "",
778
+ saasToken: wire.saas_token ?? wire.saasToken ?? "",
779
+ refreshToken: wire.refresh_token ?? wire.refreshToken ?? "",
780
+ sessionId: wire.session_id,
781
+ expiresIn: wire.expires_in != null ? Number(wire.expires_in) : void 0
782
+ };
783
+ }
784
+ /** Returns the authenticated customer. Requires customer/raw auth. */
785
+ async me(auth2) {
786
+ return this.ctx.http.request({
787
+ method: "GET",
788
+ path: `/customer/${this.ctx.tenant}/me`,
789
+ auth: requireCustomer(auth2)
790
+ });
791
+ }
792
+ /** Updates the authenticated customer. Requires customer/raw auth. */
793
+ async update(patch, auth2) {
794
+ return this.ctx.http.request({
795
+ method: "PUT",
796
+ path: `/customer/${this.ctx.tenant}/me`,
797
+ auth: requireCustomer(auth2),
798
+ body: patch
799
+ });
800
+ }
801
+ /** Changes the password. Requires customer/raw auth. */
802
+ async changePassword(input, auth2) {
803
+ await this.ctx.http.request({
804
+ method: "PUT",
805
+ path: `/customer/${this.ctx.tenant}/password`,
806
+ auth: requireCustomer(auth2),
807
+ body: input
808
+ });
809
+ }
810
+ /** Requests a password reset email. Default auth: anonymous. */
811
+ async requestPasswordReset(input, auth2 = { kind: "anonymous" }) {
812
+ await this.ctx.http.request({
813
+ method: "POST",
814
+ path: `/customer/${this.ctx.tenant}/password/reset`,
815
+ auth: auth2,
816
+ body: input
817
+ });
818
+ }
819
+ /** Confirms a password reset. Default auth: anonymous. */
820
+ async confirmPasswordReset(input, auth2 = { kind: "anonymous" }) {
821
+ await this.ctx.http.request({
822
+ method: "POST",
823
+ path: `/customer/${this.ctx.tenant}/password/reset/confirm`,
824
+ auth: auth2,
825
+ body: input
826
+ });
827
+ }
828
+ /** Address sub-resource. All operations require customer/raw auth. */
829
+ addresses = {
830
+ list: async (auth2) => this.ctx.http.request({
831
+ method: "GET",
832
+ path: `/customer/${this.ctx.tenant}/me/addresses`,
833
+ auth: requireCustomer(auth2)
834
+ }),
835
+ add: async (address, auth2) => this.ctx.http.request({
836
+ method: "POST",
837
+ path: `/customer/${this.ctx.tenant}/me/addresses`,
838
+ auth: requireCustomer(auth2),
839
+ body: address
840
+ }),
841
+ update: async (id, patch, auth2) => this.ctx.http.request({
842
+ method: "PUT",
843
+ path: `/customer/${this.ctx.tenant}/me/addresses/${id}`,
844
+ auth: requireCustomer(auth2),
845
+ body: patch
846
+ }),
847
+ remove: async (id, auth2) => this.ctx.http.request({
848
+ method: "DELETE",
849
+ path: `/customer/${this.ctx.tenant}/me/addresses/${id}`,
850
+ auth: requireCustomer(auth2)
851
+ })
852
+ };
853
+ };
854
+
855
+ // src/core/context.ts
856
+ async function* iterateAll(fetchPage, start = 1) {
857
+ let p = start;
858
+ for (; ; ) {
859
+ const page = await fetchPage(p);
860
+ for (const it of page.items) yield it;
861
+ if (!page.hasNextPage) return;
862
+ p += 1;
863
+ }
864
+ }
865
+
866
+ // src/services/product.ts
867
+ var ANON = { kind: "anonymous" };
868
+ var ProductService = class {
869
+ constructor(ctx) {
870
+ this.ctx = ctx;
871
+ }
872
+ ctx;
873
+ /** Fetches one product by id. */
874
+ async get(productId, _opts, auth2 = ANON) {
875
+ return this.ctx.http.request({
876
+ method: "GET",
877
+ path: `/product/${this.ctx.tenant}/products/${productId}`,
878
+ auth: auth2
879
+ });
880
+ }
881
+ /** Fetches one product by its code. */
882
+ async getByCode(code, auth2 = ANON) {
883
+ const rows = await this.ctx.http.request({
884
+ method: "GET",
885
+ path: `/product/${this.ctx.tenant}/products`,
886
+ query: { q: `code:${code}` },
887
+ auth: auth2
888
+ });
889
+ const first = rows[0];
890
+ if (!first) throw new Error(`No product with code "${code}"`);
891
+ return first;
892
+ }
893
+ /** One page of products. */
894
+ async list(params = {}, auth2 = ANON) {
895
+ const pageNumber = params.pageNumber ?? 1;
896
+ const pageSize = params.pageSize ?? 50;
897
+ const items = await this.ctx.http.request({
898
+ method: "GET",
899
+ path: `/product/${this.ctx.tenant}/products`,
900
+ query: { pageNumber, pageSize },
901
+ auth: auth2
902
+ });
903
+ return { items, pageNumber, pageSize, hasNextPage: items.length === pageSize };
904
+ }
905
+ /** Async-iterates every product across pages. */
906
+ listAll(params = {}, auth2 = ANON) {
907
+ const pageSize = params.pageSize ?? 50;
908
+ return iterateAll((pageNumber) => this.list({ pageNumber, pageSize }, auth2));
909
+ }
910
+ /** Searches products by free-text query. */
911
+ async search(query, params = {}, auth2 = ANON) {
912
+ const pageNumber = params.pageNumber ?? 1;
913
+ const pageSize = params.pageSize ?? 50;
914
+ const items = await this.ctx.http.request({
915
+ method: "GET",
916
+ path: `/product/${this.ctx.tenant}/products`,
917
+ query: { q: query, pageNumber, pageSize },
918
+ auth: auth2
919
+ });
920
+ return { items, pageNumber, pageSize, hasNextPage: items.length === pageSize };
921
+ }
922
+ /**
923
+ * Bulk fetch by id. POSTs `/products/search` with `q="id:(id1,id2,…)"`,
924
+ * chunking when the list is larger than `options.chunkSize` (default
925
+ * 100). An empty list short-circuits with no HTTP call. **Order is not
926
+ * guaranteed** across chunks — re-index by `id` if order matters.
927
+ */
928
+ async searchByIds(ids, options = {}, auth2 = ANON) {
929
+ if (ids.length === 0) return [];
930
+ const chunkSize = options.chunkSize ?? 100;
931
+ const chunks = [];
932
+ for (let i = 0; i < ids.length; i += chunkSize) {
933
+ chunks.push(ids.slice(i, i + chunkSize));
934
+ }
935
+ const pages = await Promise.all(
936
+ chunks.map(
937
+ (chunk) => this.ctx.http.request({
938
+ method: "POST",
939
+ path: `/product/${this.ctx.tenant}/products/search`,
940
+ query: { pageSize: chunk.length },
941
+ auth: auth2,
942
+ body: { q: `id:(${chunk.join(",")})` }
943
+ })
944
+ )
945
+ );
946
+ return pages.flat();
947
+ }
948
+ };
949
+
950
+ // src/services/category.ts
951
+ var ANON2 = { kind: "anonymous" };
952
+ var CategoryService = class {
953
+ constructor(ctx) {
954
+ this.ctx = ctx;
955
+ }
956
+ ctx;
957
+ /** Fetches one category by id. */
958
+ async get(categoryId, auth2 = ANON2) {
959
+ return this.ctx.http.request({
960
+ method: "GET",
961
+ path: `/category/${this.ctx.tenant}/categories/${categoryId}`,
962
+ auth: auth2
963
+ });
964
+ }
965
+ /** One page of categories. */
966
+ async list(params = {}, auth2 = ANON2) {
967
+ const pageNumber = params.pageNumber ?? 1;
968
+ const pageSize = params.pageSize ?? 50;
969
+ const items = await this.ctx.http.request({
970
+ method: "GET",
971
+ path: `/category/${this.ctx.tenant}/categories`,
972
+ query: { pageNumber, pageSize },
973
+ auth: auth2
974
+ });
975
+ return { items, pageNumber, pageSize, hasNextPage: items.length === pageSize };
976
+ }
977
+ /** Async-iterates every category across pages. */
978
+ listAll(params = {}, auth2 = ANON2) {
979
+ const pageSize = params.pageSize ?? 50;
980
+ return iterateAll((pageNumber) => this.list({ pageNumber, pageSize }, auth2));
981
+ }
982
+ /** Fetches the category tree, optionally rooted at `rootId`. */
983
+ async tree(rootId, auth2 = ANON2) {
984
+ return this.ctx.http.request({
985
+ method: "GET",
986
+ path: `/category/${this.ctx.tenant}/categories/tree`,
987
+ query: rootId ? { rootId } : {},
988
+ auth: auth2
989
+ });
990
+ }
991
+ /** One page of products in a category. */
992
+ async productsIn(categoryId, params = {}, auth2 = ANON2) {
993
+ const pageNumber = params.pageNumber ?? 1;
994
+ const pageSize = params.pageSize ?? 50;
995
+ const items = await this.ctx.http.request({
996
+ method: "GET",
997
+ path: `/category/${this.ctx.tenant}/categories/${categoryId}/products`,
998
+ query: { pageNumber, pageSize },
999
+ auth: auth2
1000
+ });
1001
+ return { items, pageNumber, pageSize, hasNextPage: items.length === pageSize };
1002
+ }
1003
+ /**
1004
+ * Bulk fetch by id. POSTs `/categories/search` with `q="id:(id1,id2,…)"`,
1005
+ * chunking when the list is larger than `options.chunkSize` (default
1006
+ * 100). An empty list short-circuits with no HTTP call. **Order is not
1007
+ * guaranteed** across chunks — re-index by `id` if order matters.
1008
+ */
1009
+ async searchByIds(ids, options = {}, auth2 = ANON2) {
1010
+ if (ids.length === 0) return [];
1011
+ const chunkSize = options.chunkSize ?? 100;
1012
+ const chunks = [];
1013
+ for (let i = 0; i < ids.length; i += chunkSize) {
1014
+ chunks.push(ids.slice(i, i + chunkSize));
1015
+ }
1016
+ const pages = await Promise.all(
1017
+ chunks.map(
1018
+ (chunk) => this.ctx.http.request({
1019
+ method: "POST",
1020
+ path: `/category/${this.ctx.tenant}/categories/search`,
1021
+ query: { pageSize: chunk.length },
1022
+ auth: auth2,
1023
+ body: { q: `id:(${chunk.join(",")})` }
1024
+ })
1025
+ )
1026
+ );
1027
+ return pages.flat();
1028
+ }
1029
+ };
1030
+
1031
+ // src/services/cart.ts
1032
+ function requireCartAuth(auth2) {
1033
+ if (auth2 && (auth2.kind === "customer" || auth2.kind === "anonymous")) return auth2;
1034
+ throw new EmporixValidationError(
1035
+ "CartService requires an explicit { kind: 'customer' } or { kind: 'anonymous' } AuthContext"
1036
+ );
1037
+ }
1038
+ function requireCustomerAuth(auth2) {
1039
+ if (auth2 && auth2.kind === "customer") return auth2;
1040
+ throw new EmporixValidationError("cart.merge requires a { kind: 'customer' } AuthContext");
1041
+ }
1042
+ var CartService = class {
1043
+ constructor(ctx) {
1044
+ this.ctx = ctx;
1045
+ }
1046
+ ctx;
1047
+ base() {
1048
+ return `/cart/${this.ctx.tenant}/carts`;
1049
+ }
1050
+ /** Creates a cart. */
1051
+ async create(input, auth2) {
1052
+ return this.ctx.http.request({
1053
+ method: "POST",
1054
+ path: this.base(),
1055
+ auth: requireCartAuth(auth2),
1056
+ body: input ?? {}
1057
+ });
1058
+ }
1059
+ /** Fetches a cart by id. */
1060
+ async get(cartId, auth2) {
1061
+ return this.ctx.http.request({
1062
+ method: "GET",
1063
+ path: `${this.base()}/${cartId}`,
1064
+ auth: requireCartAuth(auth2)
1065
+ });
1066
+ }
1067
+ /** Returns the current cart for the session, or null if none. */
1068
+ /**
1069
+ * Get the customer / anonymous cart matching the given criteria. Per Emporix:
1070
+ * uniqueness is defined by `siteCode` + `type` + `legalEntityId` +
1071
+ * (`customerId` derived from a customer token, or `sessionId` derived from an
1072
+ * anonymous token). With `create: true`, Emporix creates a new cart if none
1073
+ * matches.
1074
+ *
1075
+ * Returns `null` on 404 (no cart found and `create` was not set). All other
1076
+ * errors are propagated.
1077
+ */
1078
+ async getCurrent(auth2, opts) {
1079
+ const query = { siteCode: opts.siteCode };
1080
+ if (opts.type !== void 0) query.type = opts.type;
1081
+ if (opts.legalEntityId !== void 0) query.legalEntityId = opts.legalEntityId;
1082
+ if (opts.create) query.create = "true";
1083
+ try {
1084
+ return await this.ctx.http.request({
1085
+ method: "GET",
1086
+ path: this.base(),
1087
+ query,
1088
+ auth: requireCartAuth(auth2)
1089
+ });
1090
+ } catch (e) {
1091
+ if (e instanceof EmporixNotFoundError) return null;
1092
+ throw e;
1093
+ }
1094
+ }
1095
+ /** Adds an item. */
1096
+ async addItem(cartId, item, auth2) {
1097
+ return this.ctx.http.request({
1098
+ method: "POST",
1099
+ path: `${this.base()}/${cartId}/items`,
1100
+ auth: requireCartAuth(auth2),
1101
+ body: item
1102
+ });
1103
+ }
1104
+ /**
1105
+ * Adds multiple items in a single request via `POST /carts/{id}/itemsBatch`.
1106
+ * Server-side limit is 200 entries per call — callers handling larger sets
1107
+ * must chunk. The response carries a per-entry `status` (HTTP-style: 201
1108
+ * for added, 4xx/5xx for failed) and `index` matching the input position;
1109
+ * partial failures do **not** throw — inspect `status` per entry.
1110
+ */
1111
+ async addItemsBatch(cartId, items, auth2) {
1112
+ return this.ctx.http.request({
1113
+ method: "POST",
1114
+ path: `${this.base()}/${cartId}/itemsBatch`,
1115
+ auth: requireCartAuth(auth2),
1116
+ body: items
1117
+ });
1118
+ }
1119
+ /** Updates an item. */
1120
+ async updateItem(cartId, itemId, patch, auth2) {
1121
+ return this.ctx.http.request({
1122
+ method: "PUT",
1123
+ path: `${this.base()}/${cartId}/items/${itemId}`,
1124
+ auth: requireCartAuth(auth2),
1125
+ body: patch
1126
+ });
1127
+ }
1128
+ /** Removes an item. */
1129
+ async removeItem(cartId, itemId, auth2) {
1130
+ return this.ctx.http.request({
1131
+ method: "DELETE",
1132
+ path: `${this.base()}/${cartId}/items/${itemId}`,
1133
+ auth: requireCartAuth(auth2)
1134
+ });
1135
+ }
1136
+ /** Empties the cart. */
1137
+ async clear(cartId, auth2) {
1138
+ return this.ctx.http.request({
1139
+ method: "DELETE",
1140
+ path: `${this.base()}/${cartId}/items`,
1141
+ auth: requireCartAuth(auth2)
1142
+ });
1143
+ }
1144
+ /** Applies a coupon. */
1145
+ async applyCoupon(cartId, code, auth2) {
1146
+ return this.ctx.http.request({
1147
+ method: "POST",
1148
+ path: `${this.base()}/${cartId}/coupons`,
1149
+ auth: requireCartAuth(auth2),
1150
+ body: { code }
1151
+ });
1152
+ }
1153
+ /** Removes a coupon. */
1154
+ async removeCoupon(cartId, code, auth2) {
1155
+ return this.ctx.http.request({
1156
+ method: "DELETE",
1157
+ path: `${this.base()}/${cartId}/coupons/${code}`,
1158
+ auth: requireCartAuth(auth2)
1159
+ });
1160
+ }
1161
+ /** Sets the shipping address. */
1162
+ async setShippingAddress(cartId, address, auth2) {
1163
+ return this.ctx.http.request({
1164
+ method: "PUT",
1165
+ path: `${this.base()}/${cartId}/shipping-address`,
1166
+ auth: requireCartAuth(auth2),
1167
+ body: address
1168
+ });
1169
+ }
1170
+ /** Sets the billing address. */
1171
+ async setBillingAddress(cartId, address, auth2) {
1172
+ return this.ctx.http.request({
1173
+ method: "PUT",
1174
+ path: `${this.base()}/${cartId}/billing-address`,
1175
+ auth: requireCartAuth(auth2),
1176
+ body: address
1177
+ });
1178
+ }
1179
+ /**
1180
+ * Merges one or more anonymous carts into the specified customer cart.
1181
+ * Per Emporix: the target cart in the path **must belong to the logged-in
1182
+ * customer**, and each id in `anonymousCartIds` must belong to an anonymous
1183
+ * customer. Anonymous carts go `CLOSED` on success.
1184
+ *
1185
+ * Requires a customer `AuthContext`.
1186
+ */
1187
+ async merge(customerCartId, anonymousCartIds, auth2) {
1188
+ return this.ctx.http.request({
1189
+ method: "POST",
1190
+ path: `${this.base()}/${customerCartId}/merge`,
1191
+ auth: requireCustomerAuth(auth2),
1192
+ body: { carts: anonymousCartIds }
1193
+ });
1194
+ }
1195
+ };
1196
+
1197
+ // src/services/checkout.ts
1198
+ function isGuest(customer) {
1199
+ return customer?.guest === true;
1200
+ }
1201
+ function resolveAuth(auth2, guest) {
1202
+ if (auth2 && (auth2.kind === "customer" || auth2.kind === "raw")) return auth2;
1203
+ if (guest && auth2 && auth2.kind === "anonymous") return auth2;
1204
+ throw new EmporixAuthError(
1205
+ "checkout requires a customer/raw AuthContext (or anonymous for a guest checkout)"
1206
+ );
1207
+ }
1208
+ var CheckoutService = class {
1209
+ constructor(ctx) {
1210
+ this.ctx = ctx;
1211
+ }
1212
+ ctx;
1213
+ headers(opts, guest) {
1214
+ if (guest || !opts.saasToken) return void 0;
1215
+ return { "saas-token": opts.saasToken };
1216
+ }
1217
+ query(opts) {
1218
+ return opts.siteCode ? { siteCode: opts.siteCode } : void 0;
1219
+ }
1220
+ /** Checkout from a cart. Requires customer/raw auth (or anonymous for guest). */
1221
+ async placeOrder(input, auth2, opts = {}) {
1222
+ const guest = isGuest(input.customer);
1223
+ const headers = this.headers(opts, guest);
1224
+ const query = this.query(opts);
1225
+ return this.ctx.http.request({
1226
+ method: "POST",
1227
+ path: `/checkout/${this.ctx.tenant}/checkouts/order`,
1228
+ auth: resolveAuth(auth2, guest),
1229
+ ...query ? { query } : {},
1230
+ ...headers ? { headers } : {},
1231
+ body: input
1232
+ });
1233
+ }
1234
+ /** Checkout from a quote. */
1235
+ async placeOrderFromQuote(input, auth2, opts = {}) {
1236
+ const headers = this.headers(opts, false);
1237
+ const query = this.query(opts);
1238
+ return this.ctx.http.request({
1239
+ method: "POST",
1240
+ path: `/checkout/${this.ctx.tenant}/checkouts/order`,
1241
+ auth: resolveAuth(auth2, false),
1242
+ ...query ? { query } : {},
1243
+ ...headers ? { headers } : {},
1244
+ body: input
1245
+ });
1246
+ }
1247
+ };
1248
+
1249
+ // src/services/payment.ts
1250
+ var PaymentGatewayService = class {
1251
+ constructor(ctx) {
1252
+ this.ctx = ctx;
1253
+ }
1254
+ ctx;
1255
+ /** Lists configured frontend payment modes. */
1256
+ async listPaymentModes(auth2) {
1257
+ return this.ctx.http.request({
1258
+ method: "GET",
1259
+ path: `/payment-gateway/${this.ctx.tenant}/paymentmodes/frontend`,
1260
+ auth: requireCustomer(auth2)
1261
+ });
1262
+ }
1263
+ /** Authorizes a post-checkout (deferred) payment for an existing order. */
1264
+ async authorize(input, auth2) {
1265
+ return this.ctx.http.request({
1266
+ method: "POST",
1267
+ path: `/payment-gateway/${this.ctx.tenant}/payment/frontend/authorize`,
1268
+ auth: requireCustomer(auth2),
1269
+ body: input
1270
+ });
1271
+ }
1272
+ };
1273
+
1274
+ // src/services/price.ts
1275
+ var ANON3 = { kind: "anonymous" };
1276
+ var SERVICE = { kind: "service" };
1277
+ function requireContextAuth(auth2) {
1278
+ const a = auth2 ?? ANON3;
1279
+ if (a.kind === "anonymous" || a.kind === "customer" || a.kind === "raw") return a;
1280
+ throw new EmporixAuthError(
1281
+ "match-prices-by-context requires an anonymous, customer, or raw AuthContext"
1282
+ );
1283
+ }
1284
+ var PriceService = class {
1285
+ constructor(ctx) {
1286
+ this.ctx = ctx;
1287
+ }
1288
+ ctx;
1289
+ /**
1290
+ * Resolves prices using the session context bound to the bearer token
1291
+ * (currency/site/country were set at anonymous-login time). Default auth:
1292
+ * anonymous; pass a customer/raw context for personalized pricing.
1293
+ */
1294
+ async matchByContext(input, auth2) {
1295
+ return this.ctx.http.request({
1296
+ method: "POST",
1297
+ path: `/price/${this.ctx.tenant}/match-prices-by-context`,
1298
+ auth: requireContextAuth(auth2),
1299
+ body: input
1300
+ });
1301
+ }
1302
+ /**
1303
+ * Resolves prices from an explicit context. Default auth: service
1304
+ * (requires `price.price_read` / `price.price_manage`).
1305
+ */
1306
+ async match(input, auth2 = SERVICE) {
1307
+ return this.ctx.http.request({
1308
+ method: "POST",
1309
+ path: `/price/${this.ctx.tenant}/match-prices`,
1310
+ auth: auth2,
1311
+ body: input
1312
+ });
1313
+ }
1314
+ };
1315
+
1316
+ // src/services/media.ts
1317
+ var SERVICE2 = { kind: "service" };
1318
+ function isProductRef(r, productId) {
1319
+ return r.type === "PRODUCT" && r.id === productId;
1320
+ }
1321
+ var MediaService = class {
1322
+ constructor(ctx) {
1323
+ this.ctx = ctx;
1324
+ }
1325
+ ctx;
1326
+ base() {
1327
+ return `/media/${this.ctx.tenant}/assets`;
1328
+ }
1329
+ /** Create an asset. BLOB uploads via multipart; LINK via JSON. */
1330
+ async create(input, auth2 = SERVICE2) {
1331
+ if (input.kind === "blob") {
1332
+ const fd = new FormData();
1333
+ fd.set("file", input.file);
1334
+ fd.set("body", JSON.stringify(input.body));
1335
+ return this.ctx.http.request({
1336
+ method: "POST",
1337
+ path: this.base(),
1338
+ auth: auth2,
1339
+ body: fd
1340
+ });
1341
+ }
1342
+ return this.ctx.http.request({
1343
+ method: "POST",
1344
+ path: this.base(),
1345
+ auth: auth2,
1346
+ body: input.body
1347
+ });
1348
+ }
1349
+ /** List assets. Optional `query` is passed through to Emporix verbatim. */
1350
+ async list(query, auth2 = SERVICE2) {
1351
+ return this.ctx.http.request({
1352
+ method: "GET",
1353
+ path: this.base(),
1354
+ auth: auth2,
1355
+ ...query ? { query } : {}
1356
+ });
1357
+ }
1358
+ /** Fetch an asset by id. */
1359
+ async get(assetId, auth2 = SERVICE2) {
1360
+ return this.ctx.http.request({
1361
+ method: "GET",
1362
+ path: `${this.base()}/${assetId}`,
1363
+ auth: auth2
1364
+ });
1365
+ }
1366
+ /** Update an asset (e.g. swap `refIds` or `access`). */
1367
+ async update(assetId, patch, auth2 = SERVICE2) {
1368
+ return this.ctx.http.request({
1369
+ method: "PUT",
1370
+ path: `${this.base()}/${assetId}`,
1371
+ auth: auth2,
1372
+ body: patch
1373
+ });
1374
+ }
1375
+ /** Remove an asset. */
1376
+ async remove(assetId, auth2 = SERVICE2) {
1377
+ await this.ctx.http.request({
1378
+ method: "DELETE",
1379
+ path: `${this.base()}/${assetId}`,
1380
+ auth: auth2
1381
+ });
1382
+ }
1383
+ /** Multipart upload sugar: builds `AssetCreateBlob` from input. */
1384
+ async uploadFile(input, auth2 = SERVICE2) {
1385
+ const body = {
1386
+ type: "BLOB",
1387
+ access: input.access ?? "PUBLIC",
1388
+ ...input.productId ? { refIds: [{ type: "PRODUCT", id: input.productId }] } : {},
1389
+ ...input.filename || input.mimeType ? {
1390
+ details: {
1391
+ ...input.filename ? { filename: input.filename } : {},
1392
+ ...input.mimeType ? { mimeType: input.mimeType } : {}
1393
+ }
1394
+ } : {}
1395
+ };
1396
+ return this.create({ kind: "blob", file: input.file, body }, auth2);
1397
+ }
1398
+ /** External-URL sugar: builds `AssetCreateLink`. */
1399
+ async link(input, auth2 = SERVICE2) {
1400
+ const body = {
1401
+ type: "LINK",
1402
+ access: input.access ?? "PUBLIC",
1403
+ url: input.url,
1404
+ ...input.productId ? { refIds: [{ type: "PRODUCT", id: input.productId }] } : {}
1405
+ };
1406
+ return this.create({ kind: "link", body }, auth2);
1407
+ }
1408
+ /** Idempotently add a PRODUCT refId to an asset. */
1409
+ async attachToProduct(assetId, productId, auth2 = SERVICE2) {
1410
+ const a = await this.get(assetId, auth2);
1411
+ const refIds = a.refIds ?? [];
1412
+ if (refIds.some((r) => isProductRef(r, productId))) return a;
1413
+ const next = [...refIds, { type: "PRODUCT", id: productId }];
1414
+ const patch = { type: a.type, refIds: next };
1415
+ return this.update(assetId, patch, auth2);
1416
+ }
1417
+ /** Remove a PRODUCT refId from an asset (no-op if absent). */
1418
+ async detachFromProduct(assetId, productId, auth2 = SERVICE2) {
1419
+ const a = await this.get(assetId, auth2);
1420
+ const refIds = a.refIds ?? [];
1421
+ const next = refIds.filter((r) => !isProductRef(r, productId));
1422
+ if (next.length === refIds.length) return a;
1423
+ const patch = { type: a.type, refIds: next };
1424
+ return this.update(assetId, patch, auth2);
1425
+ }
1426
+ /** Convenience: list assets attached to a product (server-side filter). */
1427
+ async listForProduct(productId, auth2 = SERVICE2) {
1428
+ return this.list({ "refIds.id": productId }, auth2);
1429
+ }
1430
+ };
1431
+
1432
+ // src/services/segment.ts
1433
+ function setIfDefined(q, key, value) {
1434
+ if (value !== void 0 && value !== "") {
1435
+ q[key] = value;
1436
+ }
1437
+ }
1438
+ var SegmentService = class {
1439
+ constructor(ctx, deps) {
1440
+ this.ctx = ctx;
1441
+ this.deps = deps;
1442
+ }
1443
+ ctx;
1444
+ deps;
1445
+ base() {
1446
+ return `/customer-segment/${this.ctx.tenant}/segments`;
1447
+ }
1448
+ /** Lists segments the caller belongs to (with `segment_read_own`). */
1449
+ async list(query = {}, auth2) {
1450
+ const q = {};
1451
+ setIfDefined(q, "q", query.q);
1452
+ setIfDefined(q, "pageNumber", query.pageNumber);
1453
+ setIfDefined(q, "pageSize", query.pageSize);
1454
+ return this.ctx.http.request({
1455
+ method: "GET",
1456
+ path: this.base(),
1457
+ auth: requireCustomer(auth2),
1458
+ ...Object.keys(q).length ? { query: q } : {}
1459
+ });
1460
+ }
1461
+ /** Fetches a single segment by id. */
1462
+ async get(segmentId, auth2) {
1463
+ return this.ctx.http.request({
1464
+ method: "GET",
1465
+ path: `${this.base()}/${segmentId}`,
1466
+ auth: requireCustomer(auth2)
1467
+ });
1468
+ }
1469
+ /** Item assignments (PRODUCT + CATEGORY) across all the caller's active segments. */
1470
+ async listItems(query = {}, auth2) {
1471
+ const q = {};
1472
+ setIfDefined(q, "q", query.q);
1473
+ setIfDefined(q, "siteCode", query.siteCode);
1474
+ setIfDefined(q, "legalEntityId", query.legalEntityId);
1475
+ setIfDefined(q, "pageNumber", query.pageNumber);
1476
+ setIfDefined(q, "pageSize", query.pageSize);
1477
+ if (query.onlyActive !== void 0) q.onlyActive = String(query.onlyActive);
1478
+ return this.ctx.http.request({
1479
+ method: "GET",
1480
+ path: `${this.base()}/items`,
1481
+ auth: requireCustomer(auth2),
1482
+ ...Object.keys(q).length ? { query: q } : {}
1483
+ });
1484
+ }
1485
+ /** Item assignments of one specific segment. */
1486
+ async listSegmentItems(segmentId, query = {}, auth2) {
1487
+ const q = {};
1488
+ setIfDefined(q, "q", query.q);
1489
+ setIfDefined(q, "legalEntityId", query.legalEntityId);
1490
+ setIfDefined(q, "pageNumber", query.pageNumber);
1491
+ setIfDefined(q, "pageSize", query.pageSize);
1492
+ return this.ctx.http.request({
1493
+ method: "GET",
1494
+ path: `${this.base()}/${segmentId}/items`,
1495
+ auth: requireCustomer(auth2),
1496
+ ...Object.keys(q).length ? { query: q } : {}
1497
+ });
1498
+ }
1499
+ /** Category tree built only from the caller's active segments. */
1500
+ async getCategoryTree(query = {}, auth2) {
1501
+ const q = {};
1502
+ setIfDefined(q, "siteCode", query.siteCode);
1503
+ setIfDefined(q, "legalEntityId", query.legalEntityId);
1504
+ return this.ctx.http.request({
1505
+ method: "GET",
1506
+ path: `${this.base()}/items/category-trees`,
1507
+ auth: requireCustomer(auth2),
1508
+ ...Object.keys(q).length ? { query: q } : {}
1509
+ });
1510
+ }
1511
+ async pickItemIds(kind, query, auth2) {
1512
+ const rows = await this.listItems(query, auth2);
1513
+ const ids = [];
1514
+ for (const r of rows) {
1515
+ const id = r.item?.id;
1516
+ if (r.type === kind && typeof id === "string") ids.push(id);
1517
+ }
1518
+ return ids;
1519
+ }
1520
+ /** Product ids assigned to the caller's active segments. */
1521
+ async listMyProductIds(query, auth2) {
1522
+ return this.pickItemIds("PRODUCT", query ?? {}, auth2);
1523
+ }
1524
+ /** Category ids assigned to the caller's active segments. */
1525
+ async listMyCategoryIds(query, auth2) {
1526
+ return this.pickItemIds("CATEGORY", query ?? {}, auth2);
1527
+ }
1528
+ /**
1529
+ * Hydrates `listMyProductIds` via `ProductService.get` in parallel.
1530
+ * Resolves in the same order as the id list. Any single failure rejects
1531
+ * the whole batch (`Promise.all`); use the id-list method + your own
1532
+ * tolerance strategy if partial success matters.
1533
+ */
1534
+ async listMyProducts(query, auth2) {
1535
+ const pageNumber = query?.pageNumber ?? 1;
1536
+ const pageSize = query?.pageSize ?? 20;
1537
+ const sourceItems = await this.listItems(
1538
+ { ...query ?? {}, pageNumber, pageSize },
1539
+ auth2
1540
+ );
1541
+ const ids = [];
1542
+ for (const r of sourceItems) {
1543
+ if (r.type === "PRODUCT" && typeof r.item?.id === "string") ids.push(r.item.id);
1544
+ }
1545
+ const items = await this.deps.products.searchByIds(ids, void 0, auth2);
1546
+ return { items, pageNumber, pageSize, hasNextPage: sourceItems.length === pageSize };
1547
+ }
1548
+ /**
1549
+ * Hydrates a page of the caller's segment CATEGORY assignments into
1550
+ * real categories via one bulk `categories.searchByIds` call. Same
1551
+ * `hasNextPage` semantic as `listMyProducts`.
1552
+ */
1553
+ async listMyCategories(query, auth2) {
1554
+ const pageNumber = query?.pageNumber ?? 1;
1555
+ const pageSize = query?.pageSize ?? 20;
1556
+ const sourceItems = await this.listItems(
1557
+ { ...query ?? {}, pageNumber, pageSize },
1558
+ auth2
1559
+ );
1560
+ const ids = [];
1561
+ for (const r of sourceItems) {
1562
+ if (r.type === "CATEGORY" && typeof r.item?.id === "string") ids.push(r.item.id);
1563
+ }
1564
+ const items = await this.deps.categories.searchByIds(ids, void 0, auth2);
1565
+ return { items, pageNumber, pageSize, hasNextPage: sourceItems.length === pageSize };
1566
+ }
1567
+ };
1568
+
1569
+ // src/services/site.ts
1570
+ var ANON4 = auth.anonymous();
1571
+ var SiteService = class {
1572
+ constructor(ctx) {
1573
+ this.ctx = ctx;
1574
+ }
1575
+ ctx;
1576
+ /** Lists active sites. */
1577
+ async list(authCtx = ANON4) {
1578
+ return this.ctx.http.request({
1579
+ method: "GET",
1580
+ path: `/site/${this.ctx.tenant}/sites`,
1581
+ auth: authCtx
1582
+ });
1583
+ }
1584
+ /** Retrieves one site by code. */
1585
+ async get(code, authCtx = ANON4) {
1586
+ return this.ctx.http.request({
1587
+ method: "GET",
1588
+ path: `/site/${this.ctx.tenant}/sites/${code}`,
1589
+ auth: authCtx
1590
+ });
1591
+ }
1592
+ /**
1593
+ * Returns the tenant's default site (the one with `default: true`).
1594
+ * Throws if no default is configured — a tenant should always have one.
1595
+ */
1596
+ async current(authCtx = ANON4) {
1597
+ const sites = await this.list(authCtx);
1598
+ const def = sites.find((s) => s.default);
1599
+ if (!def) {
1600
+ throw new Error(
1601
+ `SiteService.current: no default site for tenant "${this.ctx.tenant}"`
1602
+ );
1603
+ }
1604
+ return def;
1605
+ }
1606
+ };
1607
+
1608
+ // src/services/session-context.ts
1609
+ var ANON5 = auth.anonymous();
1610
+ var SessionContextService = class {
1611
+ constructor(ctx) {
1612
+ this.ctx = ctx;
1613
+ }
1614
+ ctx;
1615
+ /**
1616
+ * Retrieves the current session context, or `null` when the server returns
1617
+ * 404 (no session exists yet — the session-context is created server-side
1618
+ * only after the user creates a cart).
1619
+ */
1620
+ async get(authCtx = ANON5) {
1621
+ try {
1622
+ return await this.ctx.http.request({
1623
+ method: "GET",
1624
+ path: `/session-context/${this.ctx.tenant}/me/context`,
1625
+ auth: authCtx
1626
+ });
1627
+ } catch (err) {
1628
+ if (isNotFound(err)) return null;
1629
+ throw err;
1630
+ }
1631
+ }
1632
+ /**
1633
+ * Partially updates the current session context. Returns `true` when the
1634
+ * PATCH was applied, `false` when there is no session context yet (the
1635
+ * server returned 404 on the version-lookup GET, so there is nothing to
1636
+ * update). Non-404 errors propagate.
1637
+ */
1638
+ async patch(input, authCtx = ANON5) {
1639
+ let version = input.version;
1640
+ if (version === void 0) {
1641
+ const current = await this.get(authCtx);
1642
+ if (current === null) return false;
1643
+ version = current.metadata?.version;
1644
+ if (version === void 0) {
1645
+ throw new Error(
1646
+ "SessionContextService.patch: no metadata.version in server response"
1647
+ );
1648
+ }
1649
+ }
1650
+ const { version: _v, ...fields } = input;
1651
+ await this.ctx.http.request({
1652
+ method: "PATCH",
1653
+ path: `/session-context/${this.ctx.tenant}/me/context`,
1654
+ body: {
1655
+ ...fields,
1656
+ metadata: { version }
1657
+ },
1658
+ auth: authCtx
1659
+ });
1660
+ return true;
1661
+ }
1662
+ };
1663
+ function isNotFound(err) {
1664
+ if (err && typeof err === "object") {
1665
+ const status = err.status;
1666
+ if (typeof status === "number" && status === 404) return true;
1667
+ }
1668
+ return false;
1669
+ }
1670
+
1671
+ // src/services/companies.ts
1672
+ var CompaniesService = class {
1673
+ constructor(ctx) {
1674
+ this.ctx = ctx;
1675
+ }
1676
+ ctx;
1677
+ base() {
1678
+ return `/customer-management/${this.ctx.tenant}/legal-entities`;
1679
+ }
1680
+ /** Lists the legal entities the calling customer is assigned to. */
1681
+ async listMine(auth2) {
1682
+ return this.ctx.http.request({
1683
+ method: "GET",
1684
+ path: this.base(),
1685
+ auth: auth2
1686
+ });
1687
+ }
1688
+ /** Fetches a single legal entity by id. */
1689
+ async get(legalEntityId, auth2) {
1690
+ return this.ctx.http.request({
1691
+ method: "GET",
1692
+ path: `${this.base()}/${legalEntityId}`,
1693
+ auth: auth2
1694
+ });
1695
+ }
1696
+ /** Creates a legal entity. Requires `customermanagement.legalentity_manage`. */
1697
+ async create(input, auth2) {
1698
+ return this.ctx.http.request({
1699
+ method: "POST",
1700
+ path: this.base(),
1701
+ auth: auth2,
1702
+ body: input
1703
+ });
1704
+ }
1705
+ /** Patches a legal entity. */
1706
+ async update(legalEntityId, patch, auth2) {
1707
+ return this.ctx.http.request({
1708
+ method: "PATCH",
1709
+ path: `${this.base()}/${legalEntityId}`,
1710
+ auth: auth2,
1711
+ body: patch
1712
+ });
1713
+ }
1714
+ /** Deletes a legal entity (async cascade on the server). */
1715
+ async delete(legalEntityId, auth2) {
1716
+ await this.ctx.http.request({
1717
+ method: "DELETE",
1718
+ path: `${this.base()}/${legalEntityId}`,
1719
+ auth: auth2
1720
+ });
1721
+ }
1722
+ };
1723
+
1724
+ // src/services/contacts.ts
1725
+ var ContactsService = class {
1726
+ constructor(ctx) {
1727
+ this.ctx = ctx;
1728
+ }
1729
+ ctx;
1730
+ base() {
1731
+ return `/customer-management/${this.ctx.tenant}/contact-assignments`;
1732
+ }
1733
+ /** Lists contact assignments for one legal entity. */
1734
+ async listForCompany(legalEntityId, auth2) {
1735
+ return this.ctx.http.request({
1736
+ method: "GET",
1737
+ path: this.base(),
1738
+ query: { legalEntityId },
1739
+ auth: auth2
1740
+ });
1741
+ }
1742
+ /** Creates a contact assignment. */
1743
+ async assign(input, auth2) {
1744
+ return this.ctx.http.request({
1745
+ method: "POST",
1746
+ path: this.base(),
1747
+ auth: auth2,
1748
+ body: input
1749
+ });
1750
+ }
1751
+ /** Patches a contact assignment. */
1752
+ async update(contactAssignmentId, patch, auth2) {
1753
+ return this.ctx.http.request({
1754
+ method: "PATCH",
1755
+ path: `${this.base()}/${contactAssignmentId}`,
1756
+ auth: auth2,
1757
+ body: patch
1758
+ });
1759
+ }
1760
+ /** Deletes a contact assignment. */
1761
+ async unassign(contactAssignmentId, auth2) {
1762
+ await this.ctx.http.request({
1763
+ method: "DELETE",
1764
+ path: `${this.base()}/${contactAssignmentId}`,
1765
+ auth: auth2
1766
+ });
1767
+ }
1768
+ };
1769
+
1770
+ // src/services/locations.ts
1771
+ var LocationsService = class {
1772
+ constructor(ctx) {
1773
+ this.ctx = ctx;
1774
+ }
1775
+ ctx;
1776
+ base() {
1777
+ return `/customer-management/${this.ctx.tenant}/locations`;
1778
+ }
1779
+ /** Lists locations owned by one legal entity. */
1780
+ async listForCompany(legalEntityId, auth2) {
1781
+ return this.ctx.http.request({
1782
+ method: "GET",
1783
+ path: this.base(),
1784
+ query: { legalEntityId },
1785
+ auth: auth2
1786
+ });
1787
+ }
1788
+ /** Fetches one location by id. */
1789
+ async get(locationId, auth2) {
1790
+ return this.ctx.http.request({
1791
+ method: "GET",
1792
+ path: `${this.base()}/${locationId}`,
1793
+ auth: auth2
1794
+ });
1795
+ }
1796
+ /** Creates a location for a legal entity. */
1797
+ async create(input, auth2) {
1798
+ return this.ctx.http.request({
1799
+ method: "POST",
1800
+ path: this.base(),
1801
+ auth: auth2,
1802
+ body: input
1803
+ });
1804
+ }
1805
+ /** Patches a location. */
1806
+ async update(locationId, patch, auth2) {
1807
+ return this.ctx.http.request({
1808
+ method: "PATCH",
1809
+ path: `${this.base()}/${locationId}`,
1810
+ auth: auth2,
1811
+ body: patch
1812
+ });
1813
+ }
1814
+ /** Deletes a location. */
1815
+ async delete(locationId, auth2) {
1816
+ await this.ctx.http.request({
1817
+ method: "DELETE",
1818
+ path: `${this.base()}/${locationId}`,
1819
+ auth: auth2
1820
+ });
1821
+ }
1822
+ };
1823
+
1824
+ // src/services/customer-groups.ts
1825
+ var CustomerGroupsService = class {
1826
+ constructor(ctx) {
1827
+ this.ctx = ctx;
1828
+ }
1829
+ ctx;
1830
+ base() {
1831
+ return `/iam/${this.ctx.tenant}/groups`;
1832
+ }
1833
+ /** Lists customer groups belonging to one legal entity. */
1834
+ async listForCompany(legalEntityId, auth2) {
1835
+ return this.ctx.http.request({
1836
+ method: "GET",
1837
+ path: this.base(),
1838
+ query: { "b2b.legalEntityId": legalEntityId },
1839
+ auth: auth2
1840
+ });
1841
+ }
1842
+ };
1843
+
1844
+ // src/services/orders.ts
1845
+ function setIfDefined2(q, key, value) {
1846
+ if (value !== void 0 && value !== "") {
1847
+ q[key] = value;
1848
+ }
1849
+ }
1850
+ var OrdersService = class {
1851
+ constructor(ctx) {
1852
+ this.ctx = ctx;
1853
+ }
1854
+ ctx;
1855
+ base() {
1856
+ return `/order-v2/${this.ctx.tenant}/orders`;
1857
+ }
1858
+ saasHeader(saasToken) {
1859
+ return saasToken ? { "saas-token": saasToken } : void 0;
1860
+ }
1861
+ /** Lists the calling customer's orders. */
1862
+ async listMine(auth2, opts = {}) {
1863
+ const query = {};
1864
+ setIfDefined2(query, "pageNumber", opts.pageNumber);
1865
+ setIfDefined2(query, "pageSize", opts.pageSize);
1866
+ setIfDefined2(query, "status", opts.status);
1867
+ setIfDefined2(query, "legalEntityId", opts.legalEntityId);
1868
+ setIfDefined2(query, "siteCode", opts.siteCode);
1869
+ const headers = this.saasHeader(opts.saasToken);
1870
+ return this.ctx.http.request({
1871
+ method: "GET",
1872
+ path: this.base(),
1873
+ query,
1874
+ auth: auth2,
1875
+ ...headers ? { headers } : {}
1876
+ });
1877
+ }
1878
+ /** Fetches one of the calling customer's orders by id. */
1879
+ async get(orderId, auth2, opts = {}) {
1880
+ const headers = this.saasHeader(opts.saasToken);
1881
+ return this.ctx.http.request({
1882
+ method: "GET",
1883
+ path: `${this.base()}/${orderId}`,
1884
+ auth: auth2,
1885
+ ...headers ? { headers } : {}
1886
+ });
1887
+ }
1888
+ /** Transitions an order to a new status. Server enforces legal transitions. */
1889
+ async transition(orderId, status, auth2, opts = {}) {
1890
+ const body = { status };
1891
+ if (opts.comment !== void 0) body.comment = opts.comment;
1892
+ const headers = this.saasHeader(opts.saasToken);
1893
+ await this.ctx.http.request({
1894
+ method: "POST",
1895
+ path: `${this.base()}/${orderId}/transitions`,
1896
+ auth: auth2,
1897
+ body,
1898
+ ...headers ? { headers } : {}
1899
+ });
1900
+ }
1901
+ /** Convenience: transitions to `DECLINED` (customer cancel). */
1902
+ async cancel(orderId, auth2, opts = {}) {
1903
+ await this.transition(orderId, "DECLINED", auth2, opts);
1904
+ }
1905
+ };
1906
+ var SalesOrdersService = class {
1907
+ constructor(ctx) {
1908
+ this.ctx = ctx;
1909
+ }
1910
+ ctx;
1911
+ base() {
1912
+ return `/order-v2/${this.ctx.tenant}/salesorders`;
1913
+ }
1914
+ /** Fetches a single sales-order by id. */
1915
+ async get(orderId, auth2) {
1916
+ return this.ctx.http.request({
1917
+ method: "GET",
1918
+ path: `${this.base()}/${orderId}`,
1919
+ auth: auth2
1920
+ });
1921
+ }
1922
+ /** Patches an existing order (status, mixins, customAttributes, metadata). */
1923
+ async update(orderId, patch, auth2, opts = {}) {
1924
+ const query = {};
1925
+ if (opts.recalculate === false) query.recalculate = "false";
1926
+ return this.ctx.http.request({
1927
+ method: "PATCH",
1928
+ path: `${this.base()}/${orderId}`,
1929
+ auth: auth2,
1930
+ ...Object.keys(query).length > 0 ? { query } : {},
1931
+ body: patch
1932
+ });
1933
+ }
1934
+ };
1935
+
1936
+ // src/client.ts
1937
+ var SDK_VERSION = "0.0.0";
1938
+ var EmporixClient = class {
1939
+ customers;
1940
+ products;
1941
+ categories;
1942
+ carts;
1943
+ checkout;
1944
+ payments;
1945
+ prices;
1946
+ media;
1947
+ segments;
1948
+ sites;
1949
+ sessionContext;
1950
+ companies;
1951
+ contacts;
1952
+ locations;
1953
+ customerGroups;
1954
+ orders;
1955
+ salesOrders;
1956
+ /** The validated tenant this client is bound to. */
1957
+ tenant;
1958
+ /**
1959
+ * The token provider in use by this client. Exposed so React/Next hosts can
1960
+ * call `attachAnonymousStore` to wire session persistence. Treat as
1961
+ * read-only — replacing it after construction is not supported.
1962
+ */
1963
+ tokenProvider;
1964
+ /**
1965
+ * The validated config used to construct this client. Exposed so React /
1966
+ * Next hosts can read static settings such as `credentials.storefront.context`
1967
+ * (siteCode, currency, targetLocation) without re-plumbing them through the
1968
+ * Provider tree. Treat as read-only.
1969
+ */
1970
+ config;
1971
+ resolver;
1972
+ constructor(config) {
1973
+ const cfg = validateConfig(config);
1974
+ this.tenant = cfg.tenant;
1975
+ this.config = cfg;
1976
+ let loggerObj = {};
1977
+ let baseLogger;
1978
+ if (cfg.logger === false) {
1979
+ baseLogger = createNoopLogger();
1980
+ } else if (cfg.logger && typeof cfg.logger.child === "function") {
1981
+ baseLogger = cfg.logger;
1982
+ } else if (cfg.logger) {
1983
+ loggerObj = cfg.logger;
1984
+ }
1985
+ this.resolver = new LevelResolver(loggerObj);
1986
+ const root = baseLogger ?? createConsoleLogger(this.resolver, {
1987
+ sdk: "emporix",
1988
+ sdkVersion: SDK_VERSION,
1989
+ tenant: cfg.tenant
1990
+ });
1991
+ const tokenProvider = cfg.tokenProvider ?? new DefaultTokenProvider(cfg);
1992
+ this.tokenProvider = tokenProvider;
1993
+ const mk = (service) => ({
1994
+ tenant: cfg.tenant,
1995
+ tokenProvider,
1996
+ logger: root.child({ service }),
1997
+ http: new HttpClient({
1998
+ host: cfg.host,
1999
+ provider: tokenProvider,
2000
+ logger: root.child({ service: "http" }),
2001
+ retry: cfg.retry,
2002
+ timeouts: cfg.timeouts
2003
+ })
2004
+ });
2005
+ this.customers = new CustomerService(mk("customer"));
2006
+ this.products = new ProductService(mk("product"));
2007
+ this.categories = new CategoryService(mk("category"));
2008
+ this.carts = new CartService(mk("cart"));
2009
+ this.checkout = new CheckoutService(mk("checkout"));
2010
+ this.payments = new PaymentGatewayService(mk("payment"));
2011
+ this.prices = new PriceService(mk("price"));
2012
+ this.media = new MediaService(mk("media"));
2013
+ this.segments = new SegmentService(mk("segment"), {
2014
+ products: this.products,
2015
+ categories: this.categories
2016
+ });
2017
+ this.sites = new SiteService(mk("site"));
2018
+ this.sessionContext = new SessionContextService(mk("session-context"));
2019
+ this.companies = new CompaniesService(mk("customer-management"));
2020
+ this.contacts = new ContactsService(mk("customer-management"));
2021
+ this.locations = new LocationsService(mk("customer-management"));
2022
+ this.customerGroups = new CustomerGroupsService(mk("iam"));
2023
+ this.orders = new OrdersService(mk("orders"));
2024
+ this.salesOrders = new SalesOrdersService(mk("sales-orders"));
2025
+ }
2026
+ /** Sets the runtime log level globally or for one service. */
2027
+ setLogLevel(level, opts = {}) {
2028
+ this.resolver.set(level, opts.service, opts.force ?? false);
2029
+ }
2030
+ /** Returns the effective log level for a service. */
2031
+ getLogLevel(service) {
2032
+ return this.resolver.get(service);
2033
+ }
2034
+ };
2035
+
2036
+ exports.CartService = CartService;
2037
+ exports.CategoryService = CategoryService;
2038
+ exports.CheckoutService = CheckoutService;
2039
+ exports.CompaniesService = CompaniesService;
2040
+ exports.ContactsService = ContactsService;
2041
+ exports.CustomerGroupsService = CustomerGroupsService;
2042
+ exports.CustomerService = CustomerService;
2043
+ exports.DEFAULT_HOST = DEFAULT_HOST;
2044
+ exports.DefaultTokenProvider = DefaultTokenProvider;
2045
+ exports.EmporixAuthError = EmporixAuthError;
2046
+ exports.EmporixClient = EmporixClient;
2047
+ exports.EmporixError = EmporixError;
2048
+ exports.EmporixForbiddenError = EmporixForbiddenError;
2049
+ exports.EmporixInsufficientScopeError = EmporixInsufficientScopeError;
2050
+ exports.EmporixNotFoundError = EmporixNotFoundError;
2051
+ exports.EmporixServerError = EmporixServerError;
2052
+ exports.EmporixValidationError = EmporixValidationError;
2053
+ exports.HttpClient = HttpClient;
2054
+ exports.LEVEL = LEVEL;
2055
+ exports.LevelResolver = LevelResolver;
2056
+ exports.LocationsService = LocationsService;
2057
+ exports.MediaService = MediaService;
2058
+ exports.OrdersService = OrdersService;
2059
+ exports.PaymentGatewayService = PaymentGatewayService;
2060
+ exports.PriceService = PriceService;
2061
+ exports.ProductService = ProductService;
2062
+ exports.SalesOrdersService = SalesOrdersService;
2063
+ exports.SegmentService = SegmentService;
2064
+ exports.SessionContextService = SessionContextService;
2065
+ exports.SiteService = SiteService;
2066
+ exports.auth = auth;
2067
+ exports.createConsoleLogger = createConsoleLogger;
2068
+ exports.createNoopLogger = createNoopLogger;
2069
+ exports.errorFromResponse = errorFromResponse;
2070
+ exports.iterateAll = iterateAll;
2071
+ exports.redact = redact;
2072
+ exports.resolveToken = resolveToken;
2073
+ exports.validateConfig = validateConfig;
2074
+ //# sourceMappingURL=index.cjs.map
2075
+ //# sourceMappingURL=index.cjs.map