@viu/emporix-sdk 4.2.3 → 4.3.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # @viu/emporix-sdk
2
2
 
3
+ ## 4.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#387](https://github.com/viuteam/emporix-sdk/pull/387) [`46f6caa`](https://github.com/viuteam/emporix-sdk/commit/46f6caa6206d975a97a1aa829f93b6159bf8df2f) Thanks [@amnael1](https://github.com/amnael1)! - Align `media.update` and the helpers built on it with the Media spec.
8
+
9
+ - **`attachToProduct` / `detachFromProduct` follow the spec** (checked against the spec, not a live tenant). They sent a JSON `PUT` of `{ type, refIds }`, which the spec rejects for both asset types: a BLOB takes no JSON `PUT`, and a LINK's requires `access` and `url`. They now send a JSON Patch that touches only `refIds` (`add /refIds/-`, `replace /refIds`) and still resolve to the asset, with the change applied.
10
+ - **Type change — `update()` and `replaceFile()` resolve to `void`.** The `PUT` answers `204` with no body, so they always resolved to `undefined` at runtime despite promising an `Asset`. Code that read the result was already broken; it now fails to compile instead.
11
+ - **Type change — the JSON body of `update()` is `AssetUpdateLinkInput`.** The spec takes JSON only for a LINK asset. A BLOB is replaced with `{ kind: "blob", file, body }` (or `replaceFile()`), and its metadata alone is changed with `patch()`. `AssetUpdateInput` stays exported but is deprecated.
12
+
13
+ Both type changes only affect code that, per the spec, did not work before — hence a minor rather than a major.
14
+
15
+ - [#388](https://github.com/viuteam/emporix-sdk/pull/388) [`4d8685e`](https://github.com/viuteam/emporix-sdk/commit/4d8685eeb5138a8d1198f6257e8abb5ae35be236) Thanks [@amnael1](https://github.com/amnael1)! - Follow what the live Media API requires on top of the spec, found by running the facade against a tenant.
16
+
17
+ - **Type change — `update()` requires `metadata.version`** in both bodies (`AssetUpdateLinkInput`, `AssetUpdateBlobInput`). The spec marks it optional, but the API answers `400` «`metadata.version` is required for update» without it.
18
+ - **`replaceFile()` works without a `version`.** It sent none and got that `400`; it now reads the current version first. Pass `version` for optimistic locking (a stale one gets a `409`).
19
+ - **`attachToProduct` / `detachFromProduct` return the asset as read back after the patch.** The API answers `204` to a reference to a product that does not exist and drops it, so the locally computed result could claim an attachment that never happened.
20
+
21
+ ## 4.2.4
22
+
23
+ ### Patch Changes
24
+
25
+ - [#381](https://github.com/viuteam/emporix-sdk/pull/381) [`f151f7c`](https://github.com/viuteam/emporix-sdk/commit/f151f7ca11c32d1aa68355a5efbf36286e7b78ac) Thanks [@amnael1](https://github.com/amnael1)! - Export from the package root every type a public method takes or returns. These were reachable only through a subpath or not at all, so a call like `imports.getLicense()` returned an `ImportLicense` that could not be named: `ImportStats`, `ImportStatsQuery`, `ImportJobGroup`, `ImportHealthThresholds`, `ImportLicense`, `LegalEntitySearchInput`, `QuoteReasonsResource`, `CustomerTokenValidation`, `CartItemBatchEntry`, `CartItemsBatchResponse`, `SegmentGroup`, `SegmentGroupInput`, `SessionContextData`, `SiteAddress`, `SiteHomeBase`, and the AI sub-resources `AgenticCrudResource`, `JobsResource`, `TemplatesResource`, `LogsResource` and `AnalyticsResource`. Type-only; no runtime change.
26
+
3
27
  ## 4.2.3
4
28
 
5
29
  ### Patch Changes
package/README.md CHANGED
@@ -72,12 +72,14 @@ The sections below highlight the most-used services; per-service guides live in
72
72
 
73
73
  ## Searching by mixin (custom) fields
74
74
 
75
- `products.search`, `categories.search`, `orders.listMine(auth, { q })`,
76
- `customerAdmin.searchCustomers({ q })` and `vendors.searchVendors({ q })` accept a
77
- raw Emporix `q` string **or** a type-safe filter built with `mixinQuery` from
78
- [`@viu/emporix-mixins`](../mixins). The filter is entity-gated (a filter built for
79
- one entity is a compile error on another) and an `or()` filter is rejected on
80
- services that don't support `compoundLogicalQuery`.
75
+ `products.search`, `categories.search`, `categories.searchByQuery`,
76
+ `orders.listMine(auth, { q })`, `orders.listForLegalEntity(id, auth, { q })`,
77
+ `salesOrders.list(auth, { q })`, `customerAdmin.searchCustomers({ q })` and
78
+ `vendors.searchVendors({ q })` accept a raw Emporix `q` string **or** a type-safe
79
+ filter built with `mixinQuery` from [`@viu/emporix-mixins`](../mixins). The
80
+ filter is entity-gated (a filter built for one entity is a compile error on
81
+ another) and an `or()` filter is rejected on services that don't support
82
+ `compoundLogicalQuery`.
81
83
 
82
84
  ```ts
83
85
  import { mixinQuery } from "@viu/emporix-mixins";
@@ -96,7 +98,7 @@ See [`../../docs/mixin-search.md`](../../docs/mixin-search.md) for the capabilit
96
98
  | `tenant` (required) | — | lowercase, 3–16 chars, `^[a-z][a-z0-9]{2,15}$` |
97
99
  | `credentials` (required) | — | the object itself is required; which sets it holds is up to you |
98
100
  | `credentials.backend` | — | `{ clientId, secret, scope? }` — service token |
99
- | `credentials.storefront` | — | `{ clientId }` — anonymous token (no secret) |
101
+ | `credentials.storefront` | — | `{ clientId, context? }` — anonymous token (no secret). `context` holds `currency` / `siteCode` / `targetLocation`, bound to the anonymous token and needed by `prices.matchByContext`, plus `language` (`Accept-Language`); `sdk.setStorefrontContext()` switches it at runtime |
100
102
  | `credentials.custom` | — | `Record<name, { clientId, secret, scope? }>` |
101
103
  | `host` | `https://api.emporix.io` | |
102
104
  | `timeouts` | `{ connectMs: 10000, readMs: 60000 }` | |
@@ -104,6 +106,7 @@ See [`../../docs/mixin-search.md`](../../docs/mixin-search.md) for the capabilit
104
106
  | `cache` | `{ expirationBufferSeconds: 60, maxLifetimeSeconds: 3600 }` | token cache |
105
107
  | `logger` | console @ `warn` | `false`, a `Logger`, or `{ level, services, pretty, redact }` |
106
108
  | `tokenProvider` | built-in | inject for SSO/token-exchange |
109
+ | `fetch` | global `fetch` | replaces `fetch` for API requests; token requests and SSE streams keep the global one |
107
110
 
108
111
  `credentials: {}` is legal: a client that never mints its own token, for when the
109
112
  token comes from outside. The Managed Dashboard module in
@@ -115,8 +118,10 @@ anonymous browsing, both when you need both.
115
118
  ## AuthContext per method
116
119
 
117
120
  `auth.service(name?)`, `auth.anonymous()`, `auth.customer(token)`,
118
- `auth.raw(token)`. The last argument of every service method is the
119
- `AuthContext`; defaults below apply when omitted.
121
+ `auth.raw(token)`. The `AuthContext` is usually a service method's last
122
+ argument — some take options after it (`orders.listMine(auth, opts)`,
123
+ `availability.get(productId, siteCode, auth, opts)`); defaults below apply when
124
+ omitted.
120
125
 
121
126
  | Method | Default | Required kind |
122
127
  | --- | --- | --- |
@@ -124,7 +129,8 @@ anonymous browsing, both when you need both.
124
129
  | `customers.me` / `.update` / `.changePassword` / `.addresses.*` | — | `customer` or `raw` |
125
130
  | `customers.anonymous()` | — | obtains anonymous session (token ignored) |
126
131
  | `products.*` / `categories.*` (reads) | `anonymous` | — (pass `customer` for personalized pricing) |
127
- | `carts.*` | — | explicit `customer` or `anonymous` |
132
+ | `carts.*` (shopper methods) | — | explicit `customer` or `anonymous` |
133
+ | `carts.search` / `.delete` / `.update` | — | any kind, unguarded — `service` for admin use; the server enforces scope |
128
134
  | `carts.merge` | — | `customer` |
129
135
  | `carts.execute` ([command chains](../../docs/cart.md)) | — | `customer`, `anonymous`, or `service` with `cart.cart_manage` |
130
136
  | `companies.*` / `contacts.*` / `locations.*` / `customerGroups.*` (B2B) | — | `customer` (reads need `*_read_own`; mutations need `*_manage`) |
@@ -133,7 +139,9 @@ anonymous browsing, both when you need both.
133
139
  `AuthContext` is **per call, never stored** — one client safely serves many
134
140
  concurrent shoppers (SSR/edge/multi-tenant). SDK-managed (`service`/`anonymous`)
135
141
  401s refresh-and-retry once; caller-managed (`customer`/`raw`) 401s propagate as
136
- `EmporixAuthError`. Full details in [`../../docs/auth.md`](../../docs/auth.md).
142
+ `EmporixAuthError` — unless a refresher is registered with
143
+ `sdk.setCustomerTokenRefresher(...)`, which refreshes and retries a `customer`
144
+ 401 once. Full details in [`../../docs/auth.md`](../../docs/auth.md).
137
145
 
138
146
  ## Logging
139
147
 
@@ -161,11 +169,20 @@ customer's role lacks scope for surface as `EmporixInsufficientScopeError`
161
169
  ## Media
162
170
 
163
171
  `sdk.media` covers the full `/media/{tenant}/assets/*` surface: create
164
- (`uploadFile` / `link` / `create`), list (paginated, `PaginatedItems<Asset>`),
165
- get, update (JSON metadata or BLOB multipart file-replacement via the
166
- `replaceFile` sugar), remove, and download (resolves to either a redirect
167
- URL for `PUBLIC` assets or an `ArrayBuffer` for `PRIVATE`). All endpoints
168
- require a server-only scope — every call defaults to a `service`
172
+ (`uploadFile` / `link` / `create`, or `startUploadSession` for a direct upload
173
+ to storage once Emporix Support enables it for the tenant), list (paginated,
174
+ `PaginatedItems<Asset>`), get, update (a `PUT` that needs `metadata.version` and
175
+ resolves to nothing: JSON for a LINK asset, multipart file replacement for a
176
+ BLOB, also via the `replaceFile` sugar, which reads the version when you pass
177
+ none), `patch` (JSON Patch — the way to change a BLOB's metadata without
178
+ re-uploading the file; `attachToProduct` / `detachFromProduct` use it and return
179
+ the asset as read back, since a reference to an unknown product is dropped
180
+ silently), remove,
181
+ `getDownloadUrl` (a storage URL with no size limit, which Emporix recommends for
182
+ every download), and download (resolves to either a redirect URL for `PUBLIC`
183
+ assets or an `ArrayBuffer` for `PRIVATE`; above the streaming limit, 30 MB by
184
+ default, it fails with a `413` and only `getDownloadUrl` serves the file). All
185
+ endpoints require a server-only scope — every call defaults to a `service`
169
186
  `AuthContext`. Storefronts read media via `product.productMedia` (denormalised
170
187
  on the product) or the `useProductMedia(productId)` hook, not by calling
171
188
  the Media service directly. See [`../../docs/media.md`](../../docs/media.md).
@@ -186,13 +203,28 @@ stock record. There is no restock-date field.
186
203
  ## Subpath exports
187
204
 
188
205
  `@viu/emporix-sdk` (the package root) re-exports **everything** — every service,
189
- type, and helper. Tree-shakeable subpaths are published for the high-traffic
206
+ type, and helper. Subpaths are published for the high-traffic
190
207
  services: `./customer`, `./product`, `./category`, `./cart`, `./checkout`,
191
208
  `./payment`, `./price`, `./media`, `./segment`, `./companies`, `./contacts`,
192
209
  `./locations`, `./customer-groups`, `./orders`, `./availability`. All other
193
210
  services (Tax, Coupon, RewardPoints, Shipping, Returns, Catalog, Vendor,
194
211
  CustomerAdmin, Approval, …) are reached from the package root.
195
212
 
213
+ A subpath import alone does not shrink a bundle: `EmporixClient` imports every
214
+ service. When bundle size matters, build the client with `createEmporixClient`,
215
+ which instantiates only the service classes you pass (`segments` also needs
216
+ `products` and `categories` in the map):
217
+
218
+ ```ts
219
+ import { createEmporixClient, ProductService, CartService } from "@viu/emporix-sdk";
220
+
221
+ const sdk = createEmporixClient(
222
+ { tenant: "mytenant", credentials: { storefront: { clientId: "..." } } },
223
+ { products: ProductService, carts: CartService },
224
+ );
225
+ const products = await sdk.products.list(); // no other service is bundled
226
+ ```
227
+
196
228
  ## Changelog
197
229
 
198
230
  npmjs.com renders only this README, never a changelog — the registry has no field
@@ -70,37 +70,31 @@ var MediaService = class {
70
70
  });
71
71
  }
72
72
  /**
73
- * Update an asset. The input is a discriminated union mirroring
74
- * {@link create}:
75
- * - `{ kind: "json", body }` — metadata-only patch (BLOB or LINK).
76
- * Sends `application/json`. Used for `refIds`, `details`, `metadata`,
77
- * or `url` changes.
78
- * - `{ kind: "blob", file, body }` — replaces the BLOB file content
79
- * (max 30 MB) AND patches metadata in the same request. Sends
80
- * `multipart/form-data`.
73
+ * Replace an asset (`PUT`, HTTP 204 — nothing is returned). The spec takes one
74
+ * body per asset type, mirrored by the discriminated input:
75
+ * - `{ kind: "json", body }` — a LINK asset, sent as `application/json`. The
76
+ * body is the whole asset: `type: "LINK"`, `access` and `url` are required.
77
+ * - `{ kind: "blob", file, body }` — a BLOB asset, sent as
78
+ * `multipart/form-data`: the file (max 30 MB) and the asset body, both
79
+ * required, so this always replaces the bytes.
81
80
  *
82
- * `type` and `access` are immutable per Emporix — they must match the
83
- * existing asset's values. Optimistic-locking is via `body.metadata.version`;
84
- * the server returns 409 Conflict on a stale version.
81
+ * A BLOB's metadata alone — `refIds`, `details`, mixins — goes through
82
+ * {@link patch}. Earlier versions also took a BLOB as the JSON body and
83
+ * promised the updated asset in return; the spec allows neither.
84
+ *
85
+ * `type` and `access` are immutable. Optimistic locking is via
86
+ * `body.metadata.version`; a stale one gets a 409.
85
87
  */
86
88
  async update(assetId, input, auth = SERVICE) {
89
+ const path = `${this.base()}/${encodeURIComponent(assetId)}`;
87
90
  if (input.kind === "blob") {
88
91
  const fd = new FormData();
89
92
  fd.set("file", input.file);
90
93
  fd.set("body", JSON.stringify(input.body));
91
- return this.ctx.http.request({
92
- method: "PUT",
93
- path: `${this.base()}/${assetId}`,
94
- auth,
95
- body: fd
96
- });
94
+ await this.ctx.http.request({ method: "PUT", path, auth, body: fd });
95
+ return;
97
96
  }
98
- return this.ctx.http.request({
99
- method: "PUT",
100
- path: `${this.base()}/${assetId}`,
101
- auth,
102
- body: input.body
103
- });
97
+ await this.ctx.http.request({ method: "PUT", path, auth, body: input.body });
104
98
  }
105
99
  /**
106
100
  * Partially update an asset with an RFC-6902 JSON-Patch op-array
@@ -283,12 +277,17 @@ var MediaService = class {
283
277
  }
284
278
  /**
285
279
  * Replace the file content of an existing BLOB asset. Sugar over
286
- * {@link update} with `kind: "blob"`. `access` is required because the
287
- * field is immutable and the server validates that the patch matches.
288
- * Pass `version` from `asset.metadata.version` if you want optimistic
289
- * locking (recommended when concurrent writers are possible).
280
+ * {@link update} with `kind: "blob"`, and like it resolves to nothing — the
281
+ * PUT answers 204. `access` is required because the field is immutable and
282
+ * the server validates that the body matches.
283
+ *
284
+ * Pass `version` from `asset.metadata.version` for optimistic locking
285
+ * (recommended when concurrent writers are possible) — a stale one gets a 409.
286
+ * Without it the current version is read first, because the live API rejects
287
+ * an update that carries none; the write then wins over any in between.
290
288
  */
291
289
  async replaceFile(assetId, input, auth = SERVICE) {
290
+ const version = input.version ?? (await this.get(assetId, auth)).metadata?.version ?? 1;
292
291
  const body = {
293
292
  type: "BLOB",
294
293
  access: input.access,
@@ -298,7 +297,7 @@ var MediaService = class {
298
297
  ...input.mimeType ? { mimeType: input.mimeType } : {}
299
298
  }
300
299
  } : {},
301
- ...input.version !== void 0 ? { metadata: { version: input.version } } : {}
300
+ metadata: { version }
302
301
  };
303
302
  return this.update(assetId, { kind: "blob", file: input.file, body }, auth);
304
303
  }
@@ -312,23 +311,42 @@ var MediaService = class {
312
311
  };
313
312
  return this.create({ kind: "link", body }, auth);
314
313
  }
315
- /** Idempotently add a PRODUCT refId to an asset. */
314
+ /**
315
+ * Idempotently add a PRODUCT refId to an asset, through a JSON Patch that
316
+ * appends the one reference, so it works for BLOB and LINK alike and leaves
317
+ * the rest of the asset alone.
318
+ *
319
+ * Resolves to the asset as read back after the patch, because Emporix answers
320
+ * 204 to a reference to a product that does not exist and drops it — check
321
+ * `refIds` rather than trusting the call. Only a PUBLIC asset takes a product
322
+ * reference; a PRIVATE one answers 400.
323
+ *
324
+ * Earlier versions sent a JSON PUT of `{ type, refIds }`, which the spec
325
+ * rejects for both types: a BLOB takes no JSON PUT, and a LINK's requires
326
+ * `access` and `url`.
327
+ */
316
328
  async attachToProduct(assetId, productId, auth = SERVICE) {
317
329
  const a = await this.get(assetId, auth);
318
330
  const refIds = a.refIds ?? [];
319
331
  if (refIds.some((r) => isProductRef(r, productId))) return a;
320
- const next = [...refIds, { type: "PRODUCT", id: productId }];
321
- const patch = { type: a.type, refIds: next };
322
- return this.update(assetId, { kind: "json", body: patch }, auth);
332
+ const ref = { type: "PRODUCT", id: productId };
333
+ const op = a.refIds === void 0 ? { op: "add", path: "/refIds", value: [ref] } : { op: "add", path: "/refIds/-", value: ref };
334
+ await this.patch(assetId, [op], auth);
335
+ return this.get(assetId, auth);
323
336
  }
324
- /** Remove a PRODUCT refId from an asset (no-op if absent). */
337
+ /**
338
+ * Remove a PRODUCT refId from an asset (no-op if absent), through a JSON Patch
339
+ * that replaces the references and nothing else. Resolves to the asset as
340
+ * read back after the patch, like {@link attachToProduct}. Earlier versions
341
+ * sent a spec-invalid JSON PUT, as that one did.
342
+ */
325
343
  async detachFromProduct(assetId, productId, auth = SERVICE) {
326
344
  const a = await this.get(assetId, auth);
327
345
  const refIds = a.refIds ?? [];
328
346
  const next = refIds.filter((r) => !isProductRef(r, productId));
329
347
  if (next.length === refIds.length) return a;
330
- const patch = { type: a.type, refIds: next };
331
- return this.update(assetId, { kind: "json", body: patch }, auth);
348
+ await this.patch(assetId, [{ op: "replace", path: "/refIds", value: next }], auth);
349
+ return this.get(assetId, auth);
332
350
  }
333
351
  /** Convenience: list assets attached to a product (server-side filter). */
334
352
  async listForProduct(productId, auth = SERVICE) {
@@ -338,5 +356,5 @@ var MediaService = class {
338
356
  var _internalMedia = { isProductRef };
339
357
 
340
358
  export { MediaService, _internalMedia };
341
- //# sourceMappingURL=chunk-5MPL4XBD.js.map
342
- //# sourceMappingURL=chunk-5MPL4XBD.js.map
359
+ //# sourceMappingURL=chunk-NFSO7WUA.js.map
360
+ //# sourceMappingURL=chunk-NFSO7WUA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/services/media.ts"],"names":["text"],"mappings":"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","file":"chunk-NFSO7WUA.js","sourcesContent":["import type { ClientContext, PaginatedItems } from \"../core/context\";\nimport { requestPage } from \"../core/paged\";\nimport type { AuthContext } from \"../core/auth\";\nimport { EmporixError, errorFromResponse } from \"../core/errors\";\nimport type {\n AssetCreateBlob,\n AssetCreateLink,\n AssetUpdateBlob,\n AssetUpdateLink,\n DownloadUrl,\n GetAsset,\n GetMediaRetrieveDownloadUrlData,\n MetadataUpdate,\n PatchOperation,\n RefId,\n UploadSession,\n UploadSessionRequest,\n} from \"../generated/media\";\n\n/**\n * The spec marks `metadata` optional on both update bodies; the live API rejects\n * an update without it — «`metadata.version` is required for update» (measured\n * 2026-10-06 on BLOB and LINK assets). Required here, so leaving it out is a\n * compile error rather than a 400.\n */\ntype Versioned<T> = Omit<T, \"metadata\"> & { metadata: MetadataUpdate };\n\n/** Generated media types (caller sends the exact wire shape). */\nexport type AssetCreateBlobInput = AssetCreateBlob;\nexport type AssetCreateLinkInput = AssetCreateLink;\nexport type AssetUpdateBlobInput = Versioned<AssetUpdateBlob>;\nexport type AssetUpdateLinkInput = Versioned<AssetUpdateLink>;\n/**\n * @deprecated `update()` takes {@link AssetUpdateLinkInput} as its JSON body and\n * {@link AssetUpdateBlobInput} next to a file; a BLOB is never sent as JSON.\n */\nexport type AssetUpdateInput = AssetUpdateBlob | AssetUpdateLink;\nexport type Asset = GetAsset;\nexport type AssetRefId = RefId;\n/** Partial-update body (`PATCH /assets/{id}`) — an RFC-6902 JSON-Patch op-array. */\nexport type AssetPatch = PatchOperation[];\n/** Result of {@link MediaService.getDownloadUrl} — `{ provider, url, expiresAt? }`. */\nexport type AssetDownloadUrl = DownloadUrl;\n/** Query for {@link MediaService.getDownloadUrl} — `{ disposition? }`. */\nexport type AssetDownloadUrlQuery = NonNullable<GetMediaRetrieveDownloadUrlData[\"query\"]>;\n/** Body for {@link MediaService.startUploadSession}: a BLOB create payload plus `uploadType`. */\nexport type AssetUploadSessionInput = UploadSessionRequest;\n/** The storage instruction {@link MediaService.startUploadSession} returns. */\nexport type AssetUploadSession = UploadSession;\n\n/**\n * Filter / pagination options for {@link MediaService.list}. The explicit\n * fields are typed for autocomplete; the index signature stays open so\n * Emporix `q`-syntax filters like `\"refIds.id\"` can be passed verbatim.\n */\nexport interface ListAssetsQuery {\n pageNumber?: number;\n pageSize?: number;\n /** Emporix sort syntax, e.g. `\"name,metadata.createdAt:desc\"`. */\n sort?: string;\n /** Emporix `q`-syntax filter, e.g. `\"name:hero\"`. */\n q?: string;\n /**\n * Ask for `X-Total-Count`. An SDK-side flag, not an Emporix query parameter:\n * it becomes a request header.\n */\n totalCount?: boolean;\n [key: string]: string | number | boolean | undefined;\n}\n\n/**\n * Result of {@link MediaService.download}. The endpoint's behaviour depends\n * on the asset's `access`:\n * - `PUBLIC` assets respond with a 30x redirect whose `Location` header\n * carries the externally-cacheable storage URL — useful when the\n * storefront wants to send the user there directly.\n * - `PRIVATE` assets respond with the raw bytes (plus an `ETag` header\n * for caching). The file lives in Google Cloud Storage and is reachable\n * without the service token only through a signed URL from\n * {@link MediaService.getDownloadUrl}.\n */\nexport type DownloadResult =\n | { kind: \"redirect\"; url: string }\n | {\n kind: \"bytes\";\n data: ArrayBuffer;\n etag?: string;\n contentType?: string;\n };\n\nconst SERVICE: AuthContext = { kind: \"service\" };\n\nfunction isProductRef(r: AssetRefId, productId: string): boolean {\n return r.type === \"PRODUCT\" && r.id === productId;\n}\n\n/**\n * Media assets (BLOB/LINK). All endpoints require a backend-only scope\n * (`media.asset_manage` / `media.asset_read`) — default auth: service.\n */\nexport class MediaService {\n static readonly channel = \"media\" as const;\n constructor(private readonly ctx: ClientContext) {}\n\n private base(): string {\n return `/media/${this.ctx.tenant}/assets`;\n }\n\n /**\n * Create an asset. BLOB uploads via multipart (max 30 MB); LINK via JSON.\n * {@link startUploadSession} sends the file straight to storage instead.\n */\n async create(\n input:\n | { kind: \"blob\"; file: Blob; body: AssetCreateBlobInput }\n | { kind: \"link\"; body: AssetCreateLinkInput },\n auth: AuthContext = SERVICE,\n ): Promise<{ id: string }> {\n if (input.kind === \"blob\") {\n const fd = new FormData();\n fd.set(\"file\", input.file);\n fd.set(\"body\", JSON.stringify(input.body));\n return this.ctx.http.request<{ id: string }>({\n method: \"POST\",\n path: this.base(),\n auth,\n body: fd,\n });\n }\n return this.ctx.http.request<{ id: string }>({\n method: \"POST\",\n path: this.base(),\n auth,\n body: input.body,\n });\n }\n\n /**\n * List assets, wrapped in the shared {@link PaginatedItems} envelope so\n * callers can iterate over pages consistently with the rest of the SDK.\n * `hasNextPage` is the standard SDK heuristic: `true` when the returned\n * page is full (`items.length === requested pageSize`). Pagination\n * defaults match Emporix's server defaults (`pageNumber: 1`,\n * `pageSize: 60`) so an empty `query` produces stable expectations.\n */\n async list(\n query: ListAssetsQuery = {},\n auth: AuthContext = SERVICE,\n ): Promise<PaginatedItems<Asset>> {\n const pageNumber = query.pageNumber ?? 1;\n const pageSize = query.pageSize ?? 60;\n // Destructured out because the rest of `query` is spread into the query\n // string: `totalCount` is an SDK-side flag that becomes a request header,\n // and would otherwise ride along as a stray `?totalCount=true`.\n const { totalCount, ...rest } = query;\n return requestPage<Asset>(\n this.ctx.http,\n {\n method: \"GET\",\n path: this.base(),\n auth,\n query: { ...rest, pageNumber, pageSize },\n },\n { pageNumber, pageSize, ...(totalCount === undefined ? {} : { totalCount }) },\n );\n }\n\n /** Fetch an asset by id. */\n async get(assetId: string, auth: AuthContext = SERVICE): Promise<Asset> {\n return this.ctx.http.request<Asset>({\n method: \"GET\",\n path: `${this.base()}/${assetId}`,\n auth,\n });\n }\n\n /**\n * Replace an asset (`PUT`, HTTP 204 — nothing is returned). The spec takes one\n * body per asset type, mirrored by the discriminated input:\n * - `{ kind: \"json\", body }` — a LINK asset, sent as `application/json`. The\n * body is the whole asset: `type: \"LINK\"`, `access` and `url` are required.\n * - `{ kind: \"blob\", file, body }` — a BLOB asset, sent as\n * `multipart/form-data`: the file (max 30 MB) and the asset body, both\n * required, so this always replaces the bytes.\n *\n * A BLOB's metadata alone — `refIds`, `details`, mixins — goes through\n * {@link patch}. Earlier versions also took a BLOB as the JSON body and\n * promised the updated asset in return; the spec allows neither.\n *\n * `type` and `access` are immutable. Optimistic locking is via\n * `body.metadata.version`; a stale one gets a 409.\n */\n async update(\n assetId: string,\n input:\n | { kind: \"json\"; body: AssetUpdateLinkInput }\n | { kind: \"blob\"; file: Blob; body: AssetUpdateBlobInput },\n auth: AuthContext = SERVICE,\n ): Promise<void> {\n const path = `${this.base()}/${encodeURIComponent(assetId)}`;\n if (input.kind === \"blob\") {\n const fd = new FormData();\n fd.set(\"file\", input.file);\n fd.set(\"body\", JSON.stringify(input.body));\n await this.ctx.http.request<void>({ method: \"PUT\", path, auth, body: fd });\n return;\n }\n await this.ctx.http.request<void>({ method: \"PUT\", path, auth, body: input.body });\n }\n\n /**\n * Partially update an asset with an RFC-6902 JSON-Patch op-array\n * (`PATCH /assets/{id}`, HTTP 204 — nothing is returned).\n *\n * This is the way to change one field without resending the whole asset, and\n * the only way to touch a `BLOB` asset's metadata **without re-uploading the\n * file** — {@link update} in `blob` mode always replaces the bytes.\n *\n * Append to an array with a path ending in `/-`, which preserves the existing\n * entries:\n *\n * ```ts\n * await client.media.patch(assetId, [\n * { op: \"add\", path: \"/refIds/-\", value: { id: \"product1\", type: \"PRODUCT\" } },\n * ]);\n * ```\n *\n * `type` and `access` are immutable here too, as with {@link update}. Only\n * `add`, `remove` and `replace` are accepted, and optimistic locking still\n * applies — a stale `metadata.version` gets a 409.\n */\n async patch(assetId: string, ops: AssetPatch, auth: AuthContext = SERVICE): Promise<void> {\n await this.ctx.http.request<void>({\n method: \"PATCH\",\n path: `${this.base()}/${encodeURIComponent(assetId)}`,\n auth,\n body: ops,\n });\n }\n\n /** Remove an asset. */\n async remove(assetId: string, auth: AuthContext = SERVICE): Promise<void> {\n await this.ctx.http.request<void>({\n method: \"DELETE\",\n path: `${this.base()}/${assetId}`,\n auth,\n });\n }\n\n /**\n * Download an asset by id. Returns a discriminated union:\n * - `{ kind: \"redirect\", url }` for `PUBLIC` assets (server-side 30x with\n * the storage URL in `Location`).\n * - `{ kind: \"bytes\", data }` for `PRIVATE` assets (server-side 200 with\n * the asset bytes; `etag` + `contentType` headers are exposed for\n * caching). When the server reports `text/plain` Content-Type (the\n * OpenAPI-documented format), the SDK decodes the base64 stream into\n * an `ArrayBuffer` transparently; binary Content-Types are passed\n * through verbatim.\n *\n * Emporix recommends {@link getDownloadUrl} for every download: here the file\n * passes through the Media API. A `413` means the asset's known size exceeds\n * the streaming limit (30 MB by default) — only {@link getDownloadUrl} can\n * serve it. A `409` means the asset is still `PENDING` from a direct upload\n * whose file has not reached storage.\n *\n * Implementation note: uses `redirect: \"manual\"` so the redirect-location\n * is observable. In Node.js this works; in a browser the redirect Location\n * is intentionally hidden by fetch — there, `PUBLIC` downloads will\n * surface as an opaque-redirect and the SDK throws. Browser code should\n * use the asset's `url` field from `get()` for `LINK` assets, or the\n * direct storage URL for `PUBLIC` `BLOB` assets (typically delivered via\n * an `<img>` tag rather than `download()`).\n */\n async download(\n assetId: string,\n auth: AuthContext = SERVICE,\n ): Promise<DownloadResult> {\n const path = `${this.base()}/${assetId}/download`;\n const res = await this.ctx.http.requestRaw(\n { method: \"GET\", path, auth },\n { redirect: \"manual\" },\n );\n\n // PUBLIC: server-side redirect — capture Location.\n if (res.status >= 300 && res.status < 400) {\n const url = res.headers.get(\"location\");\n if (!url) {\n throw new EmporixError(\n `media.download: ${res.status} response without a Location header`,\n res.status,\n );\n }\n return { kind: \"redirect\", url };\n }\n\n // PRIVATE: bytes.\n if (res.ok) {\n const etag = res.headers.get(\"etag\") ?? undefined;\n const contentType = res.headers.get(\"content-type\") ?? undefined;\n let data: ArrayBuffer;\n if (contentType?.startsWith(\"text/plain\")) {\n // Per OpenAPI spec the server returns the byte stream as a\n // base64-encoded text/plain body. Decode once into raw bytes\n // so callers always see an ArrayBuffer.\n const text = await res.text();\n const bin = atob(text);\n const arr = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) arr[i] = bin.charCodeAt(i);\n data = arr.buffer;\n } else {\n data = await res.arrayBuffer();\n }\n return {\n kind: \"bytes\",\n data,\n ...(etag !== undefined ? { etag } : {}),\n ...(contentType !== undefined ? { contentType } : {}),\n };\n }\n\n // Error: surface via the standard EmporixError hierarchy.\n const text = await res.text();\n let parsed: unknown = undefined;\n if (text) {\n try {\n parsed = JSON.parse(text);\n } catch {\n parsed = text;\n }\n }\n throw errorFromResponse(res.status, `GET ${path} → ${res.status}`, parsed);\n }\n\n /**\n * Get a URL that serves the stored file straight from storage\n * (`GET /assets/{assetId}/download-url`). Emporix recommends it for every\n * download: the file never passes through the Media API, there is no size\n * limit, and the URL can be handed to a browser as it is.\n *\n * - `PRIVATE` BLOB → `provider: \"GCS\"`, a signed Google Cloud Storage URL that\n * works without an Emporix token until `expiresAt` (15 minutes by default).\n * - `PUBLIC` BLOB → `provider: \"CLOUDINARY\"`, the permanent URL.\n * - `LINK` → `provider: \"LINK\"`, the stored URL.\n *\n * `disposition` (`attachment` by default, or `inline`) affects private URLs\n * only. Unlike {@link startUploadSession}, this works whether or not direct\n * upload is enabled for the tenant. A `409` means a direct upload is still\n * `PENDING` and its file has not reached storage; once it has, this call\n * completes the asset before answering.\n */\n async getDownloadUrl(\n assetId: string,\n query: AssetDownloadUrlQuery = {},\n auth: AuthContext = SERVICE,\n ): Promise<AssetDownloadUrl> {\n return this.ctx.http.request<AssetDownloadUrl>({\n method: \"GET\",\n path: `${this.base()}/${encodeURIComponent(assetId)}/download-url`,\n auth,\n query: { ...query },\n });\n }\n\n /**\n * Start a direct upload of a `BLOB` asset (`POST /assets/upload-session`,\n * `201`). Emporix creates the asset as `PENDING` — `id` is its id — and\n * returns where to send the file. **This method does not send it**:\n *\n * - `upload.method === \"PUT\"` (a `PRIVATE` asset, `uploadType: \"put\"` or\n * omitted): `PUT` the file to `upload.url` with every `upload.headers` entry.\n * - `upload.method === \"POST\"` (every `PUBLIC` asset, or `uploadType: \"form\"`):\n * a multipart `POST` to `upload.url` with every `upload.fields` entry\n * unchanged and the file part last.\n *\n * That request goes to Google Cloud Storage or Cloudinary, not to Emporix, so\n * it must not carry the Emporix token — which is also why a browser can make\n * it. The asset turns `READY` once storage confirms; until then, calls that\n * need the file answer `409`. The instruction is valid for 15 minutes (GCS) or\n * an hour (Cloudinary), and an unused session expires at `expiresAt`.\n *\n * Direct upload is enabled per tenant by Emporix Support. Without it the call\n * throws an `EmporixForbiddenError` whose `body.message` is `direct upload is\n * not enabled for this tenant`, and nothing is created — the same status as a\n * missing scope, so read the message.\n */\n async startUploadSession(\n input: AssetUploadSessionInput,\n auth: AuthContext = SERVICE,\n ): Promise<AssetUploadSession> {\n return this.ctx.http.request<AssetUploadSession>({\n method: \"POST\",\n path: `${this.base()}/upload-session`,\n auth,\n body: input,\n });\n }\n\n /** Multipart upload sugar: builds `AssetCreateBlob` from input. */\n async uploadFile(\n input: {\n file: Blob;\n productId?: string;\n filename?: string;\n mimeType?: string;\n access?: \"PUBLIC\" | \"PRIVATE\";\n },\n auth: AuthContext = SERVICE,\n ): Promise<{ id: string }> {\n const body: AssetCreateBlobInput = {\n type: \"BLOB\",\n access: input.access ?? \"PUBLIC\",\n ...(input.productId\n ? { refIds: [{ type: \"PRODUCT\", id: input.productId }] }\n : {}),\n ...(input.filename || input.mimeType\n ? {\n details: {\n ...(input.filename ? { filename: input.filename } : {}),\n ...(input.mimeType ? { mimeType: input.mimeType } : {}),\n },\n }\n : {}),\n };\n return this.create({ kind: \"blob\", file: input.file, body }, auth);\n }\n\n /**\n * Replace the file content of an existing BLOB asset. Sugar over\n * {@link update} with `kind: \"blob\"`, and like it resolves to nothing — the\n * PUT answers 204. `access` is required because the field is immutable and\n * the server validates that the body matches.\n *\n * Pass `version` from `asset.metadata.version` for optimistic locking\n * (recommended when concurrent writers are possible) — a stale one gets a 409.\n * Without it the current version is read first, because the live API rejects\n * an update that carries none; the write then wins over any in between.\n */\n async replaceFile(\n assetId: string,\n input: {\n file: Blob;\n access: \"PUBLIC\" | \"PRIVATE\";\n filename?: string;\n mimeType?: string;\n version?: number;\n },\n auth: AuthContext = SERVICE,\n ): Promise<void> {\n const version =\n input.version ?? (await this.get(assetId, auth)).metadata?.version ?? 1;\n const body: AssetUpdateBlobInput = {\n type: \"BLOB\",\n access: input.access,\n ...(input.filename || input.mimeType\n ? {\n details: {\n ...(input.filename ? { filename: input.filename } : {}),\n ...(input.mimeType ? { mimeType: input.mimeType } : {}),\n },\n }\n : {}),\n metadata: { version },\n };\n return this.update(assetId, { kind: \"blob\", file: input.file, body }, auth);\n }\n\n /** External-URL sugar: builds `AssetCreateLink`. */\n async link(\n input: { url: string; productId?: string; access?: \"PUBLIC\" | \"PRIVATE\" },\n auth: AuthContext = SERVICE,\n ): Promise<{ id: string }> {\n const body: AssetCreateLinkInput = {\n type: \"LINK\",\n access: input.access ?? \"PUBLIC\",\n url: input.url,\n ...(input.productId\n ? { refIds: [{ type: \"PRODUCT\", id: input.productId }] }\n : {}),\n };\n return this.create({ kind: \"link\", body }, auth);\n }\n\n /**\n * Idempotently add a PRODUCT refId to an asset, through a JSON Patch that\n * appends the one reference, so it works for BLOB and LINK alike and leaves\n * the rest of the asset alone.\n *\n * Resolves to the asset as read back after the patch, because Emporix answers\n * 204 to a reference to a product that does not exist and drops it — check\n * `refIds` rather than trusting the call. Only a PUBLIC asset takes a product\n * reference; a PRIVATE one answers 400.\n *\n * Earlier versions sent a JSON PUT of `{ type, refIds }`, which the spec\n * rejects for both types: a BLOB takes no JSON PUT, and a LINK's requires\n * `access` and `url`.\n */\n async attachToProduct(\n assetId: string,\n productId: string,\n auth: AuthContext = SERVICE,\n ): Promise<Asset> {\n const a = await this.get(assetId, auth);\n const refIds: AssetRefId[] = a.refIds ?? [];\n if (refIds.some((r) => isProductRef(r, productId))) return a;\n const ref: AssetRefId = { type: \"PRODUCT\", id: productId };\n // `/refIds/-` appends to an existing array only (RFC 6902).\n const op =\n a.refIds === undefined\n ? { op: \"add\" as const, path: \"/refIds\", value: [ref] }\n : { op: \"add\" as const, path: \"/refIds/-\", value: ref };\n await this.patch(assetId, [op], auth);\n return this.get(assetId, auth);\n }\n\n /**\n * Remove a PRODUCT refId from an asset (no-op if absent), through a JSON Patch\n * that replaces the references and nothing else. Resolves to the asset as\n * read back after the patch, like {@link attachToProduct}. Earlier versions\n * sent a spec-invalid JSON PUT, as that one did.\n */\n async detachFromProduct(\n assetId: string,\n productId: string,\n auth: AuthContext = SERVICE,\n ): Promise<Asset> {\n const a = await this.get(assetId, auth);\n const refIds: AssetRefId[] = a.refIds ?? [];\n const next = refIds.filter((r) => !isProductRef(r, productId));\n if (next.length === refIds.length) return a;\n await this.patch(assetId, [{ op: \"replace\", path: \"/refIds\", value: next }], auth);\n return this.get(assetId, auth);\n }\n\n /** Convenience: list assets attached to a product (server-side filter). */\n async listForProduct(\n productId: string,\n auth: AuthContext = SERVICE,\n ): Promise<PaginatedItems<Asset>> {\n return this.list({ \"refIds.id\": productId }, auth);\n }\n}\n\n// Re-export so consumers can `import { isProductRef } from \"@viu/emporix-sdk/media\"`?\n// Internal helper — kept module-private; convenience methods in Task 4 use it.\nexport const _internalMedia = { isProductRef };\n"]}
@@ -50,4 +50,4 @@ declare class CompaniesService {
50
50
  parentHierarchy(legalEntityId: string, auth: AuthContext): Promise<LegalEntity[]>;
51
51
  }
52
52
 
53
- export { CompaniesService, LegalEntity, LegalEntityCreate, LegalEntityUpdate };
53
+ export { CompaniesService, LegalEntity, LegalEntityCreate, type LegalEntitySearchInput, LegalEntityUpdate };
@@ -50,4 +50,4 @@ declare class CompaniesService {
50
50
  parentHierarchy(legalEntityId: string, auth: AuthContext): Promise<LegalEntity[]>;
51
51
  }
52
52
 
53
- export { CompaniesService, LegalEntity, LegalEntityCreate, LegalEntityUpdate };
53
+ export { CompaniesService, LegalEntity, LegalEntityCreate, type LegalEntitySearchInput, LegalEntityUpdate };
package/dist/index.cjs CHANGED
@@ -1242,7 +1242,7 @@ var HttpClient = class {
1242
1242
  };
1243
1243
 
1244
1244
  // package.json
1245
- var version = "4.2.3";
1245
+ var version = "4.3.0";
1246
1246
 
1247
1247
  // src/version.ts
1248
1248
  var SDK_VERSION = version;
@@ -3506,37 +3506,31 @@ var MediaService = class {
3506
3506
  });
3507
3507
  }
3508
3508
  /**
3509
- * Update an asset. The input is a discriminated union mirroring
3510
- * {@link create}:
3511
- * - `{ kind: "json", body }` — metadata-only patch (BLOB or LINK).
3512
- * Sends `application/json`. Used for `refIds`, `details`, `metadata`,
3513
- * or `url` changes.
3514
- * - `{ kind: "blob", file, body }` — replaces the BLOB file content
3515
- * (max 30 MB) AND patches metadata in the same request. Sends
3516
- * `multipart/form-data`.
3509
+ * Replace an asset (`PUT`, HTTP 204 — nothing is returned). The spec takes one
3510
+ * body per asset type, mirrored by the discriminated input:
3511
+ * - `{ kind: "json", body }` — a LINK asset, sent as `application/json`. The
3512
+ * body is the whole asset: `type: "LINK"`, `access` and `url` are required.
3513
+ * - `{ kind: "blob", file, body }` — a BLOB asset, sent as
3514
+ * `multipart/form-data`: the file (max 30 MB) and the asset body, both
3515
+ * required, so this always replaces the bytes.
3517
3516
  *
3518
- * `type` and `access` are immutable per Emporix — they must match the
3519
- * existing asset's values. Optimistic-locking is via `body.metadata.version`;
3520
- * the server returns 409 Conflict on a stale version.
3517
+ * A BLOB's metadata alone — `refIds`, `details`, mixins — goes through
3518
+ * {@link patch}. Earlier versions also took a BLOB as the JSON body and
3519
+ * promised the updated asset in return; the spec allows neither.
3520
+ *
3521
+ * `type` and `access` are immutable. Optimistic locking is via
3522
+ * `body.metadata.version`; a stale one gets a 409.
3521
3523
  */
3522
3524
  async update(assetId, input, auth2 = SERVICE5) {
3525
+ const path = `${this.base()}/${encodeURIComponent(assetId)}`;
3523
3526
  if (input.kind === "blob") {
3524
3527
  const fd = new FormData();
3525
3528
  fd.set("file", input.file);
3526
3529
  fd.set("body", JSON.stringify(input.body));
3527
- return this.ctx.http.request({
3528
- method: "PUT",
3529
- path: `${this.base()}/${assetId}`,
3530
- auth: auth2,
3531
- body: fd
3532
- });
3530
+ await this.ctx.http.request({ method: "PUT", path, auth: auth2, body: fd });
3531
+ return;
3533
3532
  }
3534
- return this.ctx.http.request({
3535
- method: "PUT",
3536
- path: `${this.base()}/${assetId}`,
3537
- auth: auth2,
3538
- body: input.body
3539
- });
3533
+ await this.ctx.http.request({ method: "PUT", path, auth: auth2, body: input.body });
3540
3534
  }
3541
3535
  /**
3542
3536
  * Partially update an asset with an RFC-6902 JSON-Patch op-array
@@ -3719,12 +3713,17 @@ var MediaService = class {
3719
3713
  }
3720
3714
  /**
3721
3715
  * Replace the file content of an existing BLOB asset. Sugar over
3722
- * {@link update} with `kind: "blob"`. `access` is required because the
3723
- * field is immutable and the server validates that the patch matches.
3724
- * Pass `version` from `asset.metadata.version` if you want optimistic
3725
- * locking (recommended when concurrent writers are possible).
3716
+ * {@link update} with `kind: "blob"`, and like it resolves to nothing — the
3717
+ * PUT answers 204. `access` is required because the field is immutable and
3718
+ * the server validates that the body matches.
3719
+ *
3720
+ * Pass `version` from `asset.metadata.version` for optimistic locking
3721
+ * (recommended when concurrent writers are possible) — a stale one gets a 409.
3722
+ * Without it the current version is read first, because the live API rejects
3723
+ * an update that carries none; the write then wins over any in between.
3726
3724
  */
3727
3725
  async replaceFile(assetId, input, auth2 = SERVICE5) {
3726
+ const version2 = input.version ?? (await this.get(assetId, auth2)).metadata?.version ?? 1;
3728
3727
  const body = {
3729
3728
  type: "BLOB",
3730
3729
  access: input.access,
@@ -3734,7 +3733,7 @@ var MediaService = class {
3734
3733
  ...input.mimeType ? { mimeType: input.mimeType } : {}
3735
3734
  }
3736
3735
  } : {},
3737
- ...input.version !== void 0 ? { metadata: { version: input.version } } : {}
3736
+ metadata: { version: version2 }
3738
3737
  };
3739
3738
  return this.update(assetId, { kind: "blob", file: input.file, body }, auth2);
3740
3739
  }
@@ -3748,23 +3747,42 @@ var MediaService = class {
3748
3747
  };
3749
3748
  return this.create({ kind: "link", body }, auth2);
3750
3749
  }
3751
- /** Idempotently add a PRODUCT refId to an asset. */
3750
+ /**
3751
+ * Idempotently add a PRODUCT refId to an asset, through a JSON Patch that
3752
+ * appends the one reference, so it works for BLOB and LINK alike and leaves
3753
+ * the rest of the asset alone.
3754
+ *
3755
+ * Resolves to the asset as read back after the patch, because Emporix answers
3756
+ * 204 to a reference to a product that does not exist and drops it — check
3757
+ * `refIds` rather than trusting the call. Only a PUBLIC asset takes a product
3758
+ * reference; a PRIVATE one answers 400.
3759
+ *
3760
+ * Earlier versions sent a JSON PUT of `{ type, refIds }`, which the spec
3761
+ * rejects for both types: a BLOB takes no JSON PUT, and a LINK's requires
3762
+ * `access` and `url`.
3763
+ */
3752
3764
  async attachToProduct(assetId, productId, auth2 = SERVICE5) {
3753
3765
  const a = await this.get(assetId, auth2);
3754
3766
  const refIds = a.refIds ?? [];
3755
3767
  if (refIds.some((r) => isProductRef(r, productId))) return a;
3756
- const next = [...refIds, { type: "PRODUCT", id: productId }];
3757
- const patch = { type: a.type, refIds: next };
3758
- return this.update(assetId, { kind: "json", body: patch }, auth2);
3768
+ const ref = { type: "PRODUCT", id: productId };
3769
+ const op = a.refIds === void 0 ? { op: "add", path: "/refIds", value: [ref] } : { op: "add", path: "/refIds/-", value: ref };
3770
+ await this.patch(assetId, [op], auth2);
3771
+ return this.get(assetId, auth2);
3759
3772
  }
3760
- /** Remove a PRODUCT refId from an asset (no-op if absent). */
3773
+ /**
3774
+ * Remove a PRODUCT refId from an asset (no-op if absent), through a JSON Patch
3775
+ * that replaces the references and nothing else. Resolves to the asset as
3776
+ * read back after the patch, like {@link attachToProduct}. Earlier versions
3777
+ * sent a spec-invalid JSON PUT, as that one did.
3778
+ */
3761
3779
  async detachFromProduct(assetId, productId, auth2 = SERVICE5) {
3762
3780
  const a = await this.get(assetId, auth2);
3763
3781
  const refIds = a.refIds ?? [];
3764
3782
  const next = refIds.filter((r) => !isProductRef(r, productId));
3765
3783
  if (next.length === refIds.length) return a;
3766
- const patch = { type: a.type, refIds: next };
3767
- return this.update(assetId, { kind: "json", body: patch }, auth2);
3784
+ await this.patch(assetId, [{ op: "replace", path: "/refIds", value: next }], auth2);
3785
+ return this.get(assetId, auth2);
3768
3786
  }
3769
3787
  /** Convenience: list assets attached to a product (server-side filter). */
3770
3788
  async listForProduct(productId, auth2 = SERVICE5) {