@viu/emporix-sdk 2.15.0 → 2.18.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 (48) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +19 -0
  3. package/dist/category.cjs +28 -0
  4. package/dist/category.cjs.map +1 -1
  5. package/dist/category.d.cts +10 -0
  6. package/dist/category.d.ts +10 -0
  7. package/dist/category.js +2 -1
  8. package/dist/{chunk-62PCCHZ2.js → chunk-36T4YPQJ.js} +7 -2
  9. package/dist/chunk-36T4YPQJ.js.map +1 -0
  10. package/dist/{chunk-GIZS4BY3.js → chunk-6XND7SYV.js} +20 -2
  11. package/dist/chunk-6XND7SYV.js.map +1 -0
  12. package/dist/{chunk-5VTBLBXJ.js → chunk-FZMP5NQZ.js} +10 -4
  13. package/dist/chunk-FZMP5NQZ.js.map +1 -0
  14. package/dist/chunk-PYLWZZC4.js +280 -0
  15. package/dist/chunk-PYLWZZC4.js.map +1 -0
  16. package/dist/chunk-S5HEIQDK.js +14 -0
  17. package/dist/chunk-S5HEIQDK.js.map +1 -0
  18. package/dist/index.cjs +68 -24
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.cts +13 -7
  21. package/dist/index.d.ts +13 -7
  22. package/dist/index.js +19 -258
  23. package/dist/index.js.map +1 -1
  24. package/dist/orders.cjs +14 -0
  25. package/dist/orders.cjs.map +1 -1
  26. package/dist/orders.d.cts +3 -0
  27. package/dist/orders.d.ts +3 -0
  28. package/dist/orders.js +2 -1
  29. package/dist/payment.cjs +24 -7
  30. package/dist/payment.cjs.map +1 -1
  31. package/dist/payment.d.cts +7 -3
  32. package/dist/payment.d.ts +7 -3
  33. package/dist/payment.js +1 -1
  34. package/dist/product.cjs +14 -2
  35. package/dist/product.cjs.map +1 -1
  36. package/dist/product.d.cts +7 -2
  37. package/dist/product.d.ts +7 -2
  38. package/dist/product.js +2 -1
  39. package/dist/query-mLQs2WFr.d.cts +28 -0
  40. package/dist/query-mLQs2WFr.d.ts +28 -0
  41. package/dist/segment.d.cts +1 -0
  42. package/dist/segment.d.ts +1 -0
  43. package/package.json +1 -1
  44. package/dist/chunk-5VTBLBXJ.js.map +0 -1
  45. package/dist/chunk-62PCCHZ2.js.map +0 -1
  46. package/dist/chunk-GIZS4BY3.js.map +0 -1
  47. package/dist/chunk-XEJPH6UN.js +0 -31
  48. package/dist/chunk-XEJPH6UN.js.map +0 -1
@@ -0,0 +1,280 @@
1
+ import { requireCustomer } from './chunk-3BDDYX3V.js';
2
+ import { EmporixAuthError, EmporixTimeoutError, EmporixNetworkError } from './chunk-UWGGF4MC.js';
3
+
4
+ // src/core/auth.ts
5
+ var auth = {
6
+ /** Service/custom credential set (default `backend`). */
7
+ service: (credentials) => credentials === void 0 ? { kind: "service" } : { kind: "service", credentials },
8
+ /** Cached anonymous storefront token. */
9
+ anonymous: () => ({ kind: "anonymous" }),
10
+ /** Caller-owned customer bearer token. */
11
+ customer: (token) => ({ kind: "customer", token }),
12
+ /** Exact token, no transformation (SSO / token-exchange). */
13
+ raw: (token) => ({ kind: "raw", token })
14
+ };
15
+ async function resolveToken(ctx, provider) {
16
+ switch (ctx.kind) {
17
+ case "service":
18
+ return provider.getToken(ctx.credentials ?? "backend");
19
+ case "anonymous":
20
+ return (await provider.getAnonymousToken()).accessToken;
21
+ case "customer":
22
+ case "raw":
23
+ return ctx.token;
24
+ }
25
+ }
26
+ var CustomerRefreshRegistry = class {
27
+ refresher = null;
28
+ inflight = null;
29
+ set(refresher) {
30
+ this.refresher = refresher;
31
+ }
32
+ get enabled() {
33
+ return this.refresher !== null;
34
+ }
35
+ /** Concurrent callers share one in-flight refresh. */
36
+ refresh(expiredToken) {
37
+ if (!this.refresher) return Promise.resolve(null);
38
+ if (this.inflight) return this.inflight;
39
+ const p = Promise.resolve(this.refresher.refresh(expiredToken)).finally(() => {
40
+ this.inflight = null;
41
+ });
42
+ this.inflight = p;
43
+ return p;
44
+ }
45
+ };
46
+ var DefaultTokenProvider = class {
47
+ constructor(cfg) {
48
+ this.cfg = cfg;
49
+ }
50
+ cfg;
51
+ serviceCache = /* @__PURE__ */ new Map();
52
+ serviceLocks = /* @__PURE__ */ new Map();
53
+ anon;
54
+ anonLock;
55
+ anonStore;
56
+ contextOverride;
57
+ refreshListeners = /* @__PURE__ */ new Set();
58
+ onRefresh(listener) {
59
+ this.refreshListeners.add(listener);
60
+ return () => {
61
+ this.refreshListeners.delete(listener);
62
+ };
63
+ }
64
+ notifyRefresh(kind, success) {
65
+ for (const l of this.refreshListeners) {
66
+ try {
67
+ l({ kind, success });
68
+ } catch {
69
+ }
70
+ }
71
+ }
72
+ attachAnonymousStore(store) {
73
+ this.anonStore = store;
74
+ if (!this.anon) {
75
+ const persisted = store.read();
76
+ if (persisted) {
77
+ this.anon = {
78
+ accessToken: "",
79
+ refreshToken: persisted.refreshToken,
80
+ sessionId: persisted.sessionId,
81
+ expiresIn: 0,
82
+ expiresAt: 0
83
+ };
84
+ }
85
+ }
86
+ }
87
+ creds(set) {
88
+ if (set === "backend") {
89
+ const b = this.cfg.credentials.backend;
90
+ if (!b?.clientId || !b.secret) {
91
+ throw new EmporixAuthError(
92
+ "A 'service' AuthContext was used but credentials.backend is not configured"
93
+ );
94
+ }
95
+ return b;
96
+ }
97
+ const c = this.cfg.credentials.custom?.[set];
98
+ if (!c) throw new Error(`Unknown credential set "${set}"`);
99
+ return c;
100
+ }
101
+ fresh(e) {
102
+ if (!e) return false;
103
+ const now = Date.now();
104
+ if (now - e.obtainedAt >= this.cfg.cache.maxLifetimeSeconds * 1e3) return false;
105
+ return now < e.expiresAt;
106
+ }
107
+ async getToken(set) {
108
+ this.creds(set);
109
+ const cached = this.serviceCache.get(set);
110
+ if (this.fresh(cached)) return cached.token;
111
+ const inflight = this.serviceLocks.get(set);
112
+ if (inflight) return inflight;
113
+ const p = this.requestServiceToken(set).finally(() => this.serviceLocks.delete(set));
114
+ this.serviceLocks.set(set, p);
115
+ return p;
116
+ }
117
+ /**
118
+ * fetch with the configured read timeout. Token endpoints sit in front of
119
+ * single-flight locks — one hung call would otherwise block every request
120
+ * on this credential set forever.
121
+ */
122
+ async boundedFetch(url, init = {}) {
123
+ const ms = this.cfg.timeouts?.readMs ?? 6e4;
124
+ const controller = new AbortController();
125
+ const timer = setTimeout(() => controller.abort(), ms);
126
+ try {
127
+ return await fetch(url, { ...init, signal: controller.signal });
128
+ } catch (err) {
129
+ if (err.name === "AbortError") {
130
+ throw new EmporixTimeoutError(`token request timed out after ${ms}ms`);
131
+ }
132
+ throw new EmporixNetworkError(`token request network failure: ${err.message}`);
133
+ } finally {
134
+ clearTimeout(timer);
135
+ }
136
+ }
137
+ async requestServiceToken(set) {
138
+ const c = this.creds(set);
139
+ const body = new URLSearchParams({
140
+ grant_type: "client_credentials",
141
+ client_id: c.clientId,
142
+ client_secret: c.secret
143
+ });
144
+ if (c.scope) body.set("scope", c.scope);
145
+ const res = await this.boundedFetch(`${this.cfg.host}/oauth/token`, {
146
+ method: "POST",
147
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
148
+ body
149
+ });
150
+ const json = await res.json().catch(() => ({}));
151
+ if (!res.ok) {
152
+ throw new EmporixAuthError(`Token request failed for "${set}"`, res.status, json);
153
+ }
154
+ const obtainedAt = Date.now();
155
+ const ttl = Number(json.expires_in ?? 3600);
156
+ this.serviceCache.set(set, {
157
+ token: json.access_token,
158
+ obtainedAt,
159
+ expiresAt: obtainedAt + (ttl - this.cfg.cache.expirationBufferSeconds) * 1e3
160
+ });
161
+ return this.serviceCache.get(set).token;
162
+ }
163
+ invalidate(set) {
164
+ this.serviceCache.delete(set);
165
+ }
166
+ anonFresh() {
167
+ return !!this.anon && Date.now() < this.anon.expiresAt;
168
+ }
169
+ async getAnonymousToken() {
170
+ if (this.anonFresh()) return this.stripExpiry(this.anon);
171
+ if (this.anonLock) return this.anonLock;
172
+ const canRefresh = !!this.anon?.refreshToken;
173
+ const obtain = canRefresh ? this.fetchAnonymous("refresh").catch(() => this.fetchAnonymous("login")) : this.fetchAnonymous("login");
174
+ const p = obtain.finally(() => {
175
+ this.anonLock = void 0;
176
+ });
177
+ this.anonLock = p;
178
+ return p;
179
+ }
180
+ /** Refreshes the anonymous session, preserving its sessionId. */
181
+ async refreshAnonymous() {
182
+ if (!this.anon) return this.getAnonymousToken();
183
+ return this.fetchAnonymous("refresh");
184
+ }
185
+ invalidateAnonymous() {
186
+ this.anon = void 0;
187
+ this.anonStore?.write(null);
188
+ }
189
+ setAnonymousContext(ctx) {
190
+ const base = this.contextOverride ?? this.cfg.credentials.storefront?.context ?? {};
191
+ this.contextOverride = { ...base, ...ctx };
192
+ this.invalidateAnonymous();
193
+ }
194
+ /** Force a stale access token while keeping the refresh token + sessionId. */
195
+ expireAnonymous() {
196
+ if (this.anon) this.anon = { ...this.anon, expiresAt: 0 };
197
+ }
198
+ stripExpiry(s) {
199
+ const { expiresAt: _expiresAt, ...rest } = s;
200
+ return rest;
201
+ }
202
+ async fetchAnonymous(mode) {
203
+ const sf = this.cfg.credentials.storefront;
204
+ if (!sf?.clientId) {
205
+ throw new Error("credentials.storefront.clientId is required for anonymous tokens");
206
+ }
207
+ const url = new URL(`${this.cfg.host}/customerlogin/auth/anonymous/${mode}`);
208
+ url.searchParams.set("tenant", this.cfg.tenant);
209
+ url.searchParams.set("client_id", sf.clientId);
210
+ const c = this.contextOverride ?? sf.context;
211
+ if (c?.currency) url.searchParams.set("currency", c.currency);
212
+ if (c?.siteCode) url.searchParams.set("siteCode", c.siteCode);
213
+ if (c?.targetLocation) url.searchParams.set("targetLocation", c.targetLocation);
214
+ if (mode === "refresh" && this.anon) {
215
+ url.searchParams.set("refresh_token", this.anon.refreshToken);
216
+ }
217
+ try {
218
+ const res = await this.boundedFetch(url, { method: "GET" });
219
+ const json = await res.json().catch(() => ({}));
220
+ if (!res.ok) {
221
+ this.notifyRefresh("anonymous", false);
222
+ throw new EmporixAuthError(`Anonymous token ${mode} failed`, res.status, json);
223
+ }
224
+ const obtainedAt = Date.now();
225
+ this.anon = {
226
+ accessToken: json.access_token,
227
+ refreshToken: json.refresh_token,
228
+ sessionId: json.sessionId,
229
+ expiresIn: json.expires_in,
230
+ expiresAt: obtainedAt + (json.expires_in - this.cfg.cache.expirationBufferSeconds) * 1e3
231
+ };
232
+ this.anonStore?.write({
233
+ refreshToken: this.anon.refreshToken,
234
+ sessionId: this.anon.sessionId
235
+ });
236
+ this.notifyRefresh("anonymous", true);
237
+ return this.stripExpiry(this.anon);
238
+ } catch (err) {
239
+ if (!(err instanceof EmporixAuthError)) {
240
+ this.notifyRefresh("anonymous", false);
241
+ }
242
+ throw err;
243
+ }
244
+ }
245
+ };
246
+
247
+ // src/services/payment.ts
248
+ var ANON = auth.anonymous();
249
+ var PaymentGatewayService = class {
250
+ constructor(ctx) {
251
+ this.ctx = ctx;
252
+ }
253
+ ctx;
254
+ static channel = "payment";
255
+ /**
256
+ * Lists configured frontend payment modes. The endpoint requires a bearer
257
+ * token but no customer scope ("No scope required"), so it defaults to an
258
+ * anonymous context and works for guests and logged-in customers alike.
259
+ */
260
+ async listPaymentModes(authCtx = ANON) {
261
+ return this.ctx.http.request({
262
+ method: "GET",
263
+ path: `/payment-gateway/${this.ctx.tenant}/paymentmodes/frontend`,
264
+ auth: authCtx
265
+ });
266
+ }
267
+ /** Authorizes a post-checkout (deferred) payment for an existing order. */
268
+ async authorize(input, authCtx) {
269
+ return this.ctx.http.request({
270
+ method: "POST",
271
+ path: `/payment-gateway/${this.ctx.tenant}/payment/frontend/authorize`,
272
+ auth: requireCustomer(authCtx),
273
+ body: input
274
+ });
275
+ }
276
+ };
277
+
278
+ export { CustomerRefreshRegistry, DefaultTokenProvider, PaymentGatewayService, auth, resolveToken };
279
+ //# sourceMappingURL=chunk-PYLWZZC4.js.map
280
+ //# sourceMappingURL=chunk-PYLWZZC4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/auth.ts","../src/services/payment.ts"],"names":[],"mappings":"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","file":"chunk-PYLWZZC4.js","sourcesContent":["import type { ResolvedConfig, ServiceCredentials } from \"./config\";\nimport { EmporixAuthError, EmporixTimeoutError, EmporixNetworkError } from \"./errors\";\n\n/** Which token a call should use. */\nexport type AuthKind = \"service\" | \"customer\" | \"anonymous\" | \"raw\";\n\n/** Caller-supplied, per-call auth selector. Never stored on the client. */\nexport type AuthContext =\n | { kind: \"service\"; credentials?: string }\n | { kind: \"anonymous\" }\n | { kind: \"customer\"; token: string }\n | { kind: \"raw\"; token: string };\n\n/**\n * Supplies a fresh customer token when a `customer`-kind request 401s. The host\n * (e.g. EmporixProvider) implements this; the SDK never refreshes the\n * caller-owned customer token unless a refresher is registered.\n */\nexport interface CustomerTokenRefresher {\n /**\n * Called on a `customer`-kind 401. Receives the token that just failed;\n * returns a fresh customer token to retry with, or `null` to give up (the\n * 401 then propagates as EmporixAuthError).\n */\n refresh(expiredToken: string): Promise<string | null>;\n}\n\n/** An obtained anonymous storefront session. */\nexport interface AnonymousSession {\n accessToken: string;\n refreshToken: string;\n sessionId: string;\n expiresIn: number;\n}\n\n/**\n * Persistence callback for anonymous sessions. `read` is called once on the\n * first need for an anonymous token to bootstrap a possibly-existing session;\n * `write` is called after every successful login or refresh. `write(null)`\n * means the SDK is invalidating the stored session.\n */\nexport interface AnonymousSessionStore {\n read(): { refreshToken: string; sessionId: string } | null;\n write(session: { refreshToken: string; sessionId: string } | null): void;\n}\n\n/** Supplies SDK-managed tokens (service/custom + anonymous). May be user-injected. */\nexport interface TokenProvider {\n /** Service/custom client-credentials token for the named credential set. */\n getToken(credentialSet: string): Promise<string>;\n /** Cached anonymous storefront session (preserves sessionId across refreshes). */\n getAnonymousToken(): Promise<AnonymousSession>;\n /** Refresh the anonymous session, preserving sessionId. */\n refreshAnonymous?(): Promise<AnonymousSession>;\n /** Invalidate a cached SDK-managed token so the next call re-auths. */\n invalidate?(credentialSet: string): void;\n /** Invalidate the cached anonymous session entirely (next call re-logs in). */\n invalidateAnonymous?(): void;\n /**\n * Mark the anonymous access token stale but keep the refresh token, so the\n * next {@link getAnonymousToken} refreshes (preserving sessionId) rather\n * than starting a brand-new session.\n */\n expireAnonymous?(): void;\n /**\n * Install a persistence adapter for the anonymous session. The host (e.g.\n * `EmporixProvider`) calls this at construction so the SDK can bootstrap\n * an existing session and persist refreshes. Idempotent: a later call\n * replaces the previous adapter.\n */\n attachAnonymousStore?(store: AnonymousSessionStore): void;\n /**\n * Override the storefront context (currency/site/country) used for the next\n * anonymous login, then invalidate the current anonymous session so it\n * re-mints with the new context. No-op for providers without anon support.\n */\n setAnonymousContext?(ctx: {\n currency?: string;\n siteCode?: string;\n targetLocation?: string;\n }): void;\n /**\n * Subscribe to token-refresh events. Optional — implementations may no-op.\n * Returns an unsubscribe function. `DefaultTokenProvider` emits for the\n * anonymous-login / anonymous-refresh paths.\n */\n onRefresh?(\n listener: (event: { kind: \"anonymous\" | \"customer\"; success: boolean }) => void,\n ): () => void;\n}\n\n/** Tiny constructors for {@link AuthContext}. */\nexport const auth = {\n /** Service/custom credential set (default `backend`). */\n service: (credentials?: string): AuthContext =>\n credentials === undefined ? { kind: \"service\" } : { kind: \"service\", credentials },\n /** Cached anonymous storefront token. */\n anonymous: (): AuthContext => ({ kind: \"anonymous\" }),\n /** Caller-owned customer bearer token. */\n customer: (token: string): AuthContext => ({ kind: \"customer\", token }),\n /** Exact token, no transformation (SSO / token-exchange). */\n raw: (token: string): AuthContext => ({ kind: \"raw\", token }),\n};\n\n/** Resolves an {@link AuthContext} to a concrete bearer token. */\nexport async function resolveToken(ctx: AuthContext, provider: TokenProvider): Promise<string> {\n switch (ctx.kind) {\n case \"service\":\n return provider.getToken(ctx.credentials ?? \"backend\");\n case \"anonymous\":\n return (await provider.getAnonymousToken()).accessToken;\n case \"customer\":\n case \"raw\":\n return ctx.token;\n }\n}\n\n/**\n * Late-bindable, single-flight holder for an optional\n * {@link CustomerTokenRefresher}. Single-flight is required because Emporix\n * rotates the refresh token on each refresh — concurrent refreshes would\n * invalidate each other. Off (returns `null`) until a refresher is set.\n */\nexport class CustomerRefreshRegistry {\n private refresher: CustomerTokenRefresher | null = null;\n private inflight: Promise<string | null> | null = null;\n\n set(refresher: CustomerTokenRefresher | null): void {\n this.refresher = refresher;\n }\n\n get enabled(): boolean {\n return this.refresher !== null;\n }\n\n /** Concurrent callers share one in-flight refresh. */\n refresh(expiredToken: string): Promise<string | null> {\n if (!this.refresher) return Promise.resolve(null);\n if (this.inflight) return this.inflight;\n const p = Promise.resolve(this.refresher.refresh(expiredToken)).finally(() => {\n this.inflight = null;\n });\n this.inflight = p;\n return p;\n }\n}\n\ninterface CacheEntry {\n token: string;\n expiresAt: number;\n obtainedAt: number;\n}\n\n/** SDK-owned token provider: client-credentials service tokens + anonymous session. */\nexport class DefaultTokenProvider implements TokenProvider {\n private readonly serviceCache = new Map<string, CacheEntry>();\n private readonly serviceLocks = new Map<string, Promise<string>>();\n private anon: (AnonymousSession & { expiresAt: number }) | undefined;\n private anonLock: Promise<AnonymousSession> | undefined;\n private anonStore?: AnonymousSessionStore;\n private contextOverride:\n | { currency?: string; siteCode?: string; targetLocation?: string }\n | undefined;\n private readonly refreshListeners = new Set<\n (event: { kind: \"anonymous\" | \"customer\"; success: boolean }) => void\n >();\n\n constructor(private readonly cfg: ResolvedConfig) {}\n\n onRefresh(\n listener: (event: { kind: \"anonymous\" | \"customer\"; success: boolean }) => void,\n ): () => void {\n this.refreshListeners.add(listener);\n return () => {\n this.refreshListeners.delete(listener);\n };\n }\n\n private notifyRefresh(kind: \"anonymous\" | \"customer\", success: boolean): void {\n for (const l of this.refreshListeners) {\n try {\n l({ kind, success });\n } catch {\n // Never let a telemetry listener break the auth path.\n }\n }\n }\n\n attachAnonymousStore(store: AnonymousSessionStore): void {\n this.anonStore = store;\n // Bootstrap `this.anon` from the store if we don't have it yet. The seeded\n // session has expiresAt = 0 so the next getAnonymousToken triggers a refresh\n // (which preserves sessionId) instead of a fresh login.\n if (!this.anon) {\n const persisted = store.read();\n if (persisted) {\n this.anon = {\n accessToken: \"\",\n refreshToken: persisted.refreshToken,\n sessionId: persisted.sessionId,\n expiresIn: 0,\n expiresAt: 0,\n };\n }\n }\n }\n\n private creds(set: string): ServiceCredentials {\n if (set === \"backend\") {\n const b = this.cfg.credentials.backend;\n if (!b?.clientId || !b.secret) {\n throw new EmporixAuthError(\n \"A 'service' AuthContext was used but credentials.backend is not configured\",\n );\n }\n return b;\n }\n const c = this.cfg.credentials.custom?.[set];\n if (!c) throw new Error(`Unknown credential set \"${set}\"`);\n return c;\n }\n\n private fresh(e: CacheEntry | undefined): boolean {\n if (!e) return false;\n const now = Date.now();\n if (now - e.obtainedAt >= this.cfg.cache.maxLifetimeSeconds * 1000) return false;\n return now < e.expiresAt;\n }\n\n async getToken(set: string): Promise<string> {\n // Surface an unknown credential set before entering the cache/lock path.\n this.creds(set);\n const cached = this.serviceCache.get(set);\n if (this.fresh(cached)) return cached!.token;\n const inflight = this.serviceLocks.get(set);\n if (inflight) return inflight;\n const p = this.requestServiceToken(set).finally(() => this.serviceLocks.delete(set));\n this.serviceLocks.set(set, p);\n return p;\n }\n\n /**\n * fetch with the configured read timeout. Token endpoints sit in front of\n * single-flight locks — one hung call would otherwise block every request\n * on this credential set forever.\n */\n private async boundedFetch(url: string | URL, init: RequestInit = {}): Promise<Response> {\n const ms = this.cfg.timeouts?.readMs ?? 60_000;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), ms);\n try {\n return await fetch(url, { ...init, signal: controller.signal });\n } catch (err) {\n if ((err as Error).name === \"AbortError\") {\n throw new EmporixTimeoutError(`token request timed out after ${ms}ms`);\n }\n throw new EmporixNetworkError(`token request network failure: ${(err as Error).message}`);\n } finally {\n clearTimeout(timer);\n }\n }\n\n private async requestServiceToken(set: string): Promise<string> {\n const c = this.creds(set);\n const body = new URLSearchParams({\n grant_type: \"client_credentials\",\n client_id: c.clientId,\n client_secret: c.secret,\n });\n if (c.scope) body.set(\"scope\", c.scope);\n const res = await this.boundedFetch(`${this.cfg.host}/oauth/token`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body,\n });\n const json = (await res.json().catch(() => ({}))) as Record<string, unknown>;\n if (!res.ok) {\n throw new EmporixAuthError(`Token request failed for \"${set}\"`, res.status, json);\n }\n const obtainedAt = Date.now();\n const ttl = Number((json.expires_in as number | undefined) ?? 3600);\n this.serviceCache.set(set, {\n token: json.access_token as string,\n obtainedAt,\n expiresAt: obtainedAt + (ttl - this.cfg.cache.expirationBufferSeconds) * 1000,\n });\n return this.serviceCache.get(set)!.token;\n }\n\n invalidate(set: string): void {\n this.serviceCache.delete(set);\n }\n\n private anonFresh(): boolean {\n return !!this.anon && Date.now() < this.anon.expiresAt;\n }\n\n async getAnonymousToken(): Promise<AnonymousSession> {\n if (this.anonFresh()) return this.stripExpiry(this.anon!);\n if (this.anonLock) return this.anonLock;\n // Token expired but a refresh token survives → refresh to keep the same\n // sessionId; only fall back to a brand-new login if the refresh fails.\n const canRefresh = !!this.anon?.refreshToken;\n const obtain = canRefresh\n ? this.fetchAnonymous(\"refresh\").catch(() => this.fetchAnonymous(\"login\"))\n : this.fetchAnonymous(\"login\");\n const p = obtain.finally(() => {\n this.anonLock = undefined;\n });\n this.anonLock = p;\n return p;\n }\n\n /** Refreshes the anonymous session, preserving its sessionId. */\n async refreshAnonymous(): Promise<AnonymousSession> {\n if (!this.anon) return this.getAnonymousToken();\n return this.fetchAnonymous(\"refresh\");\n }\n\n invalidateAnonymous(): void {\n this.anon = undefined;\n this.anonStore?.write(null);\n }\n\n setAnonymousContext(ctx: {\n currency?: string;\n siteCode?: string;\n targetLocation?: string;\n }): void {\n const base = this.contextOverride ?? this.cfg.credentials.storefront?.context ?? {};\n this.contextOverride = { ...base, ...ctx };\n this.invalidateAnonymous();\n }\n\n /** Force a stale access token while keeping the refresh token + sessionId. */\n expireAnonymous(): void {\n if (this.anon) this.anon = { ...this.anon, expiresAt: 0 };\n }\n\n private stripExpiry(s: AnonymousSession & { expiresAt: number }): AnonymousSession {\n const { expiresAt: _expiresAt, ...rest } = s;\n return rest;\n }\n\n private async fetchAnonymous(mode: \"login\" | \"refresh\"): Promise<AnonymousSession> {\n const sf = this.cfg.credentials.storefront;\n if (!sf?.clientId) {\n throw new Error(\"credentials.storefront.clientId is required for anonymous tokens\");\n }\n const url = new URL(`${this.cfg.host}/customerlogin/auth/anonymous/${mode}`);\n url.searchParams.set(\"tenant\", this.cfg.tenant);\n url.searchParams.set(\"client_id\", sf.clientId);\n // Context is the runtime override (set via setAnonymousContext) if present,\n // else the config-fixed context.\n const c = this.contextOverride ?? sf.context;\n if (c?.currency) url.searchParams.set(\"currency\", c.currency);\n if (c?.siteCode) url.searchParams.set(\"siteCode\", c.siteCode);\n if (c?.targetLocation) url.searchParams.set(\"targetLocation\", c.targetLocation);\n if (mode === \"refresh\" && this.anon) {\n url.searchParams.set(\"refresh_token\", this.anon.refreshToken);\n }\n try {\n const res = await this.boundedFetch(url, { method: \"GET\" });\n const json = (await res.json().catch(() => ({}))) as Record<string, unknown>;\n if (!res.ok) {\n this.notifyRefresh(\"anonymous\", false);\n throw new EmporixAuthError(`Anonymous token ${mode} failed`, res.status, json);\n }\n const obtainedAt = Date.now();\n this.anon = {\n accessToken: json.access_token as string,\n refreshToken: json.refresh_token as string,\n sessionId: json.sessionId as string,\n expiresIn: json.expires_in as number,\n expiresAt:\n obtainedAt +\n ((json.expires_in as number) - this.cfg.cache.expirationBufferSeconds) * 1000,\n };\n this.anonStore?.write({\n refreshToken: this.anon.refreshToken,\n sessionId: this.anon.sessionId,\n });\n this.notifyRefresh(\"anonymous\", true);\n return this.stripExpiry(this.anon);\n } catch (err) {\n // Re-throw EmporixAuthError as-is; for non-Emporix errors (network), also notify.\n if (!(err instanceof EmporixAuthError)) {\n this.notifyRefresh(\"anonymous\", false);\n }\n throw err;\n }\n }\n}\n// Customer-token refreshes happen via client.customers.refresh() and don't\n// route through this TokenProvider — only the anonymous flow notifies.\n// React-side useCustomerSession.refresh emits its own telemetry event in a\n// follow-up if needed.\n","import type { ClientContext } from \"../core/context\";\nimport { auth, type AuthContext } from \"../core/auth\";\nimport { requireCustomer } from \"../core/require-customer\";\nimport type {\n PaymentModeFrontendResponse,\n AuthorizePaymentRequest,\n} from \"../generated/payment\";\n\nconst ANON: AuthContext = auth.anonymous();\n\n/** A frontend payment mode (generated). */\nexport type PaymentMode = PaymentModeFrontendResponse;\n\n/** Post-checkout authorize request (generated; caller sends the exact wire shape). */\nexport type AuthorizePaymentInput = AuthorizePaymentRequest;\n\n/** Post-checkout authorize result. */\nexport interface AuthorizePaymentResult {\n successful: boolean;\n paymentTransactionId?: string;\n authorizationToken?: string;\n requiresExternalPayment?: boolean;\n externalPaymentRedirectURL?: string;\n externalPaymentHttpMethod?: string;\n [k: string]: unknown;\n}\n\n/** Payment-Gateway: list frontend payment modes, authorize deferred payments. */\nexport class PaymentGatewayService {\n static readonly channel = \"payment\" as const;\n constructor(private readonly ctx: ClientContext) {}\n\n /**\n * Lists configured frontend payment modes. The endpoint requires a bearer\n * token but no customer scope (\"No scope required\"), so it defaults to an\n * anonymous context and works for guests and logged-in customers alike.\n */\n async listPaymentModes(authCtx: AuthContext = ANON): Promise<PaymentMode[]> {\n return this.ctx.http.request<PaymentMode[]>({\n method: \"GET\",\n path: `/payment-gateway/${this.ctx.tenant}/paymentmodes/frontend`,\n auth: authCtx,\n });\n }\n\n /** Authorizes a post-checkout (deferred) payment for an existing order. */\n async authorize(\n input: AuthorizePaymentInput,\n authCtx?: AuthContext,\n ): Promise<AuthorizePaymentResult> {\n return this.ctx.http.request<AuthorizePaymentResult>({\n method: \"POST\",\n path: `/payment-gateway/${this.ctx.tenant}/payment/frontend/authorize`,\n auth: requireCustomer(authCtx),\n body: input,\n });\n }\n}\n"]}
@@ -0,0 +1,14 @@
1
+ // src/core/query.ts
2
+ function resolveQuery(q, cap) {
3
+ if (typeof q === "string") return q;
4
+ if (q.usesCompound === true && !cap.compoundLogicalQuery) {
5
+ throw new Error(
6
+ "This filter uses or()/compoundLogicalQuery, which the target service does not support. Combine conditions with and() (space-separated AND) instead."
7
+ );
8
+ }
9
+ return q.toString();
10
+ }
11
+
12
+ export { resolveQuery };
13
+ //# sourceMappingURL=chunk-S5HEIQDK.js.map
14
+ //# sourceMappingURL=chunk-S5HEIQDK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/query.ts"],"names":[],"mappings":";AA4BO,SAAS,YAAA,CAAa,GAAa,GAAA,EAA8B;AACtE,EAAA,IAAI,OAAO,CAAA,KAAM,QAAA,EAAU,OAAO,CAAA;AAClC,EAAA,IAAI,CAAA,CAAE,YAAA,KAAiB,IAAA,IAAQ,CAAC,IAAI,oBAAA,EAAsB;AACxD,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KAEF;AAAA,EACF;AACA,EAAA,OAAO,EAAE,QAAA,EAAS;AACpB","file":"chunk-S5HEIQDK.js","sourcesContent":["/**\n * A built query filter — structurally matched, NOT imported, so the SDK stays\n * decoupled from `@viu/emporix-mixins` (which lists the SDK as an optional\n * peer; a real import would be circular). `@viu/emporix-mixins`' `MixinFilter<E>`\n * satisfies this shape.\n */\nexport interface BuiltQuery<E extends string = string> {\n toString(): string;\n readonly usesCompound?: boolean;\n /** Phantom entity tag — gates which service a filter may be passed to. */\n readonly __entity?: E;\n}\n\n/** A `q` value a service method accepts: a raw DSL string or a built filter for entity `E`. */\nexport type QueryFor<E extends string = string> = string | BuiltQuery<E>;\n\n/** Per-service capability flags consulted by {@link resolveQuery}. */\nexport interface QueryCapability {\n /** Whether the target service supports the `compoundLogicalQuery` (OR) operator. */\n compoundLogicalQuery: boolean;\n}\n\n/**\n * Normalizes a `string | BuiltQuery` into a `q` string and enforces the\n * `compoundLogicalQuery` capability gate: an `or()`-built filter passed to a\n * service that does not support it throws rather than silently producing a\n * query the backend cannot execute.\n */\nexport function resolveQuery(q: QueryFor, cap: QueryCapability): string {\n if (typeof q === \"string\") return q;\n if (q.usesCompound === true && !cap.compoundLogicalQuery) {\n throw new Error(\n \"This filter uses or()/compoundLogicalQuery, which the target service does not support. \" +\n \"Combine conditions with and() (space-separated AND) instead.\",\n );\n }\n return q.toString();\n}\n"]}
package/dist/index.cjs CHANGED
@@ -783,7 +783,7 @@ var HttpClient = class {
783
783
  };
784
784
 
785
785
  // package.json
786
- var version = "2.15.0";
786
+ var version = "2.18.0";
787
787
 
788
788
  // src/version.ts
789
789
  var SDK_VERSION = version;
@@ -1071,6 +1071,17 @@ async function* iterateAll(fetchPage, start = 1) {
1071
1071
  }
1072
1072
  }
1073
1073
 
1074
+ // src/core/query.ts
1075
+ function resolveQuery(q, cap) {
1076
+ if (typeof q === "string") return q;
1077
+ if (q.usesCompound === true && !cap.compoundLogicalQuery) {
1078
+ throw new Error(
1079
+ "This filter uses or()/compoundLogicalQuery, which the target service does not support. Combine conditions with and() (space-separated AND) instead."
1080
+ );
1081
+ }
1082
+ return q.toString();
1083
+ }
1084
+
1074
1085
  // src/services/product.ts
1075
1086
  var ANON = { kind: "anonymous" };
1076
1087
  var ProductService = class {
@@ -1116,14 +1127,19 @@ var ProductService = class {
1116
1127
  const pageSize = params.pageSize ?? 50;
1117
1128
  return iterateAll((pageNumber) => this.list({ pageNumber, pageSize }, auth2));
1118
1129
  }
1119
- /** Searches products by free-text query. */
1130
+ /**
1131
+ * Searches products by a `q` filter — a raw Emporix DSL string or a built
1132
+ * filter (e.g. `@viu/emporix-mixins`' `mixinQuery(...)`). Product supports
1133
+ * `compoundLogicalQuery`, so `or()` filters are allowed.
1134
+ */
1120
1135
  async search(query, params = {}, auth2 = ANON) {
1136
+ const q = resolveQuery(query, { compoundLogicalQuery: true });
1121
1137
  const pageNumber = params.pageNumber ?? 1;
1122
1138
  const pageSize = params.pageSize ?? 50;
1123
1139
  const items = await this.ctx.http.request({
1124
1140
  method: "GET",
1125
1141
  path: `/product/${this.ctx.tenant}/products`,
1126
- query: { q: query, pageNumber, pageSize },
1142
+ query: { q, pageNumber, pageSize },
1127
1143
  auth: auth2
1128
1144
  });
1129
1145
  return { items, pageNumber, pageSize, hasNextPage: items.length === pageSize };
@@ -1261,6 +1277,23 @@ var CategoryService = class {
1261
1277
  });
1262
1278
  return { items, pageNumber, pageSize, hasNextPage: items.length === pageSize };
1263
1279
  }
1280
+ /**
1281
+ * Searches categories by a `q` filter — a raw Emporix DSL string or a built
1282
+ * filter (e.g. `@viu/emporix-mixins`' `mixinQuery(...)`). Category does not
1283
+ * support `compoundLogicalQuery`, so `or()` filters are rejected.
1284
+ */
1285
+ async search(query, params = {}, auth2 = ANON2) {
1286
+ const q = resolveQuery(query, { compoundLogicalQuery: false });
1287
+ const pageNumber = params.pageNumber ?? 1;
1288
+ const pageSize = params.pageSize ?? 50;
1289
+ const items = await this.ctx.http.request({
1290
+ method: "GET",
1291
+ path: `/category/${this.ctx.tenant}/categories`,
1292
+ query: { q, pageNumber, pageSize },
1293
+ auth: auth2
1294
+ });
1295
+ return { items, pageNumber, pageSize, hasNextPage: items.length === pageSize };
1296
+ }
1264
1297
  /** Async-iterates every category across pages. */
1265
1298
  listAll(params = {}, auth2 = ANON2) {
1266
1299
  const pageSize = params.pageSize ?? 50;
@@ -1585,26 +1618,31 @@ var CheckoutService = class {
1585
1618
  };
1586
1619
 
1587
1620
  // src/services/payment.ts
1621
+ var ANON3 = auth.anonymous();
1588
1622
  var PaymentGatewayService = class {
1589
1623
  constructor(ctx) {
1590
1624
  this.ctx = ctx;
1591
1625
  }
1592
1626
  ctx;
1593
1627
  static channel = "payment";
1594
- /** Lists configured frontend payment modes. */
1595
- async listPaymentModes(auth2) {
1628
+ /**
1629
+ * Lists configured frontend payment modes. The endpoint requires a bearer
1630
+ * token but no customer scope ("No scope required"), so it defaults to an
1631
+ * anonymous context and works for guests and logged-in customers alike.
1632
+ */
1633
+ async listPaymentModes(authCtx = ANON3) {
1596
1634
  return this.ctx.http.request({
1597
1635
  method: "GET",
1598
1636
  path: `/payment-gateway/${this.ctx.tenant}/paymentmodes/frontend`,
1599
- auth: requireCustomer(auth2)
1637
+ auth: authCtx
1600
1638
  });
1601
1639
  }
1602
1640
  /** Authorizes a post-checkout (deferred) payment for an existing order. */
1603
- async authorize(input, auth2) {
1641
+ async authorize(input, authCtx) {
1604
1642
  return this.ctx.http.request({
1605
1643
  method: "POST",
1606
1644
  path: `/payment-gateway/${this.ctx.tenant}/payment/frontend/authorize`,
1607
- auth: requireCustomer(auth2),
1645
+ auth: requireCustomer(authCtx),
1608
1646
  body: input
1609
1647
  });
1610
1648
  }
@@ -1621,10 +1659,10 @@ function normalizeMatch(raw) {
1621
1659
  };
1622
1660
  return { ...base, itemId, itemRef };
1623
1661
  }
1624
- var ANON3 = { kind: "anonymous" };
1662
+ var ANON4 = { kind: "anonymous" };
1625
1663
  var SERVICE = { kind: "service" };
1626
1664
  function requireContextAuth(auth2) {
1627
- const a = auth2 ?? ANON3;
1665
+ const a = auth2 ?? ANON4;
1628
1666
  if (a.kind === "anonymous" || a.kind === "customer" || a.kind === "raw") return a;
1629
1667
  throw new EmporixAuthError(
1630
1668
  "match-prices-by-context requires an anonymous, customer, or raw AuthContext"
@@ -2096,7 +2134,7 @@ var SegmentService = class {
2096
2134
  };
2097
2135
 
2098
2136
  // src/services/site.ts
2099
- var ANON4 = auth.anonymous();
2137
+ var ANON5 = auth.anonymous();
2100
2138
  var SiteService = class {
2101
2139
  constructor(ctx) {
2102
2140
  this.ctx = ctx;
@@ -2104,7 +2142,7 @@ var SiteService = class {
2104
2142
  ctx;
2105
2143
  static channel = "site";
2106
2144
  /** Lists active sites. */
2107
- async list(authCtx = ANON4) {
2145
+ async list(authCtx = ANON5) {
2108
2146
  return this.ctx.http.request({
2109
2147
  method: "GET",
2110
2148
  path: `/site/${this.ctx.tenant}/sites`,
@@ -2112,7 +2150,7 @@ var SiteService = class {
2112
2150
  });
2113
2151
  }
2114
2152
  /** Retrieves one site by code. */
2115
- async get(code, authCtx = ANON4) {
2153
+ async get(code, authCtx = ANON5) {
2116
2154
  return this.ctx.http.request({
2117
2155
  method: "GET",
2118
2156
  path: `/site/${this.ctx.tenant}/sites/${code}`,
@@ -2123,7 +2161,7 @@ var SiteService = class {
2123
2161
  * Returns the tenant's default site (the one with `default: true`).
2124
2162
  * Throws if no default is configured — a tenant should always have one.
2125
2163
  */
2126
- async current(authCtx = ANON4) {
2164
+ async current(authCtx = ANON5) {
2127
2165
  const sites = await this.list(authCtx);
2128
2166
  const def = sites.find((s) => s.default);
2129
2167
  if (!def) {
@@ -2136,7 +2174,7 @@ var SiteService = class {
2136
2174
  };
2137
2175
 
2138
2176
  // src/services/session-context.ts
2139
- var ANON5 = auth.anonymous();
2177
+ var ANON6 = auth.anonymous();
2140
2178
  var SessionContextService = class {
2141
2179
  constructor(ctx) {
2142
2180
  this.ctx = ctx;
@@ -2148,7 +2186,7 @@ var SessionContextService = class {
2148
2186
  * 404 (no session exists yet — the session-context is created server-side
2149
2187
  * only after the user creates a cart).
2150
2188
  */
2151
- async get(authCtx = ANON5) {
2189
+ async get(authCtx = ANON6) {
2152
2190
  try {
2153
2191
  return await this.ctx.http.request({
2154
2192
  method: "GET",
@@ -2166,7 +2204,7 @@ var SessionContextService = class {
2166
2204
  * server returned 404 on the version-lookup GET, so there is nothing to
2167
2205
  * update). Non-404 errors propagate.
2168
2206
  */
2169
- async patch(input, authCtx = ANON5) {
2207
+ async patch(input, authCtx = ANON6) {
2170
2208
  let version2 = input.version;
2171
2209
  if (version2 === void 0) {
2172
2210
  const current = await this.get(authCtx);
@@ -2431,6 +2469,9 @@ var OrdersService = class {
2431
2469
  setIfDefined2(query, "status", opts.status);
2432
2470
  setIfDefined2(query, "legalEntityId", opts.legalEntityId);
2433
2471
  setIfDefined2(query, "siteCode", opts.siteCode);
2472
+ if (opts.q !== void 0) {
2473
+ setIfDefined2(query, "q", resolveQuery(opts.q, { compoundLogicalQuery: false }));
2474
+ }
2434
2475
  const headers = this.saasHeader(opts.saasToken);
2435
2476
  const items = await this.ctx.http.request({
2436
2477
  method: "GET",
@@ -2501,7 +2542,7 @@ var SalesOrdersService = class {
2501
2542
  };
2502
2543
 
2503
2544
  // src/services/availability.ts
2504
- var ANON6 = { kind: "anonymous" };
2545
+ var ANON7 = { kind: "anonymous" };
2505
2546
  var AvailabilityService = class {
2506
2547
  constructor(ctx) {
2507
2548
  this.ctx = ctx;
@@ -2512,7 +2553,7 @@ var AvailabilityService = class {
2512
2553
  * Single product. Resolves the availability record, or — when
2513
2554
  * `opts.defaultAvailableOnNotFound` is set — a default available record on 404.
2514
2555
  */
2515
- async get(productId, siteCode, auth2 = ANON6, opts = {}) {
2556
+ async get(productId, siteCode, auth2 = ANON7, opts = {}) {
2516
2557
  try {
2517
2558
  return await this.ctx.http.request({
2518
2559
  method: "GET",
@@ -2535,7 +2576,7 @@ var AvailabilityService = class {
2535
2576
  * `opts.defaultAvailableOnNotFound` is set). The result preserves input order
2536
2577
  * and length. An empty `productIds` resolves to `[]` without a request.
2537
2578
  */
2538
- async getMany(productIds, siteCode, auth2 = ANON6, opts = {}) {
2579
+ async getMany(productIds, siteCode, auth2 = ANON7, opts = {}) {
2539
2580
  if (productIds.length === 0) return [];
2540
2581
  const found = await this.ctx.http.request({
2541
2582
  method: "POST",
@@ -4946,13 +4987,14 @@ var VendorService = class {
4946
4987
  auth: auth2
4947
4988
  });
4948
4989
  }
4949
- /** Search vendors (`POST /vendors/search`). */
4990
+ /** Search vendors (`POST /vendors/search`). A built filter in `q` is resolved to a string. */
4950
4991
  async searchVendors(query, auth2 = SERVICE24) {
4992
+ const body = query.q !== void 0 ? { ...query, q: resolveQuery(query.q, { compoundLogicalQuery: false }) } : query;
4951
4993
  return this.ctx.http.request({
4952
4994
  method: "POST",
4953
4995
  path: `${this.base()}/vendors/search`,
4954
4996
  auth: auth2,
4955
- body: query
4997
+ body
4956
4998
  });
4957
4999
  }
4958
5000
  /** Create a vendor. */
@@ -5136,9 +5178,10 @@ var CustomerAdminService = class {
5136
5178
  ...Object.keys(query).length ? { query } : {}
5137
5179
  });
5138
5180
  }
5139
- /** Search customers (`POST /customers/search`). */
5181
+ /** Search customers (`POST /customers/search`). A built filter in `q` is resolved to a string. */
5140
5182
  async searchCustomers(query, auth2 = SERVICE26) {
5141
- return this.ctx.http.request({ method: "POST", path: `${this.base()}/search`, auth: auth2, body: query });
5183
+ const body = query.q !== void 0 ? { ...query, q: resolveQuery(query.q, { compoundLogicalQuery: false }) } : query;
5184
+ return this.ctx.http.request({ method: "POST", path: `${this.base()}/search`, auth: auth2, body });
5142
5185
  }
5143
5186
  /** Retrieve a customer profile by number. */
5144
5187
  async getCustomer(customerNumber, auth2 = SERVICE26) {
@@ -5540,6 +5583,7 @@ exports.errorFromResponse = errorFromResponse;
5540
5583
  exports.iterateAll = iterateAll;
5541
5584
  exports.productIdFromYrn = productIdFromYrn;
5542
5585
  exports.redact = redact;
5586
+ exports.resolveQuery = resolveQuery;
5543
5587
  exports.resolveToken = resolveToken;
5544
5588
  exports.validateConfig = validateConfig;
5545
5589
  //# sourceMappingURL=index.cjs.map