@viu/emporix-sdk 4.2.4 → 4.3.1

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,31 @@
1
1
  # @viu/emporix-sdk
2
2
 
3
+ ## 4.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#389](https://github.com/viuteam/emporix-sdk/pull/389) [`784d80e`](https://github.com/viuteam/emporix-sdk/commit/784d80e4fc24b01e9e9fc5667d8e85dc0cad2433) Thanks [@viu-release-bot](https://github.com/apps/viu-release-bot)! - chore(sdk): sync generated types with upstream Emporix API specs
8
+
9
+ Updated services: ai-service
10
+
11
+ ## 4.3.0
12
+
13
+ ### Minor Changes
14
+
15
+ - [#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.
16
+
17
+ - **`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.
18
+ - **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.
19
+ - **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.
20
+
21
+ Both type changes only affect code that, per the spec, did not work before — hence a minor rather than a major.
22
+
23
+ - [#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.
24
+
25
+ - **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.
26
+ - **`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`).
27
+ - **`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.
28
+
3
29
  ## 4.2.4
4
30
 
5
31
  ### Patch Changes
package/README.md CHANGED
@@ -171,9 +171,13 @@ customer's role lacks scope for surface as `EmporixInsufficientScopeError`
171
171
  `sdk.media` covers the full `/media/{tenant}/assets/*` surface: create
172
172
  (`uploadFile` / `link` / `create`, or `startUploadSession` for a direct upload
173
173
  to storage once Emporix Support enables it for the tenant), list (paginated,
174
- `PaginatedItems<Asset>`), get, update (JSON metadata or BLOB multipart
175
- file-replacement via the `replaceFile` sugar), `patch` (JSON Patch — the way to
176
- change a BLOB's metadata without re-uploading the file), remove,
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,
177
181
  `getDownloadUrl` (a storage URL with no size limit, which Emporix recommends for
178
182
  every download), and download (resolves to either a redirect URL for `PUBLIC`
179
183
  assets or an `ArrayBuffer` for `PRIVATE`; above the streaming limit, 30 MB by
@@ -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"]}
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.4";
1245
+ var version = "4.3.1";
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) {