@viu/emporix-sdk 1.0.0 → 2.1.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 (67) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +26 -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-JTJV7WUW.js} +25 -2
  16. package/dist/chunk-JTJV7WUW.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-WSXRUYBE.js +248 -0
  20. package/dist/chunk-WSXRUYBE.js.map +1 -0
  21. package/dist/chunk-XMEP74BU.js +60 -0
  22. package/dist/chunk-XMEP74BU.js.map +1 -0
  23. package/dist/companies.d.cts +1 -1
  24. package/dist/companies.d.ts +1 -1
  25. package/dist/contacts.d.cts +1 -1
  26. package/dist/contacts.d.ts +1 -1
  27. package/dist/{context-vKgzZ-5f.d.cts → context-CGuxSb56.d.cts} +16 -1
  28. package/dist/{context-vKgzZ-5f.d.ts → context-CGuxSb56.d.ts} +16 -1
  29. package/dist/customer-groups.d.cts +1 -1
  30. package/dist/customer-groups.d.ts +1 -1
  31. package/dist/customer.d.cts +1 -1
  32. package/dist/customer.d.ts +1 -1
  33. package/dist/index.cjs +311 -9
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.cts +8 -5
  36. package/dist/index.d.ts +8 -5
  37. package/dist/index.js +61 -6
  38. package/dist/index.js.map +1 -1
  39. package/dist/locations.d.cts +1 -1
  40. package/dist/locations.d.ts +1 -1
  41. package/dist/media.cjs +218 -9
  42. package/dist/media.cjs.map +1 -1
  43. package/dist/media.d.cts +103 -7
  44. package/dist/media.d.ts +103 -7
  45. package/dist/media.js +2 -1
  46. package/dist/orders.d.cts +1 -1
  47. package/dist/orders.d.ts +1 -1
  48. package/dist/payment.d.cts +1 -1
  49. package/dist/payment.d.ts +1 -1
  50. package/dist/price.cjs +44 -0
  51. package/dist/price.cjs.map +1 -1
  52. package/dist/price.d.cts +27 -2
  53. package/dist/price.d.ts +27 -2
  54. package/dist/price.js +1 -1
  55. package/dist/product.cjs +23 -0
  56. package/dist/product.cjs.map +1 -1
  57. package/dist/product.d.cts +18 -1
  58. package/dist/product.d.ts +18 -1
  59. package/dist/product.js +1 -1
  60. package/dist/segment.d.cts +1 -1
  61. package/dist/segment.d.ts +1 -1
  62. package/package.json +7 -2
  63. package/dist/chunk-F3WAQH2E.js +0 -120
  64. package/dist/chunk-F3WAQH2E.js.map +0 -1
  65. package/dist/chunk-HEO34WLO.js +0 -47
  66. package/dist/chunk-HEO34WLO.js.map +0 -1
  67. package/dist/chunk-QDABAMVN.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,61 @@
1
1
  # @viu/emporix-sdk
2
2
 
3
+ ## 2.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#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
8
+ `useAvailability` / `useAvailabilities` React hooks for site-aware product
9
+ availability. `getMany` uses the batch `POST .../search` endpoint and returns
10
+ results in input order; an opt-in `defaultAvailableOnNotFound` treats products
11
+ with no stock record as available. New `@viu/emporix-sdk/availability` subpath export.
12
+
13
+ - [#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:
14
+ split large match-prices-by-context requests into bounded-concurrency chunks
15
+ (default 50 items, 4 in flight) with per-chunk error handling.
16
+
17
+ - [#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
18
+ useVariantChildren React hook to resolve the VARIANT children of a
19
+ PARENT_VARIANT product without hand-building the search query.
20
+
21
+ ## 2.0.0
22
+
23
+ ### Major Changes
24
+
25
+ - [#52](https://github.com/viuteam/emporix-sdk/pull/52) [`26640fe`](https://github.com/viuteam/emporix-sdk/commit/26640fe281083e6ce0475a547e292ac82ba7d9bf) Thanks [@amnael1](https://github.com/amnael1)! - Add `client.media.download(assetId, auth?)` for retrieving asset content via `GET /media/{tenant}/assets/{id}/download`. Returns a discriminated union:
26
+ - `{ kind: "redirect", url }` for `PUBLIC` assets — the SDK captures the server's 30x `Location` header so the caller can redirect the user to the storage URL without proxying bytes.
27
+ - `{ kind: "bytes", data, etag?, contentType? }` for `PRIVATE` assets — the SDK returns the response body as an `ArrayBuffer`. When the server uses the OpenAPI-documented `text/plain` + base64 wire format, the SDK decodes it transparently; binary content types are passed through.
28
+
29
+ Also adds a low-level `HttpClient.requestRaw` escape hatch (used internally by `download`) for endpoints whose responses are not JSON. Auth resolution + timeout + logging are applied; the retry-on-5xx and 401-reauth-once paths from `request` are intentionally skipped (callers of `requestRaw` handle their own response shape).
30
+
31
+ Browser note: `download()` uses `redirect: "manual"` so it can observe `PUBLIC` redirect URLs. In Node this works as documented; in a browser the redirect `Location` is hidden by the fetch spec — there, `PUBLIC` downloads will surface as an error. Browser code should use the asset's `url` field (for `LINK` assets) or render `PUBLIC` `BLOB` assets via the storage URL directly.
32
+
33
+ **Breaking — `media.list()`** now returns `PaginatedItems<Asset>` instead of `Asset[]`. Callers must read `.items` for the array. The new shape includes `pageNumber`, `pageSize`, and the standard `hasNextPage` heuristic so paginated listings behave like every other list endpoint in the SDK. The previous shape silently truncated at the server-default page size (60); the new shape makes pagination explicit.
34
+
35
+ ```ts
36
+ // before
37
+ const assets = await client.media.list(); // Asset[], page 1 only
38
+ // after
39
+ const { items, hasNextPage } = await client.media.list({ pageSize: 100 });
40
+ ```
41
+
42
+ `client.media.listForProduct(productId)` shares the same envelope change.
43
+
44
+ **Breaking — `media.update()`** now takes a discriminated input matching `create()`:
45
+
46
+ ```ts
47
+ // before
48
+ await client.media.update(id, patch);
49
+ // after — JSON path (refIds, details, url, metadata)
50
+ await client.media.update(id, { kind: "json", body: patch });
51
+ // after — BLOB file-replacement (multipart, up to 10MB)
52
+ await client.media.update(id, { kind: "blob", file, body });
53
+ ```
54
+
55
+ The previous signature only supported the JSON path; the new BLOB path closes the gap where re-uploading bytes required `remove` + `create` (and lost the asset id). A new `client.media.replaceFile(assetId, { file, access, ... })` sugar wraps the common case.
56
+
57
+ New named type exports: `AssetUpdateBlobInput`, `AssetUpdateLinkInput`, `ListAssetsQuery`.
58
+
3
59
  ## 1.0.0
4
60
 
5
61
  ### Minor 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
 
@@ -70,6 +74,7 @@ await sdk.customers.me(auth.raw(externalJwt));
70
74
  | `carts.*` | — | explicit `customer` or `anonymous` |
71
75
  | `carts.merge` | — | `customer` |
72
76
  | `companies.*` / `contacts.*` / `locations.*` / `customerGroups.*` (B2B) | — | `customer` (reads need `*_read_own`; mutations need `*_manage`) |
77
+ | `media.*` (Asset CRUD + download) | `service` | `service` — server-only (`media.asset_read` / `media.asset_manage`); never call from the browser |
73
78
 
74
79
  `AuthContext` is **per call, never stored** — one client safely serves many
75
80
  concurrent shoppers (SSR/edge/multi-tenant). SDK-managed (`service`/`anonymous`)
@@ -98,11 +103,31 @@ customer's role lacks scope for surface as `EmporixInsufficientScopeError`
98
103
  (extends `EmporixForbiddenError`, carries `requiredScope`). See
99
104
  [`../../docs/b2b.md`](../../docs/b2b.md).
100
105
 
106
+ ## Media
107
+
108
+ `sdk.media` covers the full `/media/{tenant}/assets/*` surface: create
109
+ (`uploadFile` / `link` / `create`), list (paginated, `PaginatedItems<Asset>`),
110
+ get, update (JSON metadata or BLOB multipart file-replacement via the
111
+ `replaceFile` sugar), remove, and download (resolves to either a redirect
112
+ URL for `PUBLIC` assets or an `ArrayBuffer` for `PRIVATE`). All endpoints
113
+ require a server-only scope — every call defaults to a `service`
114
+ `AuthContext`. Storefronts read media via `product.productMedia` (denormalised
115
+ on the product) or the `useProductMedia(productId)` hook, not by calling
116
+ the Media service directly. See [`../../docs/media.md`](../../docs/media.md).
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
+
101
126
  ## Subpath exports
102
127
 
103
128
  `@viu/emporix-sdk` (everything) plus `./customer`, `./product`, `./category`,
104
129
  `./cart`, `./checkout`, `./payment`, `./price`, `./media`, `./segment`,
105
- `./companies`, `./contacts`, `./locations`, `./customer-groups` for tree-shaking.
130
+ `./companies`, `./contacts`, `./locations`, `./customer-groups`, `./availability` for tree-shaking.
106
131
 
107
132
  ## Authors
108
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-CGuxSb56.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-CGuxSb56.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-vKgzZ-5f.cjs';
1
+ import { C as ClientContext, b as AuthContext } from './context-CGuxSb56.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-vKgzZ-5f.js';
1
+ import { C as ClientContext, b as AuthContext } from './context-CGuxSb56.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-vKgzZ-5f.cjs';
1
+ import { C as ClientContext, b as AuthContext, P as PaginatedItems } from './context-CGuxSb56.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-vKgzZ-5f.js';
1
+ import { C as ClientContext, b as AuthContext, P as PaginatedItems } from './context-CGuxSb56.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-vKgzZ-5f.cjs';
1
+ import { C as ClientContext, b as AuthContext } from './context-CGuxSb56.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-vKgzZ-5f.js';
1
+ import { C as ClientContext, b as AuthContext } from './context-CGuxSb56.js';
2
2
 
3
3
  /**
4
4
  * Information about the customer's address.
@@ -82,8 +82,31 @@ var ProductService = class {
82
82
  );
83
83
  return pages.flat();
84
84
  }
85
+ /**
86
+ * Streams the VARIANT children of a PARENT_VARIANT product, page by page,
87
+ * via the search query `productType:VARIANT parentVariantId:<id>`. Default
88
+ * pageSize 200. The query syntax (space-separated fields = implicit AND) is
89
+ * encapsulated here so consumers don't build it themselves.
90
+ */
91
+ listVariantChildrenAll(parentVariantId, params = {}, auth = ANON) {
92
+ const pageSize = params.pageSize ?? 200;
93
+ const q = `productType:VARIANT parentVariantId:${parentVariantId}`;
94
+ return iterateAll((pageNumber) => this.search(q, { pageNumber, pageSize }, auth));
95
+ }
96
+ /**
97
+ * Resolves ALL VARIANT children of a PARENT_VARIANT product into a flat
98
+ * array (loads every page). Default pageSize 200. Returns `[]` when there are
99
+ * no children — never throws.
100
+ */
101
+ async listVariantChildren(parentVariantId, params = {}, auth = ANON) {
102
+ const out = [];
103
+ for await (const child of this.listVariantChildrenAll(parentVariantId, params, auth)) {
104
+ out.push(child);
105
+ }
106
+ return out;
107
+ }
85
108
  };
86
109
 
87
110
  export { ProductService };
88
- //# sourceMappingURL=chunk-QDABAMVN.js.map
89
- //# sourceMappingURL=chunk-QDABAMVN.js.map
111
+ //# sourceMappingURL=chunk-JTJV7WUW.js.map
112
+ //# sourceMappingURL=chunk-JTJV7WUW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/services/product.ts"],"names":[],"mappings":"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","file":"chunk-JTJV7WUW.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 * 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"]}