@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.js ADDED
@@ -0,0 +1,791 @@
1
+ import { LocationsService } from './chunk-NM7VDXBR.js';
2
+ export { LocationsService } from './chunk-NM7VDXBR.js';
3
+ import { MediaService } from './chunk-F3WAQH2E.js';
4
+ export { MediaService } from './chunk-F3WAQH2E.js';
5
+ import { OrdersService, SalesOrdersService } from './chunk-6UNFMCHZ.js';
6
+ export { OrdersService, SalesOrdersService } from './chunk-6UNFMCHZ.js';
7
+ import { PaymentGatewayService } from './chunk-HKPZI7Y5.js';
8
+ export { PaymentGatewayService } from './chunk-HKPZI7Y5.js';
9
+ import { PriceService } from './chunk-HEO34WLO.js';
10
+ export { PriceService } from './chunk-HEO34WLO.js';
11
+ import { ProductService } from './chunk-QDABAMVN.js';
12
+ export { ProductService } from './chunk-QDABAMVN.js';
13
+ import { SegmentService } from './chunk-Z6PNI5VT.js';
14
+ export { SegmentService } from './chunk-Z6PNI5VT.js';
15
+ import { CartService } from './chunk-IEZHGU7F.js';
16
+ export { CartService } from './chunk-IEZHGU7F.js';
17
+ import { CategoryService } from './chunk-4BJUCCKJ.js';
18
+ export { CategoryService } from './chunk-4BJUCCKJ.js';
19
+ export { iterateAll } from './chunk-GWJ2U2CP.js';
20
+ import { CheckoutService } from './chunk-OMYVOOSX.js';
21
+ export { CheckoutService } from './chunk-OMYVOOSX.js';
22
+ import { CompaniesService } from './chunk-RXVVFNYN.js';
23
+ export { CompaniesService } from './chunk-RXVVFNYN.js';
24
+ import { ContactsService } from './chunk-B56JZNI7.js';
25
+ export { ContactsService } from './chunk-B56JZNI7.js';
26
+ import { CustomerGroupsService } from './chunk-SKX7KCMH.js';
27
+ export { CustomerGroupsService } from './chunk-SKX7KCMH.js';
28
+ import { CustomerService } from './chunk-TLH6SLAF.js';
29
+ export { CustomerService } from './chunk-TLH6SLAF.js';
30
+ import './chunk-BFQ732VZ.js';
31
+ import { EmporixAuthError, errorFromResponse } from './chunk-KTODT6DV.js';
32
+ export { EmporixAuthError, EmporixError, EmporixForbiddenError, EmporixInsufficientScopeError, EmporixNotFoundError, EmporixServerError, EmporixValidationError, errorFromResponse } from './chunk-KTODT6DV.js';
33
+
34
+ // src/core/config.ts
35
+ var DEFAULT_HOST = "https://api.emporix.io";
36
+ var TENANT_RE = /^[a-z][a-z0-9]{2,15}$/;
37
+ function validateConfig(input) {
38
+ if (!TENANT_RE.test(input.tenant)) {
39
+ throw new Error(
40
+ `Invalid tenant "${input.tenant}": must be lowercase, 3\u201316 chars, match ^[a-z][a-z0-9]+$`
41
+ );
42
+ }
43
+ if (!input.credentials) {
44
+ throw new Error("credentials is required (provide at least one of backend/storefront/custom)");
45
+ }
46
+ return {
47
+ tenant: input.tenant,
48
+ host: input.host ?? DEFAULT_HOST,
49
+ credentials: input.credentials,
50
+ tokenProvider: input.tokenProvider,
51
+ timeouts: {
52
+ connectMs: input.timeouts?.connectMs ?? 1e4,
53
+ readMs: input.timeouts?.readMs ?? 6e4
54
+ },
55
+ retry: { maxAttempts: input.retry?.maxAttempts ?? 3 },
56
+ cache: {
57
+ expirationBufferSeconds: input.cache?.expirationBufferSeconds ?? 60,
58
+ maxLifetimeSeconds: input.cache?.maxLifetimeSeconds ?? 3600
59
+ },
60
+ logger: input.logger
61
+ };
62
+ }
63
+
64
+ // src/core/logger.ts
65
+ var LEVEL = {
66
+ trace: 10,
67
+ debug: 20,
68
+ info: 30,
69
+ warn: 40,
70
+ error: 50,
71
+ silent: 60
72
+ };
73
+ function isValidLevel(v) {
74
+ return v !== void 0 && v in LEVEL;
75
+ }
76
+ function readEnv(name) {
77
+ try {
78
+ return typeof process !== "undefined" && process.env ? process.env[name] : void 0;
79
+ } catch {
80
+ return void 0;
81
+ }
82
+ }
83
+ var LevelResolver = class {
84
+ constructor(cfg, warn = () => {
85
+ }) {
86
+ this.warn = warn;
87
+ this.cfgLevel = cfg.level ?? "warn";
88
+ this.cfgServices = { ...cfg.services };
89
+ }
90
+ warn;
91
+ cfgLevel;
92
+ cfgServices;
93
+ forcedServices = {};
94
+ forcedGlobal;
95
+ warned = false;
96
+ envFor(svc) {
97
+ const raw = readEnv(`EMPORIX_LOG_LEVEL_${svc.toUpperCase()}`);
98
+ if (raw === void 0) return void 0;
99
+ if (isValidLevel(raw)) return raw;
100
+ if (!this.warned) {
101
+ this.warned = true;
102
+ this.warn(`Invalid EMPORIX_LOG_LEVEL_${svc.toUpperCase()}="${raw}" ignored`);
103
+ }
104
+ return void 0;
105
+ }
106
+ envGlobal() {
107
+ const raw = readEnv("EMPORIX_LOG_LEVEL");
108
+ if (raw === void 0) return void 0;
109
+ if (isValidLevel(raw)) return raw;
110
+ if (!this.warned) {
111
+ this.warned = true;
112
+ this.warn(`Invalid EMPORIX_LOG_LEVEL="${raw}" ignored`);
113
+ }
114
+ return void 0;
115
+ }
116
+ /**
117
+ * Effective level for a service. Forced overrides (from `set(..., force)`)
118
+ * take precedence over env vars; otherwise env > programmatic config.
119
+ */
120
+ get(svc) {
121
+ return this.forcedServices[svc] ?? this.envFor(svc) ?? this.forcedGlobal ?? this.envGlobal() ?? this.cfgServices[svc] ?? this.cfgLevel ?? "warn";
122
+ }
123
+ /** Numeric effective level for a service. */
124
+ numericLevel(svc) {
125
+ return LEVEL[this.get(svc)];
126
+ }
127
+ /** True if `svc`'s effective level allows emitting at `at`. */
128
+ isAtLeast(svc, at) {
129
+ return this.numericLevel(svc) <= LEVEL[at];
130
+ }
131
+ /** Mutates programmatic level (global or one service). Env-set levels are sticky unless `force`. */
132
+ set(level, svc, force = false) {
133
+ if (svc) {
134
+ const envBound = readEnv(`EMPORIX_LOG_LEVEL_${svc.toUpperCase()}`) !== void 0;
135
+ if (envBound && !force) {
136
+ this.warn(`Level for "${svc}" is env-controlled; pass force to override`);
137
+ return;
138
+ }
139
+ if (force) this.forcedServices[svc] = level;
140
+ else this.cfgServices[svc] = level;
141
+ } else {
142
+ const envBound = readEnv("EMPORIX_LOG_LEVEL") !== void 0;
143
+ if (envBound && !force) {
144
+ this.warn("Global level is env-controlled; pass force to override");
145
+ return;
146
+ }
147
+ if (force) this.forcedGlobal = level;
148
+ else this.cfgLevel = level;
149
+ }
150
+ }
151
+ };
152
+ var DEFAULT_REDACT = /* @__PURE__ */ new Set([
153
+ "authorization",
154
+ "password",
155
+ "oldpassword",
156
+ "newpassword",
157
+ "clientsecret",
158
+ "secret",
159
+ "access_token",
160
+ "refresh_token",
161
+ "customertoken",
162
+ "saastoken",
163
+ "saas-token",
164
+ "bearertoken",
165
+ "apikey",
166
+ "token"
167
+ ]);
168
+ function redact(value, extra = []) {
169
+ const keys = new Set(DEFAULT_REDACT);
170
+ for (const k of extra) keys.add(k.toLowerCase());
171
+ const walk = (v) => {
172
+ if (Array.isArray(v)) return v.map(walk);
173
+ if (v && typeof v === "object") {
174
+ const src = v;
175
+ if (typeof src.kind === "string" && "token" in src) return { kind: src.kind };
176
+ const out = {};
177
+ for (const [k, val] of Object.entries(src)) {
178
+ if (keys.has(k.toLowerCase())) {
179
+ out[k] = k.toLowerCase() === "authorization" && typeof val === "string" ? "Bearer ***redacted***" : "***redacted***";
180
+ } else {
181
+ out[k] = walk(val);
182
+ }
183
+ }
184
+ return out;
185
+ }
186
+ return v;
187
+ };
188
+ return walk(value);
189
+ }
190
+ var EMIT = {
191
+ trace: LEVEL.trace,
192
+ debug: LEVEL.debug,
193
+ info: LEVEL.info,
194
+ warn: LEVEL.warn,
195
+ error: LEVEL.error
196
+ };
197
+ var BaseLogger = class {
198
+ constructor(resolver, bindings, extraRedact) {
199
+ this.resolver = resolver;
200
+ this.bindings = bindings;
201
+ this.extraRedact = extraRedact;
202
+ }
203
+ resolver;
204
+ bindings;
205
+ extraRedact;
206
+ svc() {
207
+ return this.bindings.service ?? "http";
208
+ }
209
+ get level() {
210
+ return this.resolver.get(this.svc());
211
+ }
212
+ isLevelEnabled(level) {
213
+ return this.resolver.isAtLeast(this.svc(), level);
214
+ }
215
+ at(level, msg, fields) {
216
+ if (this.resolver.numericLevel(this.svc()) > EMIT[level]) return;
217
+ const merged = { ...this.bindings, ...fields ?? {} };
218
+ this.emit(level, msg, redact(merged, this.extraRedact));
219
+ }
220
+ trace(m, f) {
221
+ this.at("trace", m, f);
222
+ }
223
+ debug(m, f) {
224
+ this.at("debug", m, f);
225
+ }
226
+ info(m, f) {
227
+ this.at("info", m, f);
228
+ }
229
+ warn(m, f) {
230
+ this.at("warn", m, f);
231
+ }
232
+ error(m, f) {
233
+ this.at("error", m, f);
234
+ }
235
+ };
236
+ var ConsoleLogger = class _ConsoleLogger extends BaseLogger {
237
+ constructor(resolver, bindings, extra, sink, pretty) {
238
+ super(resolver, bindings, extra);
239
+ this.sink = sink;
240
+ this.pretty = pretty;
241
+ }
242
+ sink;
243
+ pretty;
244
+ emit(level, msg, fields) {
245
+ if (this.pretty) {
246
+ const fn = level === "error" ? this.sink.error : level === "warn" ? this.sink.warn : level === "info" ? this.sink.info : this.sink.log;
247
+ fn(`[${level}] ${msg}`, fields);
248
+ } else {
249
+ this.sink.log(JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), level, msg, ...fields }));
250
+ }
251
+ }
252
+ child(bindings) {
253
+ return new _ConsoleLogger(
254
+ this.resolver,
255
+ { ...this.bindings, ...bindings },
256
+ this.extraRedact,
257
+ this.sink,
258
+ this.pretty
259
+ );
260
+ }
261
+ };
262
+ var NoopLogger = class {
263
+ level = "silent";
264
+ isLevelEnabled() {
265
+ return false;
266
+ }
267
+ trace() {
268
+ }
269
+ debug() {
270
+ }
271
+ info() {
272
+ }
273
+ warn() {
274
+ }
275
+ error() {
276
+ }
277
+ child() {
278
+ return this;
279
+ }
280
+ };
281
+ function createConsoleLogger(resolver, bindings = {}, opts = {}) {
282
+ const pretty = opts.pretty ?? readEnv("NODE_ENV") !== "production";
283
+ const sink = opts.sink ?? {
284
+ log: console.log,
285
+ info: console.info,
286
+ warn: console.warn,
287
+ error: console.error
288
+ };
289
+ return new ConsoleLogger(resolver, bindings, opts.redact ?? [], sink, pretty);
290
+ }
291
+ function createNoopLogger() {
292
+ return new NoopLogger();
293
+ }
294
+
295
+ // src/core/auth.ts
296
+ var auth = {
297
+ /** Service/custom credential set (default `backend`). */
298
+ service: (credentials) => credentials === void 0 ? { kind: "service" } : { kind: "service", credentials },
299
+ /** Cached anonymous storefront token. */
300
+ anonymous: () => ({ kind: "anonymous" }),
301
+ /** Caller-owned customer bearer token. */
302
+ customer: (token) => ({ kind: "customer", token }),
303
+ /** Exact token, no transformation (SSO / token-exchange). */
304
+ raw: (token) => ({ kind: "raw", token })
305
+ };
306
+ async function resolveToken(ctx, provider) {
307
+ switch (ctx.kind) {
308
+ case "service":
309
+ return provider.getToken(ctx.credentials ?? "backend");
310
+ case "anonymous":
311
+ return (await provider.getAnonymousToken()).accessToken;
312
+ case "customer":
313
+ case "raw":
314
+ return ctx.token;
315
+ }
316
+ }
317
+ var DefaultTokenProvider = class {
318
+ constructor(cfg) {
319
+ this.cfg = cfg;
320
+ }
321
+ cfg;
322
+ serviceCache = /* @__PURE__ */ new Map();
323
+ serviceLocks = /* @__PURE__ */ new Map();
324
+ anon;
325
+ anonLock;
326
+ anonStore;
327
+ refreshListeners = /* @__PURE__ */ new Set();
328
+ onRefresh(listener) {
329
+ this.refreshListeners.add(listener);
330
+ return () => {
331
+ this.refreshListeners.delete(listener);
332
+ };
333
+ }
334
+ notifyRefresh(kind, success) {
335
+ for (const l of this.refreshListeners) {
336
+ try {
337
+ l({ kind, success });
338
+ } catch {
339
+ }
340
+ }
341
+ }
342
+ attachAnonymousStore(store) {
343
+ this.anonStore = store;
344
+ if (!this.anon) {
345
+ const persisted = store.read();
346
+ if (persisted) {
347
+ this.anon = {
348
+ accessToken: "",
349
+ refreshToken: persisted.refreshToken,
350
+ sessionId: persisted.sessionId,
351
+ expiresIn: 0,
352
+ expiresAt: 0
353
+ };
354
+ }
355
+ }
356
+ }
357
+ creds(set) {
358
+ if (set === "backend") {
359
+ const b = this.cfg.credentials.backend;
360
+ if (!b?.clientId || !b.secret) {
361
+ throw new EmporixAuthError(
362
+ "A 'service' AuthContext was used but credentials.backend is not configured"
363
+ );
364
+ }
365
+ return b;
366
+ }
367
+ const c = this.cfg.credentials.custom?.[set];
368
+ if (!c) throw new Error(`Unknown credential set "${set}"`);
369
+ return c;
370
+ }
371
+ fresh(e) {
372
+ if (!e) return false;
373
+ const now = Date.now();
374
+ if (now - e.obtainedAt >= this.cfg.cache.maxLifetimeSeconds * 1e3) return false;
375
+ return now < e.expiresAt;
376
+ }
377
+ async getToken(set) {
378
+ this.creds(set);
379
+ const cached = this.serviceCache.get(set);
380
+ if (this.fresh(cached)) return cached.token;
381
+ const inflight = this.serviceLocks.get(set);
382
+ if (inflight) return inflight;
383
+ const p = this.requestServiceToken(set).finally(() => this.serviceLocks.delete(set));
384
+ this.serviceLocks.set(set, p);
385
+ return p;
386
+ }
387
+ async requestServiceToken(set) {
388
+ const c = this.creds(set);
389
+ const body = new URLSearchParams({
390
+ grant_type: "client_credentials",
391
+ client_id: c.clientId,
392
+ client_secret: c.secret
393
+ });
394
+ if (c.scope) body.set("scope", c.scope);
395
+ const res = await fetch(`${this.cfg.host}/oauth/token`, {
396
+ method: "POST",
397
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
398
+ body
399
+ });
400
+ const json = await res.json().catch(() => ({}));
401
+ if (!res.ok) {
402
+ throw new EmporixAuthError(`Token request failed for "${set}"`, res.status, json);
403
+ }
404
+ const obtainedAt = Date.now();
405
+ const ttl = Number(json.expires_in ?? 3600);
406
+ this.serviceCache.set(set, {
407
+ token: json.access_token,
408
+ obtainedAt,
409
+ expiresAt: obtainedAt + (ttl - this.cfg.cache.expirationBufferSeconds) * 1e3
410
+ });
411
+ return this.serviceCache.get(set).token;
412
+ }
413
+ invalidate(set) {
414
+ this.serviceCache.delete(set);
415
+ }
416
+ anonFresh() {
417
+ return !!this.anon && Date.now() < this.anon.expiresAt;
418
+ }
419
+ async getAnonymousToken() {
420
+ if (this.anonFresh()) return this.stripExpiry(this.anon);
421
+ if (this.anonLock) return this.anonLock;
422
+ const canRefresh = !!this.anon?.refreshToken;
423
+ const obtain = canRefresh ? this.fetchAnonymous("refresh").catch(() => this.fetchAnonymous("login")) : this.fetchAnonymous("login");
424
+ const p = obtain.finally(() => {
425
+ this.anonLock = void 0;
426
+ });
427
+ this.anonLock = p;
428
+ return p;
429
+ }
430
+ /** Refreshes the anonymous session, preserving its sessionId. */
431
+ async refreshAnonymous() {
432
+ if (!this.anon) return this.getAnonymousToken();
433
+ return this.fetchAnonymous("refresh");
434
+ }
435
+ invalidateAnonymous() {
436
+ this.anon = void 0;
437
+ this.anonStore?.write(null);
438
+ }
439
+ /** Force a stale access token while keeping the refresh token + sessionId. */
440
+ expireAnonymous() {
441
+ if (this.anon) this.anon = { ...this.anon, expiresAt: 0 };
442
+ }
443
+ stripExpiry(s) {
444
+ const { expiresAt: _expiresAt, ...rest } = s;
445
+ return rest;
446
+ }
447
+ async fetchAnonymous(mode) {
448
+ const sf = this.cfg.credentials.storefront;
449
+ if (!sf?.clientId) {
450
+ throw new Error("credentials.storefront.clientId is required for anonymous tokens");
451
+ }
452
+ const url = new URL(`${this.cfg.host}/customerlogin/auth/anonymous/${mode}`);
453
+ url.searchParams.set("tenant", this.cfg.tenant);
454
+ url.searchParams.set("client_id", sf.clientId);
455
+ const c = sf.context;
456
+ if (c?.currency) url.searchParams.set("currency", c.currency);
457
+ if (c?.siteCode) url.searchParams.set("siteCode", c.siteCode);
458
+ if (c?.targetLocation) url.searchParams.set("targetLocation", c.targetLocation);
459
+ if (mode === "refresh" && this.anon) {
460
+ url.searchParams.set("refresh_token", this.anon.refreshToken);
461
+ }
462
+ try {
463
+ const res = await fetch(url, { method: "GET" });
464
+ const json = await res.json().catch(() => ({}));
465
+ if (!res.ok) {
466
+ this.notifyRefresh("anonymous", false);
467
+ throw new EmporixAuthError(`Anonymous token ${mode} failed`, res.status, json);
468
+ }
469
+ const obtainedAt = Date.now();
470
+ this.anon = {
471
+ accessToken: json.access_token,
472
+ refreshToken: json.refresh_token,
473
+ sessionId: json.sessionId,
474
+ expiresIn: json.expires_in,
475
+ expiresAt: obtainedAt + (json.expires_in - this.cfg.cache.expirationBufferSeconds) * 1e3
476
+ };
477
+ this.anonStore?.write({
478
+ refreshToken: this.anon.refreshToken,
479
+ sessionId: this.anon.sessionId
480
+ });
481
+ this.notifyRefresh("anonymous", true);
482
+ return this.stripExpiry(this.anon);
483
+ } catch (err) {
484
+ if (!(err instanceof EmporixAuthError)) {
485
+ this.notifyRefresh("anonymous", false);
486
+ }
487
+ throw err;
488
+ }
489
+ }
490
+ };
491
+
492
+ // src/core/http.ts
493
+ var requestSeq = 0;
494
+ function safeJson(text) {
495
+ try {
496
+ return JSON.parse(text);
497
+ } catch {
498
+ return text;
499
+ }
500
+ }
501
+ var HttpClient = class {
502
+ constructor(opts) {
503
+ this.opts = opts;
504
+ this.sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
505
+ }
506
+ opts;
507
+ sleep;
508
+ async request(o) {
509
+ const requestId = `req-${++requestSeq}`;
510
+ const log = this.opts.logger.child({ requestId });
511
+ const url = new URL(this.opts.host + o.path);
512
+ for (const [k, v] of Object.entries(o.query ?? {})) {
513
+ if (v !== void 0) url.searchParams.set(k, String(v));
514
+ }
515
+ const sdkManaged = o.auth.kind === "service" || o.auth.kind === "anonymous";
516
+ const maxAttempts = this.opts.retry.maxAttempts;
517
+ let reauthed = false;
518
+ for (let attempt = 1; ; attempt++) {
519
+ const token = await resolveToken(o.auth, this.opts.provider);
520
+ log.debug("http request", {
521
+ authKind: o.auth.kind,
522
+ method: o.method,
523
+ url: url.pathname,
524
+ attempt
525
+ });
526
+ const controller = new AbortController();
527
+ const timer = setTimeout(
528
+ () => controller.abort(),
529
+ o.timeoutMs ?? this.opts.timeouts.readMs
530
+ );
531
+ const isFormData = typeof FormData !== "undefined" && o.body instanceof FormData;
532
+ const init = {
533
+ method: o.method,
534
+ headers: {
535
+ ...o.headers ?? {},
536
+ Authorization: `Bearer ${token}`,
537
+ // JSON bodies: set Content-Type. FormData bodies: let `fetch`
538
+ // emit `multipart/form-data; boundary=...` itself.
539
+ ...o.body !== void 0 && !isFormData ? { "Content-Type": "application/json" } : {}
540
+ },
541
+ signal: controller.signal
542
+ };
543
+ if (o.body !== void 0) {
544
+ init.body = isFormData ? o.body : JSON.stringify(o.body);
545
+ }
546
+ let res;
547
+ try {
548
+ res = await fetch(url, init);
549
+ } finally {
550
+ clearTimeout(timer);
551
+ }
552
+ const text = await res.text();
553
+ const parsed = text ? safeJson(text) : void 0;
554
+ if (res.ok) {
555
+ log.debug("http ok", { status: res.status });
556
+ return parsed;
557
+ }
558
+ if (res.status === 401) {
559
+ if (sdkManaged && !reauthed) {
560
+ reauthed = true;
561
+ if (o.auth.kind === "service") {
562
+ this.opts.provider.invalidate?.(o.auth.credentials ?? "backend");
563
+ } else if (this.opts.provider.expireAnonymous) {
564
+ this.opts.provider.expireAnonymous();
565
+ } else {
566
+ this.opts.provider.invalidateAnonymous?.();
567
+ }
568
+ log.warn("sdk-managed 401, re-authing once", { authKind: o.auth.kind });
569
+ continue;
570
+ }
571
+ throw errorFromResponse(res.status, `${o.method} ${o.path} \u2192 401`, parsed);
572
+ }
573
+ const retryable = res.status >= 500 || res.status === 429;
574
+ if (retryable && attempt < maxAttempts) {
575
+ const retryAfter = Number(res.headers.get("Retry-After"));
576
+ const backoff = Number.isFinite(retryAfter) && retryAfter >= 0 ? retryAfter * 1e3 : Math.min(1e3 * 2 ** (attempt - 1), 8e3) + Math.random() * 100;
577
+ log.warn("retryable failure", { status: res.status, attempt, backoffMs: backoff });
578
+ await this.sleep(backoff);
579
+ continue;
580
+ }
581
+ log.error("http error (final)", { status: res.status, attempt });
582
+ throw errorFromResponse(res.status, `${o.method} ${o.path} \u2192 ${res.status}`, parsed);
583
+ }
584
+ }
585
+ };
586
+
587
+ // src/services/site.ts
588
+ var ANON = auth.anonymous();
589
+ var SiteService = class {
590
+ constructor(ctx) {
591
+ this.ctx = ctx;
592
+ }
593
+ ctx;
594
+ /** Lists active sites. */
595
+ async list(authCtx = ANON) {
596
+ return this.ctx.http.request({
597
+ method: "GET",
598
+ path: `/site/${this.ctx.tenant}/sites`,
599
+ auth: authCtx
600
+ });
601
+ }
602
+ /** Retrieves one site by code. */
603
+ async get(code, authCtx = ANON) {
604
+ return this.ctx.http.request({
605
+ method: "GET",
606
+ path: `/site/${this.ctx.tenant}/sites/${code}`,
607
+ auth: authCtx
608
+ });
609
+ }
610
+ /**
611
+ * Returns the tenant's default site (the one with `default: true`).
612
+ * Throws if no default is configured — a tenant should always have one.
613
+ */
614
+ async current(authCtx = ANON) {
615
+ const sites = await this.list(authCtx);
616
+ const def = sites.find((s) => s.default);
617
+ if (!def) {
618
+ throw new Error(
619
+ `SiteService.current: no default site for tenant "${this.ctx.tenant}"`
620
+ );
621
+ }
622
+ return def;
623
+ }
624
+ };
625
+
626
+ // src/services/session-context.ts
627
+ var ANON2 = auth.anonymous();
628
+ var SessionContextService = class {
629
+ constructor(ctx) {
630
+ this.ctx = ctx;
631
+ }
632
+ ctx;
633
+ /**
634
+ * Retrieves the current session context, or `null` when the server returns
635
+ * 404 (no session exists yet — the session-context is created server-side
636
+ * only after the user creates a cart).
637
+ */
638
+ async get(authCtx = ANON2) {
639
+ try {
640
+ return await this.ctx.http.request({
641
+ method: "GET",
642
+ path: `/session-context/${this.ctx.tenant}/me/context`,
643
+ auth: authCtx
644
+ });
645
+ } catch (err) {
646
+ if (isNotFound(err)) return null;
647
+ throw err;
648
+ }
649
+ }
650
+ /**
651
+ * Partially updates the current session context. Returns `true` when the
652
+ * PATCH was applied, `false` when there is no session context yet (the
653
+ * server returned 404 on the version-lookup GET, so there is nothing to
654
+ * update). Non-404 errors propagate.
655
+ */
656
+ async patch(input, authCtx = ANON2) {
657
+ let version = input.version;
658
+ if (version === void 0) {
659
+ const current = await this.get(authCtx);
660
+ if (current === null) return false;
661
+ version = current.metadata?.version;
662
+ if (version === void 0) {
663
+ throw new Error(
664
+ "SessionContextService.patch: no metadata.version in server response"
665
+ );
666
+ }
667
+ }
668
+ const { version: _v, ...fields } = input;
669
+ await this.ctx.http.request({
670
+ method: "PATCH",
671
+ path: `/session-context/${this.ctx.tenant}/me/context`,
672
+ body: {
673
+ ...fields,
674
+ metadata: { version }
675
+ },
676
+ auth: authCtx
677
+ });
678
+ return true;
679
+ }
680
+ };
681
+ function isNotFound(err) {
682
+ if (err && typeof err === "object") {
683
+ const status = err.status;
684
+ if (typeof status === "number" && status === 404) return true;
685
+ }
686
+ return false;
687
+ }
688
+
689
+ // src/client.ts
690
+ var SDK_VERSION = "0.0.0";
691
+ var EmporixClient = class {
692
+ customers;
693
+ products;
694
+ categories;
695
+ carts;
696
+ checkout;
697
+ payments;
698
+ prices;
699
+ media;
700
+ segments;
701
+ sites;
702
+ sessionContext;
703
+ companies;
704
+ contacts;
705
+ locations;
706
+ customerGroups;
707
+ orders;
708
+ salesOrders;
709
+ /** The validated tenant this client is bound to. */
710
+ tenant;
711
+ /**
712
+ * The token provider in use by this client. Exposed so React/Next hosts can
713
+ * call `attachAnonymousStore` to wire session persistence. Treat as
714
+ * read-only — replacing it after construction is not supported.
715
+ */
716
+ tokenProvider;
717
+ /**
718
+ * The validated config used to construct this client. Exposed so React /
719
+ * Next hosts can read static settings such as `credentials.storefront.context`
720
+ * (siteCode, currency, targetLocation) without re-plumbing them through the
721
+ * Provider tree. Treat as read-only.
722
+ */
723
+ config;
724
+ resolver;
725
+ constructor(config) {
726
+ const cfg = validateConfig(config);
727
+ this.tenant = cfg.tenant;
728
+ this.config = cfg;
729
+ let loggerObj = {};
730
+ let baseLogger;
731
+ if (cfg.logger === false) {
732
+ baseLogger = createNoopLogger();
733
+ } else if (cfg.logger && typeof cfg.logger.child === "function") {
734
+ baseLogger = cfg.logger;
735
+ } else if (cfg.logger) {
736
+ loggerObj = cfg.logger;
737
+ }
738
+ this.resolver = new LevelResolver(loggerObj);
739
+ const root = baseLogger ?? createConsoleLogger(this.resolver, {
740
+ sdk: "emporix",
741
+ sdkVersion: SDK_VERSION,
742
+ tenant: cfg.tenant
743
+ });
744
+ const tokenProvider = cfg.tokenProvider ?? new DefaultTokenProvider(cfg);
745
+ this.tokenProvider = tokenProvider;
746
+ const mk = (service) => ({
747
+ tenant: cfg.tenant,
748
+ tokenProvider,
749
+ logger: root.child({ service }),
750
+ http: new HttpClient({
751
+ host: cfg.host,
752
+ provider: tokenProvider,
753
+ logger: root.child({ service: "http" }),
754
+ retry: cfg.retry,
755
+ timeouts: cfg.timeouts
756
+ })
757
+ });
758
+ this.customers = new CustomerService(mk("customer"));
759
+ this.products = new ProductService(mk("product"));
760
+ this.categories = new CategoryService(mk("category"));
761
+ this.carts = new CartService(mk("cart"));
762
+ this.checkout = new CheckoutService(mk("checkout"));
763
+ this.payments = new PaymentGatewayService(mk("payment"));
764
+ this.prices = new PriceService(mk("price"));
765
+ this.media = new MediaService(mk("media"));
766
+ this.segments = new SegmentService(mk("segment"), {
767
+ products: this.products,
768
+ categories: this.categories
769
+ });
770
+ this.sites = new SiteService(mk("site"));
771
+ this.sessionContext = new SessionContextService(mk("session-context"));
772
+ this.companies = new CompaniesService(mk("customer-management"));
773
+ this.contacts = new ContactsService(mk("customer-management"));
774
+ this.locations = new LocationsService(mk("customer-management"));
775
+ this.customerGroups = new CustomerGroupsService(mk("iam"));
776
+ this.orders = new OrdersService(mk("orders"));
777
+ this.salesOrders = new SalesOrdersService(mk("sales-orders"));
778
+ }
779
+ /** Sets the runtime log level globally or for one service. */
780
+ setLogLevel(level, opts = {}) {
781
+ this.resolver.set(level, opts.service, opts.force ?? false);
782
+ }
783
+ /** Returns the effective log level for a service. */
784
+ getLogLevel(service) {
785
+ return this.resolver.get(service);
786
+ }
787
+ };
788
+
789
+ export { DEFAULT_HOST, DefaultTokenProvider, EmporixClient, HttpClient, LEVEL, LevelResolver, SessionContextService, SiteService, auth, createConsoleLogger, createNoopLogger, redact, resolveToken, validateConfig };
790
+ //# sourceMappingURL=index.js.map
791
+ //# sourceMappingURL=index.js.map