@viu/emporix-sdk 2.0.0 → 2.2.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 (60) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +13 -1
  3. package/dist/availability.cjs +101 -0
  4. package/dist/availability.cjs.map +1 -0
  5. package/dist/availability.d.cts +87 -0
  6. package/dist/availability.d.ts +87 -0
  7. package/dist/availability.js +4 -0
  8. package/dist/availability.js.map +1 -0
  9. package/dist/cart.d.cts +1 -1
  10. package/dist/cart.d.ts +1 -1
  11. package/dist/category.d.cts +1 -1
  12. package/dist/category.d.ts +1 -1
  13. package/dist/checkout.d.cts +1 -1
  14. package/dist/checkout.d.ts +1 -1
  15. package/dist/{chunk-QDABAMVN.js → chunk-5JHTZBOC.js} +65 -2
  16. package/dist/chunk-5JHTZBOC.js.map +1 -0
  17. package/dist/chunk-SCI7D74B.js +91 -0
  18. package/dist/chunk-SCI7D74B.js.map +1 -0
  19. package/dist/chunk-XMEP74BU.js +60 -0
  20. package/dist/chunk-XMEP74BU.js.map +1 -0
  21. package/dist/companies.d.cts +1 -1
  22. package/dist/companies.d.ts +1 -1
  23. package/dist/contacts.d.cts +1 -1
  24. package/dist/contacts.d.ts +1 -1
  25. package/dist/{context-mMTqsW1Z.d.cts → context-Bs5ZSwds.d.cts} +1 -1
  26. package/dist/{context-mMTqsW1Z.d.ts → context-Bs5ZSwds.d.ts} +1 -1
  27. package/dist/customer-groups.d.cts +1 -1
  28. package/dist/customer-groups.d.ts +1 -1
  29. package/dist/customer.d.cts +1 -1
  30. package/dist/customer.d.ts +1 -1
  31. package/dist/index.cjs +295 -6
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.d.cts +125 -4
  34. package/dist/index.d.ts +125 -4
  35. package/dist/index.js +137 -11
  36. package/dist/index.js.map +1 -1
  37. package/dist/locations.d.cts +1 -1
  38. package/dist/locations.d.ts +1 -1
  39. package/dist/media.d.cts +1 -1
  40. package/dist/media.d.ts +1 -1
  41. package/dist/orders.d.cts +1 -1
  42. package/dist/orders.d.ts +1 -1
  43. package/dist/payment.d.cts +1 -1
  44. package/dist/payment.d.ts +1 -1
  45. package/dist/price.cjs +44 -0
  46. package/dist/price.cjs.map +1 -1
  47. package/dist/price.d.cts +27 -2
  48. package/dist/price.d.ts +27 -2
  49. package/dist/price.js +1 -1
  50. package/dist/product.cjs +63 -0
  51. package/dist/product.cjs.map +1 -1
  52. package/dist/product.d.cts +31 -1
  53. package/dist/product.d.ts +31 -1
  54. package/dist/product.js +1 -1
  55. package/dist/segment.d.cts +1 -1
  56. package/dist/segment.d.ts +1 -1
  57. package/package.json +6 -1
  58. package/dist/chunk-HEO34WLO.js +0 -47
  59. package/dist/chunk-HEO34WLO.js.map +0 -1
  60. package/dist/chunk-QDABAMVN.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,43 @@
1
1
  # @viu/emporix-sdk
2
2
 
3
+ ## 2.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#61](https://github.com/viuteam/emporix-sdk/pull/61) [`4a869c9`](https://github.com/viuteam/emporix-sdk/commit/4a869c9aa60cce99b79b41b8470af367e1b4e249) Thanks [@amnael1](https://github.com/amnael1)! - Add Configuration Service bindings: `client.tenantConfig` and
8
+ `client.clientConfig` provide full CRUD (`list`/`get`/`create`/`update`/`delete`)
9
+ over tenant-wide and per-client configuration. Server-side only — these use the
10
+ service (clientCredentials) token and must not be called from a browser.
11
+
12
+ - [#63](https://github.com/viuteam/emporix-sdk/pull/63) [`bb2ce4f`](https://github.com/viuteam/emporix-sdk/commit/bb2ce4f891e50e07cee02e03340d2abe1133fdc0) Thanks [@amnael1](https://github.com/amnael1)! - Add `products.searchByCodes(codes, { chunkSize? })` — bulk-fetch products by
13
+ `code` via `POST /products/search` (`q="code:(…)"`), chunked at 100, analogous
14
+ to `searchByIds`. Codes with query-delimiter characters are dropped with a
15
+ warning. Adds the `useProductsByCodes` React hook (30s stale-time).
16
+
17
+ ### Patch Changes
18
+
19
+ - [#62](https://github.com/viuteam/emporix-sdk/pull/62) [`9747445`](https://github.com/viuteam/emporix-sdk/commit/9747445c13a27319462755c5333c9ae0a4741e68) Thanks [@amnael1](https://github.com/amnael1)! - Report the real package version as `sdkVersion` on every log line instead of the
20
+ hardcoded `0.0.0` placeholder. The version is now read from `package.json` and
21
+ inlined at build time (browser-safe, no runtime filesystem access).
22
+
23
+ ## 2.1.0
24
+
25
+ ### Minor Changes
26
+
27
+ - [#56](https://github.com/viuteam/emporix-sdk/pull/56) [`939a1b0`](https://github.com/viuteam/emporix-sdk/commit/939a1b0a24063db38545dc81f88c319f93e81833) Thanks [@amnael1](https://github.com/amnael1)! - Add AvailabilityService (`client.availability.get` / `.getMany`) and the
28
+ `useAvailability` / `useAvailabilities` React hooks for site-aware product
29
+ availability. `getMany` uses the batch `POST .../search` endpoint and returns
30
+ results in input order; an opt-in `defaultAvailableOnNotFound` treats products
31
+ with no stock record as available. New `@viu/emporix-sdk/availability` subpath export.
32
+
33
+ - [#58](https://github.com/viuteam/emporix-sdk/pull/58) [`caaff28`](https://github.com/viuteam/emporix-sdk/commit/caaff2819e64cf42e4c58dfe4c04fa994312f901) Thanks [@amnael1](https://github.com/amnael1)! - Add PriceService.matchByContextChunked and the useMatchPricesChunked React hook:
34
+ split large match-prices-by-context requests into bounded-concurrency chunks
35
+ (default 50 items, 4 in flight) with per-chunk error handling.
36
+
37
+ - [#57](https://github.com/viuteam/emporix-sdk/pull/57) [`0302ea3`](https://github.com/viuteam/emporix-sdk/commit/0302ea368e6d7feb0a064aac71a6f5314380deb3) Thanks [@amnael1](https://github.com/amnael1)! - Add ProductService.listVariantChildren / listVariantChildrenAll and the
38
+ useVariantChildren React hook to resolve the VARIANT children of a
39
+ PARENT_VARIANT product without hand-building the search query.
40
+
3
41
  ## 2.0.0
4
42
 
5
43
  ### Major Changes
package/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # @viu/emporix-sdk
2
2
 
3
+ [![CI](https://github.com/viuteam/emporix-sdk/actions/workflows/pr-check.yml/badge.svg?branch=main)](https://github.com/viuteam/emporix-sdk/actions/workflows/pr-check.yml)
4
+ [![npm](https://img.shields.io/npm/v/@viu/emporix-sdk)](https://www.npmjs.com/package/@viu/emporix-sdk)
5
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/@viu/emporix-sdk)](https://bundlephobia.com/package/@viu/emporix-sdk)
6
+
3
7
  Framework-agnostic TypeScript SDK for the Emporix Commerce Engine. Native
4
8
  `fetch` only (Node 20.19+), zero runtime dependencies.
5
9
 
@@ -111,11 +115,19 @@ require a server-only scope — every call defaults to a `service`
111
115
  on the product) or the `useProductMedia(productId)` hook, not by calling
112
116
  the Media service directly. See [`../../docs/media.md`](../../docs/media.md).
113
117
 
118
+ ## Availability
119
+
120
+ `sdk.availability` reads site-aware product availability: `get(productId, siteCode)`
121
+ for one product and `getMany(productIds, siteCode)` for a batch (single
122
+ `POST .../search` request, result in input order). The opt-in
123
+ `defaultAvailableOnNotFound` returns `{ available: true }` for products without a
124
+ stock record. There is no restock-date field. See [`../../docs/availability.md`](../../docs/availability.md).
125
+
114
126
  ## Subpath exports
115
127
 
116
128
  `@viu/emporix-sdk` (everything) plus `./customer`, `./product`, `./category`,
117
129
  `./cart`, `./checkout`, `./payment`, `./price`, `./media`, `./segment`,
118
- `./companies`, `./contacts`, `./locations`, `./customer-groups` for tree-shaking.
130
+ `./companies`, `./contacts`, `./locations`, `./customer-groups`, `./availability` for tree-shaking.
119
131
 
120
132
  ## Authors
121
133
 
@@ -0,0 +1,101 @@
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 EmporixNotFoundError = class extends EmporixError {
42
+ };
43
+
44
+ // src/services/availability.ts
45
+ var ANON = { kind: "anonymous" };
46
+ var AvailabilityService = class {
47
+ constructor(ctx) {
48
+ this.ctx = ctx;
49
+ }
50
+ ctx;
51
+ /**
52
+ * Single product. Resolves the availability record, or — when
53
+ * `opts.defaultAvailableOnNotFound` is set — a default available record on 404.
54
+ */
55
+ async get(productId, siteCode, auth = ANON, opts = {}) {
56
+ try {
57
+ return await this.ctx.http.request({
58
+ method: "GET",
59
+ path: `/availability/${this.ctx.tenant}/availability/${encodeURIComponent(
60
+ productId
61
+ )}/${encodeURIComponent(siteCode)}`,
62
+ auth
63
+ });
64
+ } catch (err) {
65
+ if (err instanceof EmporixNotFoundError && opts.defaultAvailableOnNotFound) {
66
+ return { productId, site: siteCode, available: true };
67
+ }
68
+ throw err;
69
+ }
70
+ }
71
+ /**
72
+ * Batch read via `POST .../availability/search` (one request). Products with
73
+ * no availability record are absent from the response; each is synthesized as
74
+ * `{ available: false }` (or `{ available: true }` when
75
+ * `opts.defaultAvailableOnNotFound` is set). The result preserves input order
76
+ * and length. An empty `productIds` resolves to `[]` without a request.
77
+ */
78
+ async getMany(productIds, siteCode, auth = ANON, opts = {}) {
79
+ if (productIds.length === 0) return [];
80
+ const found = await this.ctx.http.request({
81
+ method: "POST",
82
+ path: `/availability/${this.ctx.tenant}/availability/search`,
83
+ auth,
84
+ query: { site: siteCode, pageSize: productIds.length },
85
+ body: productIds
86
+ });
87
+ const byId = /* @__PURE__ */ new Map();
88
+ for (const a of found) if (a.productId) byId.set(a.productId, a);
89
+ return productIds.map(
90
+ (id) => byId.get(id) ?? {
91
+ productId: id,
92
+ site: siteCode,
93
+ available: Boolean(opts.defaultAvailableOnNotFound)
94
+ }
95
+ );
96
+ }
97
+ };
98
+
99
+ exports.AvailabilityService = AvailabilityService;
100
+ //# sourceMappingURL=availability.cjs.map
101
+ //# sourceMappingURL=availability.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/errors.ts","../src/services/availability.ts"],"names":[],"mappings":"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","file":"availability.cjs","sourcesContent":["/** Keys whose values are stripped from any serialised error body. */\nconst REDACTED_BODY_KEYS = new Set([\n \"access_token\", \"refresh_token\", \"token\", \"customertoken\", \"saastoken\",\n \"secret\", \"client_secret\", \"authorization\", \"password\",\n]);\n\nfunction scrub(body: unknown): unknown {\n if (Array.isArray(body)) return body.map(scrub);\n if (body && typeof body === \"object\") {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(body as Record<string, unknown>)) {\n out[k] = REDACTED_BODY_KEYS.has(k.toLowerCase()) ? \"***redacted***\" : scrub(v);\n }\n return out;\n }\n return body;\n}\n\n/** Base class for every error thrown by the SDK. */\nexport class EmporixError extends Error {\n readonly status: number | undefined;\n readonly body: unknown;\n constructor(message: string, status?: number, body?: unknown) {\n super(message);\n this.name = new.target.name;\n this.status = status;\n this.body = body;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n /** Safe serialisation — token-like body fields are redacted. */\n toJSON(): Record<string, unknown> {\n return { name: this.name, message: this.message, status: this.status, body: scrub(this.body) };\n }\n}\n\n/** 401 — authentication failed or token expired. */\nexport class EmporixAuthError extends EmporixError {}\n/** 403 — authenticated but not permitted. */\nexport class EmporixForbiddenError extends EmporixError {}\n/** 404 — resource not found. */\nexport class EmporixNotFoundError extends EmporixError {}\n/** 400/422 — request validation failed. */\nexport class EmporixValidationError extends EmporixError {}\n/** 5xx — server-side failure. */\nexport class EmporixServerError extends EmporixError {}\n\n/**\n * 403 with a `missing scope: <name>` hint in the body's `details` array.\n * Subclasses {@link EmporixForbiddenError} so existing 403 catches still work.\n */\nexport class EmporixInsufficientScopeError extends EmporixForbiddenError {\n readonly requiredScope: string | undefined;\n constructor(message: string, status: number, body: unknown, requiredScope?: string) {\n super(message, status, body);\n this.requiredScope = requiredScope;\n }\n}\n\nconst SCOPE_HINT_RE = /missing scope[: ]+([a-z0-9._-]+)/i;\n\nfunction extractRequiredScope(body: unknown): string | undefined {\n if (body && typeof body === \"object\" && \"details\" in body) {\n const details = (body as { details?: unknown }).details;\n if (Array.isArray(details)) {\n for (const d of details) {\n if (typeof d === \"string\") {\n const m = d.match(SCOPE_HINT_RE);\n if (m) return m[1];\n }\n }\n }\n }\n return undefined;\n}\n\n/** Maps an HTTP status to the matching {@link EmporixError} subclass. */\nexport function errorFromResponse(status: number, message: string, body: unknown): EmporixError {\n if (status === 401) return new EmporixAuthError(message, status, body);\n if (status === 403) {\n const scope = extractRequiredScope(body);\n if (scope) return new EmporixInsufficientScopeError(message, status, body, scope);\n return new EmporixForbiddenError(message, status, body);\n }\n if (status === 404) return new EmporixNotFoundError(message, status, body);\n if (status === 400 || status === 422) return new EmporixValidationError(message, status, body);\n if (status >= 500) return new EmporixServerError(message, status, body);\n return new EmporixError(message, status, body);\n}\n","import type { ClientContext } from \"../core/context\";\nimport type { AuthContext } from \"../core/auth\";\nimport { EmporixNotFoundError } from \"../core/errors\";\nimport type { AvailabilityWithBundle } from \"../generated/availability\";\n\n/**\n * Site-aware product availability. Mirrors the Emporix Availability Service\n * `AvailabilityWithBundle` shape (the single-product GET and the batch search\n * both return the bundle-aware variant). There is no restock-date field.\n */\nexport type Availability = AvailabilityWithBundle;\n\n/** Shared options for {@link AvailabilityService} reads. */\nexport interface AvailabilityOptions {\n /**\n * When `true`, a product with no availability record resolves to a default\n * `{ available: true }` instead of throwing (single `get`) / being marked\n * unavailable (`getMany`). Off by default — opt in for tenants that sell\n * without stock management.\n */\n defaultAvailableOnNotFound?: boolean;\n}\n\nconst ANON: AuthContext = { kind: \"anonymous\" };\n\n/**\n * Reads product availability per site. Default auth is anonymous (like\n * `PriceService.matchByContext`); pass a customer/raw/service context to use a\n * different token. Requires the `availability.availability_view` scope on\n * whichever token is used.\n */\nexport class AvailabilityService {\n constructor(private readonly ctx: ClientContext) {}\n\n /**\n * Single product. Resolves the availability record, or — when\n * `opts.defaultAvailableOnNotFound` is set — a default available record on 404.\n */\n async get(\n productId: string,\n siteCode: string,\n auth: AuthContext = ANON,\n opts: AvailabilityOptions = {},\n ): Promise<Availability> {\n try {\n return await this.ctx.http.request<Availability>({\n method: \"GET\",\n path: `/availability/${this.ctx.tenant}/availability/${encodeURIComponent(\n productId,\n )}/${encodeURIComponent(siteCode)}`,\n auth,\n });\n } catch (err) {\n if (err instanceof EmporixNotFoundError && opts.defaultAvailableOnNotFound) {\n return { productId, site: siteCode, available: true };\n }\n throw err;\n }\n }\n\n /**\n * Batch read via `POST .../availability/search` (one request). Products with\n * no availability record are absent from the response; each is synthesized as\n * `{ available: false }` (or `{ available: true }` when\n * `opts.defaultAvailableOnNotFound` is set). The result preserves input order\n * and length. An empty `productIds` resolves to `[]` without a request.\n */\n async getMany(\n productIds: string[],\n siteCode: string,\n auth: AuthContext = ANON,\n opts: AvailabilityOptions = {},\n ): Promise<Availability[]> {\n if (productIds.length === 0) return [];\n const found = await this.ctx.http.request<Availability[]>({\n method: \"POST\",\n path: `/availability/${this.ctx.tenant}/availability/search`,\n auth,\n query: { site: siteCode, pageSize: productIds.length },\n body: productIds,\n });\n const byId = new Map<string, Availability>();\n for (const a of found) if (a.productId) byId.set(a.productId, a);\n return productIds.map(\n (id) =>\n byId.get(id) ?? {\n productId: id,\n site: siteCode,\n available: Boolean(opts.defaultAvailableOnNotFound),\n },\n );\n }\n}\n"]}
@@ -0,0 +1,87 @@
1
+ import { C as ClientContext, b as AuthContext } from './context-Bs5ZSwds.cjs';
2
+
3
+ type DistributionChannel = 'ASSORTMENT' | 'HOME_DELIVERY' | 'PICKUP';
4
+ type Availability$1 = {
5
+ id?: string;
6
+ site?: string;
7
+ stockLevel?: number;
8
+ available?: boolean;
9
+ productId?: string;
10
+ vendorId?: string;
11
+ popularity?: number;
12
+ distributionChannel?: DistributionChannel;
13
+ mixins?: {
14
+ [key: string]: unknown;
15
+ };
16
+ metadata?: {
17
+ createdAt?: string;
18
+ modifiedAt?: string;
19
+ mixins?: {
20
+ [key: string]: string;
21
+ };
22
+ };
23
+ };
24
+ type AvailabilityWithBundle = {
25
+ id?: string;
26
+ site?: string;
27
+ stockLevel?: number;
28
+ available?: boolean;
29
+ productId?: string;
30
+ vendorId?: string;
31
+ popularity?: number;
32
+ distributionChannel?: DistributionChannel;
33
+ bundleAvailabilities?: AvailabilityList;
34
+ mixins?: {
35
+ [key: string]: unknown;
36
+ };
37
+ metadata?: {
38
+ createdAt?: string;
39
+ modifiedAt?: string;
40
+ mixins?: {
41
+ [key: string]: string;
42
+ };
43
+ };
44
+ };
45
+ type AvailabilityList = Array<Availability$1>;
46
+
47
+ /**
48
+ * Site-aware product availability. Mirrors the Emporix Availability Service
49
+ * `AvailabilityWithBundle` shape (the single-product GET and the batch search
50
+ * both return the bundle-aware variant). There is no restock-date field.
51
+ */
52
+ type Availability = AvailabilityWithBundle;
53
+ /** Shared options for {@link AvailabilityService} reads. */
54
+ interface AvailabilityOptions {
55
+ /**
56
+ * When `true`, a product with no availability record resolves to a default
57
+ * `{ available: true }` instead of throwing (single `get`) / being marked
58
+ * unavailable (`getMany`). Off by default — opt in for tenants that sell
59
+ * without stock management.
60
+ */
61
+ defaultAvailableOnNotFound?: boolean;
62
+ }
63
+ /**
64
+ * Reads product availability per site. Default auth is anonymous (like
65
+ * `PriceService.matchByContext`); pass a customer/raw/service context to use a
66
+ * different token. Requires the `availability.availability_view` scope on
67
+ * whichever token is used.
68
+ */
69
+ declare class AvailabilityService {
70
+ private readonly ctx;
71
+ constructor(ctx: ClientContext);
72
+ /**
73
+ * Single product. Resolves the availability record, or — when
74
+ * `opts.defaultAvailableOnNotFound` is set — a default available record on 404.
75
+ */
76
+ get(productId: string, siteCode: string, auth?: AuthContext, opts?: AvailabilityOptions): Promise<Availability>;
77
+ /**
78
+ * Batch read via `POST .../availability/search` (one request). Products with
79
+ * no availability record are absent from the response; each is synthesized as
80
+ * `{ available: false }` (or `{ available: true }` when
81
+ * `opts.defaultAvailableOnNotFound` is set). The result preserves input order
82
+ * and length. An empty `productIds` resolves to `[]` without a request.
83
+ */
84
+ getMany(productIds: string[], siteCode: string, auth?: AuthContext, opts?: AvailabilityOptions): Promise<Availability[]>;
85
+ }
86
+
87
+ export { type Availability, type AvailabilityOptions, AvailabilityService };
@@ -0,0 +1,87 @@
1
+ import { C as ClientContext, b as AuthContext } from './context-Bs5ZSwds.js';
2
+
3
+ type DistributionChannel = 'ASSORTMENT' | 'HOME_DELIVERY' | 'PICKUP';
4
+ type Availability$1 = {
5
+ id?: string;
6
+ site?: string;
7
+ stockLevel?: number;
8
+ available?: boolean;
9
+ productId?: string;
10
+ vendorId?: string;
11
+ popularity?: number;
12
+ distributionChannel?: DistributionChannel;
13
+ mixins?: {
14
+ [key: string]: unknown;
15
+ };
16
+ metadata?: {
17
+ createdAt?: string;
18
+ modifiedAt?: string;
19
+ mixins?: {
20
+ [key: string]: string;
21
+ };
22
+ };
23
+ };
24
+ type AvailabilityWithBundle = {
25
+ id?: string;
26
+ site?: string;
27
+ stockLevel?: number;
28
+ available?: boolean;
29
+ productId?: string;
30
+ vendorId?: string;
31
+ popularity?: number;
32
+ distributionChannel?: DistributionChannel;
33
+ bundleAvailabilities?: AvailabilityList;
34
+ mixins?: {
35
+ [key: string]: unknown;
36
+ };
37
+ metadata?: {
38
+ createdAt?: string;
39
+ modifiedAt?: string;
40
+ mixins?: {
41
+ [key: string]: string;
42
+ };
43
+ };
44
+ };
45
+ type AvailabilityList = Array<Availability$1>;
46
+
47
+ /**
48
+ * Site-aware product availability. Mirrors the Emporix Availability Service
49
+ * `AvailabilityWithBundle` shape (the single-product GET and the batch search
50
+ * both return the bundle-aware variant). There is no restock-date field.
51
+ */
52
+ type Availability = AvailabilityWithBundle;
53
+ /** Shared options for {@link AvailabilityService} reads. */
54
+ interface AvailabilityOptions {
55
+ /**
56
+ * When `true`, a product with no availability record resolves to a default
57
+ * `{ available: true }` instead of throwing (single `get`) / being marked
58
+ * unavailable (`getMany`). Off by default — opt in for tenants that sell
59
+ * without stock management.
60
+ */
61
+ defaultAvailableOnNotFound?: boolean;
62
+ }
63
+ /**
64
+ * Reads product availability per site. Default auth is anonymous (like
65
+ * `PriceService.matchByContext`); pass a customer/raw/service context to use a
66
+ * different token. Requires the `availability.availability_view` scope on
67
+ * whichever token is used.
68
+ */
69
+ declare class AvailabilityService {
70
+ private readonly ctx;
71
+ constructor(ctx: ClientContext);
72
+ /**
73
+ * Single product. Resolves the availability record, or — when
74
+ * `opts.defaultAvailableOnNotFound` is set — a default available record on 404.
75
+ */
76
+ get(productId: string, siteCode: string, auth?: AuthContext, opts?: AvailabilityOptions): Promise<Availability>;
77
+ /**
78
+ * Batch read via `POST .../availability/search` (one request). Products with
79
+ * no availability record are absent from the response; each is synthesized as
80
+ * `{ available: false }` (or `{ available: true }` when
81
+ * `opts.defaultAvailableOnNotFound` is set). The result preserves input order
82
+ * and length. An empty `productIds` resolves to `[]` without a request.
83
+ */
84
+ getMany(productIds: string[], siteCode: string, auth?: AuthContext, opts?: AvailabilityOptions): Promise<Availability[]>;
85
+ }
86
+
87
+ export { type Availability, type AvailabilityOptions, AvailabilityService };
@@ -0,0 +1,4 @@
1
+ export { AvailabilityService } from './chunk-XMEP74BU.js';
2
+ import './chunk-KTODT6DV.js';
3
+ //# sourceMappingURL=availability.js.map
4
+ //# sourceMappingURL=availability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"availability.js"}
package/dist/cart.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { C as ClientContext, b as AuthContext } from './context-mMTqsW1Z.cjs';
1
+ import { C as ClientContext, b as AuthContext } from './context-Bs5ZSwds.cjs';
2
2
 
3
3
  /**
4
4
  * Access control identifier that determines who can view or manage the cart.
package/dist/cart.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { C as ClientContext, b as AuthContext } from './context-mMTqsW1Z.js';
1
+ import { C as ClientContext, b as AuthContext } from './context-Bs5ZSwds.js';
2
2
 
3
3
  /**
4
4
  * Access control identifier that determines who can view or manage the cart.
@@ -1,4 +1,4 @@
1
- import { C as ClientContext, b as AuthContext, P as PaginatedItems } from './context-mMTqsW1Z.cjs';
1
+ import { C as ClientContext, b as AuthContext, P as PaginatedItems } from './context-Bs5ZSwds.cjs';
2
2
  import { Product } from './product.cjs';
3
3
 
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { C as ClientContext, b as AuthContext, P as PaginatedItems } from './context-mMTqsW1Z.js';
1
+ import { C as ClientContext, b as AuthContext, P as PaginatedItems } from './context-Bs5ZSwds.js';
2
2
  import { Product } from './product.js';
3
3
 
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { C as ClientContext, b as AuthContext } from './context-mMTqsW1Z.cjs';
1
+ import { C as ClientContext, b as AuthContext } from './context-Bs5ZSwds.cjs';
2
2
 
3
3
  /**
4
4
  * Information about the customer's address.
@@ -1,4 +1,4 @@
1
- import { C as ClientContext, b as AuthContext } from './context-mMTqsW1Z.js';
1
+ import { C as ClientContext, b as AuthContext } from './context-Bs5ZSwds.js';
2
2
 
3
3
  /**
4
4
  * Information about the customer's address.
@@ -82,8 +82,71 @@ var ProductService = class {
82
82
  );
83
83
  return pages.flat();
84
84
  }
85
+ /**
86
+ * Bulk fetch by code. POSTs `/products/search` with `q="code:(c1,c2,…)"`,
87
+ * chunking when the list is larger than `options.chunkSize` (default 100).
88
+ * Duplicate codes are de-duplicated. Codes containing query-delimiter
89
+ * characters (`(`, `)`, `,`, whitespace, `"`) are dropped with a logged
90
+ * warning, because the Emporix `q` syntax uses them as delimiters and does
91
+ * not support escaping them in a plain IN-list. An empty list — or one with
92
+ * no safe codes — short-circuits with no HTTP call. **Order is not
93
+ * guaranteed** across chunks — re-index by `code` if order matters.
94
+ */
95
+ async searchByCodes(codes, options = {}, auth = ANON) {
96
+ const unique = [...new Set(codes)];
97
+ const unsafe = /[(),"\s]/;
98
+ const safe = unique.filter((c) => !unsafe.test(c));
99
+ const dropped = unique.filter((c) => unsafe.test(c));
100
+ if (dropped.length > 0) {
101
+ this.ctx.logger.warn(
102
+ "products.searchByCodes: dropped codes containing query-delimiter characters",
103
+ { dropped }
104
+ );
105
+ }
106
+ if (safe.length === 0) return [];
107
+ const chunkSize = options.chunkSize ?? 100;
108
+ const chunks = [];
109
+ for (let i = 0; i < safe.length; i += chunkSize) {
110
+ chunks.push(safe.slice(i, i + chunkSize));
111
+ }
112
+ const pages = await Promise.all(
113
+ chunks.map(
114
+ (chunk) => this.ctx.http.request({
115
+ method: "POST",
116
+ path: `/product/${this.ctx.tenant}/products/search`,
117
+ query: { pageSize: chunk.length },
118
+ auth,
119
+ body: { q: `code:(${chunk.join(",")})` }
120
+ })
121
+ )
122
+ );
123
+ return pages.flat();
124
+ }
125
+ /**
126
+ * Streams the VARIANT children of a PARENT_VARIANT product, page by page,
127
+ * via the search query `productType:VARIANT parentVariantId:<id>`. Default
128
+ * pageSize 200. The query syntax (space-separated fields = implicit AND) is
129
+ * encapsulated here so consumers don't build it themselves.
130
+ */
131
+ listVariantChildrenAll(parentVariantId, params = {}, auth = ANON) {
132
+ const pageSize = params.pageSize ?? 200;
133
+ const q = `productType:VARIANT parentVariantId:${parentVariantId}`;
134
+ return iterateAll((pageNumber) => this.search(q, { pageNumber, pageSize }, auth));
135
+ }
136
+ /**
137
+ * Resolves ALL VARIANT children of a PARENT_VARIANT product into a flat
138
+ * array (loads every page). Default pageSize 200. Returns `[]` when there are
139
+ * no children — never throws.
140
+ */
141
+ async listVariantChildren(parentVariantId, params = {}, auth = ANON) {
142
+ const out = [];
143
+ for await (const child of this.listVariantChildrenAll(parentVariantId, params, auth)) {
144
+ out.push(child);
145
+ }
146
+ return out;
147
+ }
85
148
  };
86
149
 
87
150
  export { ProductService };
88
- //# sourceMappingURL=chunk-QDABAMVN.js.map
89
- //# sourceMappingURL=chunk-QDABAMVN.js.map
151
+ //# sourceMappingURL=chunk-5JHTZBOC.js.map
152
+ //# sourceMappingURL=chunk-5JHTZBOC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/services/product.ts"],"names":[],"mappings":"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","file":"chunk-5JHTZBOC.js","sourcesContent":["import type { ClientContext, PaginatedItems } from \"../core/context\";\nimport { iterateAll } from \"../core/context\";\nimport type { AuthContext } from \"../core/auth\";\nimport type {\n BasicProductWithId,\n BundleProductWithId,\n ParentVariantProductWithId,\n ProductMedia,\n} from \"../generated/product\";\n\nconst ANON: AuthContext = { kind: \"anonymous\" };\n\n/** A product as returned by the Product service (all generated fields). */\nexport type Product = BasicProductWithId | BundleProductWithId | ParentVariantProductWithId;\n\n/** A single product media entry (generated; `ProductMedia` is the list type). */\nexport type Media = ProductMedia[number];\n\n/** Catalog reads. Default auth: anonymous; pass customer for personalized pricing. */\nexport class ProductService {\n constructor(private readonly ctx: ClientContext) {}\n\n /** Fetches one product by id. */\n async get(\n productId: string,\n _opts?: Record<string, never>,\n auth: AuthContext = ANON,\n ): Promise<Product> {\n return this.ctx.http.request<Product>({\n method: \"GET\",\n path: `/product/${this.ctx.tenant}/products/${productId}`,\n auth,\n });\n }\n\n /** Fetches one product by its code. */\n async getByCode(code: string, auth: AuthContext = ANON): Promise<Product> {\n const rows = await this.ctx.http.request<Product[]>({\n method: \"GET\",\n path: `/product/${this.ctx.tenant}/products`,\n query: { q: `code:${code}` },\n auth,\n });\n const first = rows[0];\n if (!first) throw new Error(`No product with code \"${code}\"`);\n return first;\n }\n\n /** One page of products. */\n async list(\n params: { pageNumber?: number; pageSize?: number } = {},\n auth: AuthContext = ANON,\n ): Promise<PaginatedItems<Product>> {\n const pageNumber = params.pageNumber ?? 1;\n const pageSize = params.pageSize ?? 50;\n const items = await this.ctx.http.request<Product[]>({\n method: \"GET\",\n path: `/product/${this.ctx.tenant}/products`,\n query: { pageNumber, pageSize },\n auth,\n });\n return { items, pageNumber, pageSize, hasNextPage: items.length === pageSize };\n }\n\n /** Async-iterates every product across pages. */\n listAll(params: { pageSize?: number } = {}, auth: AuthContext = ANON): AsyncIterable<Product> {\n const pageSize = params.pageSize ?? 50;\n return iterateAll<Product>((pageNumber) => this.list({ pageNumber, pageSize }, auth));\n }\n\n /** Searches products by free-text query. */\n async search(\n query: string,\n params: { pageNumber?: number; pageSize?: number } = {},\n auth: AuthContext = ANON,\n ): Promise<PaginatedItems<Product>> {\n const pageNumber = params.pageNumber ?? 1;\n const pageSize = params.pageSize ?? 50;\n const items = await this.ctx.http.request<Product[]>({\n method: \"GET\",\n path: `/product/${this.ctx.tenant}/products`,\n query: { q: query, pageNumber, pageSize },\n auth,\n });\n return { items, pageNumber, pageSize, hasNextPage: items.length === pageSize };\n }\n\n /**\n * Bulk fetch by id. POSTs `/products/search` with `q=\"id:(id1,id2,…)\"`,\n * chunking when the list is larger than `options.chunkSize` (default\n * 100). An empty list short-circuits with no HTTP call. **Order is not\n * guaranteed** across chunks — re-index by `id` if order matters.\n */\n async searchByIds(\n ids: string[],\n options: { chunkSize?: number } = {},\n auth: AuthContext = ANON,\n ): Promise<Product[]> {\n if (ids.length === 0) return [];\n const chunkSize = options.chunkSize ?? 100;\n const chunks: string[][] = [];\n for (let i = 0; i < ids.length; i += chunkSize) {\n chunks.push(ids.slice(i, i + chunkSize));\n }\n const pages = await Promise.all(\n chunks.map((chunk) =>\n this.ctx.http.request<Product[]>({\n method: \"POST\",\n path: `/product/${this.ctx.tenant}/products/search`,\n query: { pageSize: chunk.length },\n auth,\n body: { q: `id:(${chunk.join(\",\")})` },\n }),\n ),\n );\n return pages.flat();\n }\n\n /**\n * Bulk fetch by code. POSTs `/products/search` with `q=\"code:(c1,c2,…)\"`,\n * chunking when the list is larger than `options.chunkSize` (default 100).\n * Duplicate codes are de-duplicated. Codes containing query-delimiter\n * characters (`(`, `)`, `,`, whitespace, `\"`) are dropped with a logged\n * warning, because the Emporix `q` syntax uses them as delimiters and does\n * not support escaping them in a plain IN-list. An empty list — or one with\n * no safe codes — short-circuits with no HTTP call. **Order is not\n * guaranteed** across chunks — re-index by `code` if order matters.\n */\n async searchByCodes(\n codes: string[],\n options: { chunkSize?: number } = {},\n auth: AuthContext = ANON,\n ): Promise<Product[]> {\n const unique = [...new Set(codes)];\n const unsafe = /[(),\"\\s]/;\n const safe = unique.filter((c) => !unsafe.test(c));\n const dropped = unique.filter((c) => unsafe.test(c));\n if (dropped.length > 0) {\n this.ctx.logger.warn(\n \"products.searchByCodes: dropped codes containing query-delimiter characters\",\n { dropped },\n );\n }\n if (safe.length === 0) return [];\n const chunkSize = options.chunkSize ?? 100;\n const chunks: string[][] = [];\n for (let i = 0; i < safe.length; i += chunkSize) {\n chunks.push(safe.slice(i, i + chunkSize));\n }\n const pages = await Promise.all(\n chunks.map((chunk) =>\n this.ctx.http.request<Product[]>({\n method: \"POST\",\n path: `/product/${this.ctx.tenant}/products/search`,\n query: { pageSize: chunk.length },\n auth,\n body: { q: `code:(${chunk.join(\",\")})` },\n }),\n ),\n );\n return pages.flat();\n }\n\n /**\n * Streams the VARIANT children of a PARENT_VARIANT product, page by page,\n * via the search query `productType:VARIANT parentVariantId:<id>`. Default\n * pageSize 200. The query syntax (space-separated fields = implicit AND) is\n * encapsulated here so consumers don't build it themselves.\n */\n listVariantChildrenAll(\n parentVariantId: string,\n params: { pageSize?: number } = {},\n auth: AuthContext = ANON,\n ): AsyncIterable<Product> {\n const pageSize = params.pageSize ?? 200;\n const q = `productType:VARIANT parentVariantId:${parentVariantId}`;\n return iterateAll<Product>((pageNumber) => this.search(q, { pageNumber, pageSize }, auth));\n }\n\n /**\n * Resolves ALL VARIANT children of a PARENT_VARIANT product into a flat\n * array (loads every page). Default pageSize 200. Returns `[]` when there are\n * no children — never throws.\n */\n async listVariantChildren(\n parentVariantId: string,\n params: { pageSize?: number } = {},\n auth: AuthContext = ANON,\n ): Promise<Product[]> {\n const out: Product[] = [];\n for await (const child of this.listVariantChildrenAll(parentVariantId, params, auth)) {\n out.push(child);\n }\n return out;\n }\n}\n"]}
@@ -0,0 +1,91 @@
1
+ import { EmporixAuthError } from './chunk-KTODT6DV.js';
2
+
3
+ // src/services/price.ts
4
+ var ANON = { kind: "anonymous" };
5
+ var SERVICE = { kind: "service" };
6
+ function requireContextAuth(auth) {
7
+ const a = auth ?? ANON;
8
+ if (a.kind === "anonymous" || a.kind === "customer" || a.kind === "raw") return a;
9
+ throw new EmporixAuthError(
10
+ "match-prices-by-context requires an anonymous, customer, or raw AuthContext"
11
+ );
12
+ }
13
+ var PriceService = class {
14
+ constructor(ctx) {
15
+ this.ctx = ctx;
16
+ }
17
+ ctx;
18
+ /**
19
+ * Resolves prices using the session context bound to the bearer token
20
+ * (currency/site/country were set at anonymous-login time). Default auth:
21
+ * anonymous; pass a customer/raw context for personalized pricing.
22
+ */
23
+ async matchByContext(input, auth) {
24
+ return this.ctx.http.request({
25
+ method: "POST",
26
+ path: `/price/${this.ctx.tenant}/match-prices-by-context`,
27
+ auth: requireContextAuth(auth),
28
+ body: input
29
+ });
30
+ }
31
+ /**
32
+ * Resolves prices from an explicit context. Default auth: service
33
+ * (requires `price.price_read` / `price.price_manage`).
34
+ */
35
+ async match(input, auth = SERVICE) {
36
+ return this.ctx.http.request({
37
+ method: "POST",
38
+ path: `/price/${this.ctx.tenant}/match-prices`,
39
+ auth,
40
+ body: input
41
+ });
42
+ }
43
+ /**
44
+ * Chunked variant of {@link matchByContext} for large `items` arrays. The
45
+ * Emporix backend handles only a limited number of items per request
46
+ * (production limit ~50), so this splits `input.items` into chunks and runs
47
+ * `matchByContext` with bounded concurrency.
48
+ *
49
+ * By default a failing chunk is skipped (its items are absent from the
50
+ * result) and `opts.onChunkError` is called once for it; pass
51
+ * `throwOnAnyChunkError: true` to reject on the first failure instead.
52
+ *
53
+ * **Result order is not guaranteed** — match entries back to your items by
54
+ * `priceId` / `itemRef.id`.
55
+ */
56
+ async matchByContextChunked(input, opts = {}, auth) {
57
+ const chunkSize = opts.chunkSize ?? 50;
58
+ const concurrency = opts.concurrency ?? 4;
59
+ if (chunkSize < 1) throw new Error("chunkSize must be >= 1");
60
+ if (concurrency < 1) throw new Error("concurrency must be >= 1");
61
+ const items = input.items ?? [];
62
+ if (items.length === 0) return [];
63
+ const chunks = [];
64
+ for (let i = 0; i < items.length; i += chunkSize) {
65
+ chunks.push({ ...input, items: items.slice(i, i + chunkSize) });
66
+ }
67
+ const results = new Array(chunks.length);
68
+ let cursor = 0;
69
+ const worker = async () => {
70
+ for (; ; ) {
71
+ const idx = cursor++;
72
+ const chunk = chunks[idx];
73
+ if (chunk === void 0) return;
74
+ try {
75
+ results[idx] = await this.matchByContext(chunk, auth);
76
+ } catch (err) {
77
+ if (opts.throwOnAnyChunkError) throw err;
78
+ results[idx] = [];
79
+ opts.onChunkError?.(err, idx);
80
+ }
81
+ }
82
+ };
83
+ const workerCount = Math.min(concurrency, chunks.length);
84
+ await Promise.all(Array.from({ length: workerCount }, () => worker()));
85
+ return results.flat();
86
+ }
87
+ };
88
+
89
+ export { PriceService };
90
+ //# sourceMappingURL=chunk-SCI7D74B.js.map
91
+ //# sourceMappingURL=chunk-SCI7D74B.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/services/price.ts"],"names":[],"mappings":"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","file":"chunk-SCI7D74B.js","sourcesContent":["import type { ClientContext } from \"../core/context\";\nimport type { AuthContext } from \"../core/auth\";\nimport { EmporixAuthError } from \"../core/errors\";\nimport type { Match, MatchByContext, MatchResponse } from \"../generated/price\";\n\n/** Session-context match request body (generated). */\nexport type PriceMatchByContextInput = MatchByContext;\n\n/** Explicit-context match request body (generated). */\nexport type PriceMatchInput = Match;\n\n/** A resolved price (full generated match-response schema). */\nexport type PriceMatch = MatchResponse;\n\n/** Options for {@link PriceService.matchByContextChunked}. */\nexport interface MatchByContextChunkedOptions {\n /** Items per request. Default 50. Must be >= 1. */\n chunkSize?: number;\n /** Maximum number of requests in flight at once. Default 4. Must be >= 1. */\n concurrency?: number;\n /** Invoked once per failed chunk (default mode only — not when throwing). */\n onChunkError?: (err: unknown, chunkIndex: number) => void;\n /** When true, the first failing chunk rejects the whole call. Default false. */\n throwOnAnyChunkError?: boolean;\n}\n\nconst ANON: AuthContext = { kind: \"anonymous\" };\nconst SERVICE: AuthContext = { kind: \"service\" };\n\nfunction requireContextAuth(auth: AuthContext | undefined): AuthContext {\n const a = auth ?? ANON;\n if (a.kind === \"anonymous\" || a.kind === \"customer\" || a.kind === \"raw\") return a;\n throw new EmporixAuthError(\n \"match-prices-by-context requires an anonymous, customer, or raw AuthContext\",\n );\n}\n\n/**\n * Price matching. The Cart service does not resolve prices — call this\n * explicitly before rendering money and again right before placing an order.\n * The SDK is stateless: it never caches or revalidates prices.\n */\nexport class PriceService {\n constructor(private readonly ctx: ClientContext) {}\n\n /**\n * Resolves prices using the session context bound to the bearer token\n * (currency/site/country were set at anonymous-login time). Default auth:\n * anonymous; pass a customer/raw context for personalized pricing.\n */\n async matchByContext(\n input: PriceMatchByContextInput,\n auth?: AuthContext,\n ): Promise<PriceMatch[]> {\n return this.ctx.http.request<PriceMatch[]>({\n method: \"POST\",\n path: `/price/${this.ctx.tenant}/match-prices-by-context`,\n auth: requireContextAuth(auth),\n body: input,\n });\n }\n\n /**\n * Resolves prices from an explicit context. Default auth: service\n * (requires `price.price_read` / `price.price_manage`).\n */\n async match(input: PriceMatchInput, auth: AuthContext = SERVICE): Promise<PriceMatch[]> {\n return this.ctx.http.request<PriceMatch[]>({\n method: \"POST\",\n path: `/price/${this.ctx.tenant}/match-prices`,\n auth,\n body: input,\n });\n }\n\n /**\n * Chunked variant of {@link matchByContext} for large `items` arrays. The\n * Emporix backend handles only a limited number of items per request\n * (production limit ~50), so this splits `input.items` into chunks and runs\n * `matchByContext` with bounded concurrency.\n *\n * By default a failing chunk is skipped (its items are absent from the\n * result) and `opts.onChunkError` is called once for it; pass\n * `throwOnAnyChunkError: true` to reject on the first failure instead.\n *\n * **Result order is not guaranteed** — match entries back to your items by\n * `priceId` / `itemRef.id`.\n */\n async matchByContextChunked(\n input: PriceMatchByContextInput,\n opts: MatchByContextChunkedOptions = {},\n auth?: AuthContext,\n ): Promise<PriceMatch[]> {\n const chunkSize = opts.chunkSize ?? 50;\n const concurrency = opts.concurrency ?? 4;\n if (chunkSize < 1) throw new Error(\"chunkSize must be >= 1\");\n if (concurrency < 1) throw new Error(\"concurrency must be >= 1\");\n\n const items = input.items ?? [];\n if (items.length === 0) return [];\n\n const chunks: PriceMatchByContextInput[] = [];\n for (let i = 0; i < items.length; i += chunkSize) {\n chunks.push({ ...input, items: items.slice(i, i + chunkSize) });\n }\n\n const results: PriceMatch[][] = new Array(chunks.length);\n let cursor = 0;\n\n const worker = async (): Promise<void> => {\n for (;;) {\n const idx = cursor++;\n const chunk = chunks[idx];\n if (chunk === undefined) return; // past the end\n try {\n results[idx] = await this.matchByContext(chunk, auth);\n } catch (err) {\n if (opts.throwOnAnyChunkError) throw err;\n results[idx] = [];\n opts.onChunkError?.(err, idx);\n }\n }\n };\n\n const workerCount = Math.min(concurrency, chunks.length);\n await Promise.all(Array.from({ length: workerCount }, () => worker()));\n\n return results.flat();\n }\n}\n"]}