@viu/emporix-sdk 4.2.4 → 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 +18 -0
- package/README.md +7 -3
- package/dist/{chunk-5MPL4XBD.js → chunk-NFSO7WUA.js} +55 -37
- package/dist/chunk-NFSO7WUA.js.map +1 -0
- package/dist/index.cjs +54 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/media.cjs +53 -35
- package/dist/media.cjs.map +1 -1
- package/dist/media.d.cts +59 -22
- package/dist/media.d.ts +59 -22
- package/dist/media.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-5MPL4XBD.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
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
|
+
|
|
3
21
|
## 4.2.4
|
|
4
22
|
|
|
5
23
|
### 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 (
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* - `{ kind: "json", body }` —
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
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
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* the
|
|
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
|
-
|
|
92
|
-
|
|
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
|
-
|
|
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"
|
|
287
|
-
*
|
|
288
|
-
*
|
|
289
|
-
*
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
|
321
|
-
const
|
|
322
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
331
|
-
return this.
|
|
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-
|
|
342
|
-
//# sourceMappingURL=chunk-
|
|
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.
|
|
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
|
-
*
|
|
3510
|
-
*
|
|
3511
|
-
* - `{ kind: "json", body }` —
|
|
3512
|
-
*
|
|
3513
|
-
*
|
|
3514
|
-
*
|
|
3515
|
-
*
|
|
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
|
-
*
|
|
3519
|
-
*
|
|
3520
|
-
* the
|
|
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
|
-
|
|
3528
|
-
|
|
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
|
-
|
|
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"
|
|
3723
|
-
*
|
|
3724
|
-
*
|
|
3725
|
-
*
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
|
3757
|
-
const
|
|
3758
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
3767
|
-
return this.
|
|
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) {
|