@viu/emporix-sdk 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +13 -0
  3. package/dist/cart.d.cts +1 -1
  4. package/dist/cart.d.ts +1 -1
  5. package/dist/category.d.cts +1 -1
  6. package/dist/category.d.ts +1 -1
  7. package/dist/checkout.d.cts +1 -1
  8. package/dist/checkout.d.ts +1 -1
  9. package/dist/chunk-WSXRUYBE.js +248 -0
  10. package/dist/chunk-WSXRUYBE.js.map +1 -0
  11. package/dist/companies.d.cts +1 -1
  12. package/dist/companies.d.ts +1 -1
  13. package/dist/contacts.d.cts +1 -1
  14. package/dist/contacts.d.ts +1 -1
  15. package/dist/{context-vKgzZ-5f.d.cts → context-mMTqsW1Z.d.cts} +15 -0
  16. package/dist/{context-vKgzZ-5f.d.ts → context-mMTqsW1Z.d.ts} +15 -0
  17. package/dist/customer-groups.d.cts +1 -1
  18. package/dist/customer-groups.d.ts +1 -1
  19. package/dist/customer.d.cts +1 -1
  20. package/dist/customer.d.ts +1 -1
  21. package/dist/index.cjs +186 -9
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.d.cts +3 -3
  24. package/dist/index.d.ts +3 -3
  25. package/dist/index.js +53 -2
  26. package/dist/index.js.map +1 -1
  27. package/dist/locations.d.cts +1 -1
  28. package/dist/locations.d.ts +1 -1
  29. package/dist/media.cjs +218 -9
  30. package/dist/media.cjs.map +1 -1
  31. package/dist/media.d.cts +103 -7
  32. package/dist/media.d.ts +103 -7
  33. package/dist/media.js +2 -1
  34. package/dist/orders.d.cts +1 -1
  35. package/dist/orders.d.ts +1 -1
  36. package/dist/payment.d.cts +1 -1
  37. package/dist/payment.d.ts +1 -1
  38. package/dist/price.d.cts +1 -1
  39. package/dist/price.d.ts +1 -1
  40. package/dist/product.d.cts +1 -1
  41. package/dist/product.d.ts +1 -1
  42. package/dist/segment.d.cts +1 -1
  43. package/dist/segment.d.ts +1 -1
  44. package/package.json +2 -2
  45. package/dist/chunk-F3WAQH2E.js +0 -120
  46. package/dist/chunk-F3WAQH2E.js.map +0 -1
package/dist/index.cjs CHANGED
@@ -634,6 +634,57 @@ var HttpClient = class {
634
634
  throw errorFromResponse(res.status, `${o.method} ${o.path} \u2192 ${res.status}`, parsed);
635
635
  }
636
636
  }
637
+ /**
638
+ * Single-shot fetch that returns the raw {@link Response} unparsed. Used by
639
+ * endpoints whose responses are not JSON (binary downloads, redirect-only
640
+ * endpoints). Skips the retry-on-5xx and 401-reauth-once paths intentionally
641
+ * — those depend on parsing the JSON error body, which a raw consumer must
642
+ * handle itself if needed. Auth resolution + timeout + logging are still
643
+ * applied.
644
+ *
645
+ * `extra.redirect` is forwarded to `fetch` so callers can use `'manual'` to
646
+ * capture 30x responses with their `Location` header (Node fetch exposes
647
+ * the header in this mode; browser fetch returns an opaque response).
648
+ */
649
+ async requestRaw(o, extra) {
650
+ const requestId = `req-${++requestSeq}`;
651
+ const log = this.opts.logger.child({ requestId });
652
+ const url = new URL(this.opts.host + o.path);
653
+ for (const [k, v] of Object.entries(o.query ?? {})) {
654
+ if (v !== void 0) url.searchParams.set(k, String(v));
655
+ }
656
+ const token = await resolveToken(o.auth, this.opts.provider);
657
+ log.debug("http requestRaw", {
658
+ authKind: o.auth.kind,
659
+ method: o.method,
660
+ url: url.pathname,
661
+ redirect: extra?.redirect ?? "follow"
662
+ });
663
+ const controller = new AbortController();
664
+ const timer = setTimeout(
665
+ () => controller.abort(),
666
+ o.timeoutMs ?? this.opts.timeouts.readMs
667
+ );
668
+ const isFormData = typeof FormData !== "undefined" && o.body instanceof FormData;
669
+ const init = {
670
+ method: o.method,
671
+ headers: {
672
+ ...o.headers ?? {},
673
+ Authorization: `Bearer ${token}`,
674
+ ...o.body !== void 0 && !isFormData ? { "Content-Type": "application/json" } : {}
675
+ },
676
+ signal: controller.signal,
677
+ ...extra?.redirect ? { redirect: extra.redirect } : {}
678
+ };
679
+ if (o.body !== void 0) {
680
+ init.body = isFormData ? o.body : JSON.stringify(o.body);
681
+ }
682
+ try {
683
+ return await fetch(url, init);
684
+ } finally {
685
+ clearTimeout(timer);
686
+ }
687
+ }
637
688
  };
638
689
 
639
690
  // src/core/require-customer.ts
@@ -1346,14 +1397,29 @@ var MediaService = class {
1346
1397
  body: input.body
1347
1398
  });
1348
1399
  }
1349
- /** List assets. Optional `query` is passed through to Emporix verbatim. */
1350
- async list(query, auth2 = SERVICE2) {
1351
- return this.ctx.http.request({
1400
+ /**
1401
+ * List assets, wrapped in the shared {@link PaginatedItems} envelope so
1402
+ * callers can iterate over pages consistently with the rest of the SDK.
1403
+ * `hasNextPage` is the standard SDK heuristic: `true` when the returned
1404
+ * page is full (`items.length === requested pageSize`). Pagination
1405
+ * defaults match Emporix's server defaults (`pageNumber: 1`,
1406
+ * `pageSize: 60`) so an empty `query` produces stable expectations.
1407
+ */
1408
+ async list(query = {}, auth2 = SERVICE2) {
1409
+ const pageNumber = query.pageNumber ?? 1;
1410
+ const pageSize = query.pageSize ?? 60;
1411
+ const items = await this.ctx.http.request({
1352
1412
  method: "GET",
1353
1413
  path: this.base(),
1354
1414
  auth: auth2,
1355
- ...query ? { query } : {}
1415
+ query: { ...query, pageNumber, pageSize }
1356
1416
  });
1417
+ return {
1418
+ items,
1419
+ pageNumber,
1420
+ pageSize,
1421
+ hasNextPage: items.length === pageSize
1422
+ };
1357
1423
  }
1358
1424
  /** Fetch an asset by id. */
1359
1425
  async get(assetId, auth2 = SERVICE2) {
@@ -1363,13 +1429,37 @@ var MediaService = class {
1363
1429
  auth: auth2
1364
1430
  });
1365
1431
  }
1366
- /** Update an asset (e.g. swap `refIds` or `access`). */
1367
- async update(assetId, patch, auth2 = SERVICE2) {
1432
+ /**
1433
+ * Update an asset. The input is a discriminated union mirroring
1434
+ * {@link create}:
1435
+ * - `{ kind: "json", body }` — metadata-only patch (BLOB or LINK).
1436
+ * Sends `application/json`. Used for `refIds`, `details`, `metadata`,
1437
+ * or `url` changes.
1438
+ * - `{ kind: "blob", file, body }` — replaces the BLOB file content
1439
+ * (max 10MB) AND patches metadata in the same request. Sends
1440
+ * `multipart/form-data`.
1441
+ *
1442
+ * `type` and `access` are immutable per Emporix — they must match the
1443
+ * existing asset's values. Optimistic-locking is via `body.metadata.version`;
1444
+ * the server returns 409 Conflict on a stale version.
1445
+ */
1446
+ async update(assetId, input, auth2 = SERVICE2) {
1447
+ if (input.kind === "blob") {
1448
+ const fd = new FormData();
1449
+ fd.set("file", input.file);
1450
+ fd.set("body", JSON.stringify(input.body));
1451
+ return this.ctx.http.request({
1452
+ method: "PUT",
1453
+ path: `${this.base()}/${assetId}`,
1454
+ auth: auth2,
1455
+ body: fd
1456
+ });
1457
+ }
1368
1458
  return this.ctx.http.request({
1369
1459
  method: "PUT",
1370
1460
  path: `${this.base()}/${assetId}`,
1371
1461
  auth: auth2,
1372
- body: patch
1462
+ body: input.body
1373
1463
  });
1374
1464
  }
1375
1465
  /** Remove an asset. */
@@ -1380,6 +1470,72 @@ var MediaService = class {
1380
1470
  auth: auth2
1381
1471
  });
1382
1472
  }
1473
+ /**
1474
+ * Download an asset by id. Returns a discriminated union:
1475
+ * - `{ kind: "redirect", url }` for `PUBLIC` assets (server-side 30x with
1476
+ * the storage URL in `Location`).
1477
+ * - `{ kind: "bytes", data }` for `PRIVATE` assets (server-side 200 with
1478
+ * the asset bytes; `etag` + `contentType` headers are exposed for
1479
+ * caching). When the server reports `text/plain` Content-Type (the
1480
+ * OpenAPI-documented format), the SDK decodes the base64 stream into
1481
+ * an `ArrayBuffer` transparently; binary Content-Types are passed
1482
+ * through verbatim.
1483
+ *
1484
+ * Implementation note: uses `redirect: "manual"` so the redirect-location
1485
+ * is observable. In Node.js this works; in a browser the redirect Location
1486
+ * is intentionally hidden by fetch — there, `PUBLIC` downloads will
1487
+ * surface as an opaque-redirect and the SDK throws. Browser code should
1488
+ * use the asset's `url` field from `get()` for `LINK` assets, or the
1489
+ * direct storage URL for `PUBLIC` `BLOB` assets (typically delivered via
1490
+ * an `<img>` tag rather than `download()`).
1491
+ */
1492
+ async download(assetId, auth2 = SERVICE2) {
1493
+ const path = `${this.base()}/${assetId}/download`;
1494
+ const res = await this.ctx.http.requestRaw(
1495
+ { method: "GET", path, auth: auth2 },
1496
+ { redirect: "manual" }
1497
+ );
1498
+ if (res.status >= 300 && res.status < 400) {
1499
+ const url = res.headers.get("location");
1500
+ if (!url) {
1501
+ throw new EmporixError(
1502
+ `media.download: ${res.status} response without a Location header`,
1503
+ res.status
1504
+ );
1505
+ }
1506
+ return { kind: "redirect", url };
1507
+ }
1508
+ if (res.ok) {
1509
+ const etag = res.headers.get("etag") ?? void 0;
1510
+ const contentType = res.headers.get("content-type") ?? void 0;
1511
+ let data;
1512
+ if (contentType?.startsWith("text/plain")) {
1513
+ const text2 = await res.text();
1514
+ const bin = atob(text2);
1515
+ const arr = new Uint8Array(bin.length);
1516
+ for (let i = 0; i < bin.length; i++) arr[i] = bin.charCodeAt(i);
1517
+ data = arr.buffer;
1518
+ } else {
1519
+ data = await res.arrayBuffer();
1520
+ }
1521
+ return {
1522
+ kind: "bytes",
1523
+ data,
1524
+ ...etag !== void 0 ? { etag } : {},
1525
+ ...contentType !== void 0 ? { contentType } : {}
1526
+ };
1527
+ }
1528
+ const text = await res.text();
1529
+ let parsed = void 0;
1530
+ if (text) {
1531
+ try {
1532
+ parsed = JSON.parse(text);
1533
+ } catch {
1534
+ parsed = text;
1535
+ }
1536
+ }
1537
+ throw errorFromResponse(res.status, `GET ${path} \u2192 ${res.status}`, parsed);
1538
+ }
1383
1539
  /** Multipart upload sugar: builds `AssetCreateBlob` from input. */
1384
1540
  async uploadFile(input, auth2 = SERVICE2) {
1385
1541
  const body = {
@@ -1395,6 +1551,27 @@ var MediaService = class {
1395
1551
  };
1396
1552
  return this.create({ kind: "blob", file: input.file, body }, auth2);
1397
1553
  }
1554
+ /**
1555
+ * Replace the file content of an existing BLOB asset. Sugar over
1556
+ * {@link update} with `kind: "blob"`. `access` is required because the
1557
+ * field is immutable and the server validates that the patch matches.
1558
+ * Pass `version` from `asset.metadata.version` if you want optimistic
1559
+ * locking (recommended when concurrent writers are possible).
1560
+ */
1561
+ async replaceFile(assetId, input, auth2 = SERVICE2) {
1562
+ const body = {
1563
+ type: "BLOB",
1564
+ access: input.access,
1565
+ ...input.filename || input.mimeType ? {
1566
+ details: {
1567
+ ...input.filename ? { filename: input.filename } : {},
1568
+ ...input.mimeType ? { mimeType: input.mimeType } : {}
1569
+ }
1570
+ } : {},
1571
+ ...input.version !== void 0 ? { metadata: { version: input.version } } : {}
1572
+ };
1573
+ return this.update(assetId, { kind: "blob", file: input.file, body }, auth2);
1574
+ }
1398
1575
  /** External-URL sugar: builds `AssetCreateLink`. */
1399
1576
  async link(input, auth2 = SERVICE2) {
1400
1577
  const body = {
@@ -1412,7 +1589,7 @@ var MediaService = class {
1412
1589
  if (refIds.some((r) => isProductRef(r, productId))) return a;
1413
1590
  const next = [...refIds, { type: "PRODUCT", id: productId }];
1414
1591
  const patch = { type: a.type, refIds: next };
1415
- return this.update(assetId, patch, auth2);
1592
+ return this.update(assetId, { kind: "json", body: patch }, auth2);
1416
1593
  }
1417
1594
  /** Remove a PRODUCT refId from an asset (no-op if absent). */
1418
1595
  async detachFromProduct(assetId, productId, auth2 = SERVICE2) {
@@ -1421,7 +1598,7 @@ var MediaService = class {
1421
1598
  const next = refIds.filter((r) => !isProductRef(r, productId));
1422
1599
  if (next.length === refIds.length) return a;
1423
1600
  const patch = { type: a.type, refIds: next };
1424
- return this.update(assetId, patch, auth2);
1601
+ return this.update(assetId, { kind: "json", body: patch }, auth2);
1425
1602
  }
1426
1603
  /** Convenience: list assets attached to a product (server-side filter). */
1427
1604
  async listForProduct(productId, auth2 = SERVICE2) {