@mitway/sdk 0.3.0 → 0.4.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/dist/index.js CHANGED
@@ -528,6 +528,29 @@ var HttpClient = class {
528
528
  throw error;
529
529
  }
530
530
  }
531
+ /**
532
+ * Low-level fetch helper for binary bodies (uploads) and streamed responses
533
+ * (downloads). Applies the current Bearer token (user session → anon key
534
+ * fallback) plus any configured default headers, resolves `path` against
535
+ * `baseUrl`, and returns the raw `Response` — it does NOT unwrap the
536
+ * `{ data, error }` envelope, so the caller is responsible for status
537
+ * checking and parsing.
538
+ *
539
+ * Used by the storage module for object upload/download paths where the
540
+ * body or response is not JSON.
541
+ */
542
+ async rawFetch(path, init = {}) {
543
+ const url = this.buildUrl(path);
544
+ const headers = new Headers(init.headers ?? {});
545
+ for (const [k, v] of Object.entries(this.defaultHeaders)) {
546
+ if (!headers.has(k)) headers.set(k, v);
547
+ }
548
+ if (!headers.has("Authorization")) {
549
+ const token = this.userToken ?? this.anonKey;
550
+ if (token) headers.set("Authorization", `Bearer ${token}`);
551
+ }
552
+ return this.fetch(url, { ...init, headers });
553
+ }
531
554
  get(path, options) {
532
555
  return this.request("GET", path, options);
533
556
  }
@@ -1477,6 +1500,373 @@ var Realtime = class {
1477
1500
  }
1478
1501
  };
1479
1502
 
1503
+ // src/modules/storage.ts
1504
+ function bucketFromWire(row) {
1505
+ return {
1506
+ id: row.id,
1507
+ name: row.name,
1508
+ public: row.public,
1509
+ fileSizeLimitBytes: row.file_size_limit_bytes,
1510
+ allowedMimeTypes: row.allowed_mime_types,
1511
+ createdAt: row.created_at,
1512
+ updatedAt: row.updated_at
1513
+ };
1514
+ }
1515
+ function objectFromWire(row, bucketName) {
1516
+ return {
1517
+ id: row.id,
1518
+ bucket: bucketName,
1519
+ key: row.key,
1520
+ size: row.size,
1521
+ mimeType: row.mime_type,
1522
+ etag: row.etag,
1523
+ cacheControl: row.cache_control,
1524
+ contentDisposition: row.content_disposition,
1525
+ uploadedBy: row.uploaded_by,
1526
+ uploadedAt: row.uploaded_at,
1527
+ updatedAt: row.updated_at
1528
+ };
1529
+ }
1530
+ function configFromWire(row) {
1531
+ return {
1532
+ defaultFileSizeLimitBytes: row.default_file_size_limit_bytes,
1533
+ maxFileSizeLimitBytes: row.max_file_size_limit_bytes,
1534
+ tenantStorageQuotaBytes: row.tenant_storage_quota_bytes,
1535
+ reservedSpaceBytes: row.reserved_space_bytes,
1536
+ signedUrlDefaultTtlSec: row.signed_url_default_ttl_sec,
1537
+ signedUrlMaxTtlSec: row.signed_url_max_ttl_sec
1538
+ };
1539
+ }
1540
+ function encodeKey(key) {
1541
+ return key.split("/").map(encodeURIComponent).join("/");
1542
+ }
1543
+ function wrapError2(err, fallback) {
1544
+ if (err instanceof MitwayBaasError) return { data: null, error: err };
1545
+ return {
1546
+ data: null,
1547
+ error: new MitwayBaasError(
1548
+ err instanceof Error ? err.message : fallback,
1549
+ 0,
1550
+ "STORAGE_ERROR"
1551
+ )
1552
+ };
1553
+ }
1554
+ async function readEnvelopeError(response) {
1555
+ let code = "STORAGE_ERROR";
1556
+ let message = `HTTP ${response.status}`;
1557
+ try {
1558
+ const body = await response.json();
1559
+ if (body && body.error) {
1560
+ code = body.error.code ?? code;
1561
+ message = body.error.message ?? message;
1562
+ }
1563
+ } catch {
1564
+ }
1565
+ return new MitwayBaasError(message, response.status, code);
1566
+ }
1567
+ var StorageBucketClient = class {
1568
+ constructor(http, bucketName) {
1569
+ this.http = http;
1570
+ this.bucketName = bucketName;
1571
+ }
1572
+ http;
1573
+ bucketName;
1574
+ bucketBase() {
1575
+ return `/api/storage/buckets/${encodeURIComponent(this.bucketName)}`;
1576
+ }
1577
+ objectPath(key) {
1578
+ return `${this.bucketBase()}/objects/${encodeKey(key)}`;
1579
+ }
1580
+ async upload(key, body, opts = {}) {
1581
+ try {
1582
+ const method = opts.upsert ? "PUT" : "POST";
1583
+ const headers = {
1584
+ "Content-Type": opts.contentType ?? "application/octet-stream"
1585
+ };
1586
+ if (opts.cacheControl) headers["Cache-Control"] = opts.cacheControl;
1587
+ if (opts.contentDisposition)
1588
+ headers["Content-Disposition"] = opts.contentDisposition;
1589
+ const response = await this.http.rawFetch(this.objectPath(key), {
1590
+ method,
1591
+ headers,
1592
+ body,
1593
+ signal: opts.abortSignal
1594
+ });
1595
+ if (!response.ok) {
1596
+ return { data: null, error: await readEnvelopeError(response) };
1597
+ }
1598
+ const parsed = await response.json();
1599
+ if (parsed.error || !parsed.data) {
1600
+ return {
1601
+ data: null,
1602
+ error: new MitwayBaasError(
1603
+ parsed.error?.message ?? "Upload failed",
1604
+ response.status,
1605
+ parsed.error?.code ?? "STORAGE_ERROR"
1606
+ )
1607
+ };
1608
+ }
1609
+ return { data: objectFromWire(parsed.data, this.bucketName), error: null };
1610
+ } catch (err) {
1611
+ return wrapError2(err, "Upload failed");
1612
+ }
1613
+ }
1614
+ async download(key, opts = {}) {
1615
+ try {
1616
+ const headers = {};
1617
+ if (opts.range) {
1618
+ headers["Range"] = `bytes=${opts.range.start}-${opts.range.end}`;
1619
+ }
1620
+ const response = await this.http.rawFetch(this.objectPath(key), {
1621
+ method: "GET",
1622
+ headers,
1623
+ signal: opts.abortSignal
1624
+ });
1625
+ if (!response.ok) {
1626
+ return { data: null, error: await readEnvelopeError(response) };
1627
+ }
1628
+ const blob = await response.blob();
1629
+ return { data: blob, error: null };
1630
+ } catch (err) {
1631
+ return wrapError2(err, "Download failed");
1632
+ }
1633
+ }
1634
+ async getStream(key, opts = {}) {
1635
+ try {
1636
+ const headers = {};
1637
+ if (opts.range) {
1638
+ headers["Range"] = `bytes=${opts.range.start}-${opts.range.end}`;
1639
+ }
1640
+ const response = await this.http.rawFetch(this.objectPath(key), {
1641
+ method: "GET",
1642
+ headers,
1643
+ signal: opts.abortSignal
1644
+ });
1645
+ if (!response.ok) {
1646
+ return { data: null, error: await readEnvelopeError(response) };
1647
+ }
1648
+ if (!response.body) {
1649
+ return {
1650
+ data: null,
1651
+ error: new MitwayBaasError(
1652
+ "Response body is not a stream",
1653
+ response.status,
1654
+ "STORAGE_ERROR"
1655
+ )
1656
+ };
1657
+ }
1658
+ return {
1659
+ data: response.body,
1660
+ error: null
1661
+ };
1662
+ } catch (err) {
1663
+ return wrapError2(err, "Download failed");
1664
+ }
1665
+ }
1666
+ async remove(keys) {
1667
+ try {
1668
+ const results = await Promise.allSettled(
1669
+ keys.map(
1670
+ (key) => this.http.rawFetch(this.objectPath(key), { method: "DELETE" })
1671
+ )
1672
+ );
1673
+ const removed = [];
1674
+ const errors = [];
1675
+ for (let i = 0; i < keys.length; i++) {
1676
+ const key = keys[i];
1677
+ const r = results[i];
1678
+ if (r.status === "fulfilled" && r.value.ok) {
1679
+ removed.push(key);
1680
+ } else if (r.status === "fulfilled") {
1681
+ errors.push(`${key}: HTTP ${r.value.status}`);
1682
+ } else {
1683
+ const msg = r.reason instanceof Error ? r.reason.message : String(r.reason);
1684
+ errors.push(`${key}: ${msg}`);
1685
+ }
1686
+ }
1687
+ if (errors.length > 0) {
1688
+ return {
1689
+ data: null,
1690
+ error: new MitwayBaasError(
1691
+ `Failed to delete some objects: ${errors.join("; ")}`,
1692
+ 0,
1693
+ "STORAGE_ERROR"
1694
+ )
1695
+ };
1696
+ }
1697
+ return { data: { removed }, error: null };
1698
+ } catch (err) {
1699
+ return wrapError2(err, "Delete failed");
1700
+ }
1701
+ }
1702
+ async list(opts = {}) {
1703
+ try {
1704
+ const params = {};
1705
+ if (opts.prefix !== void 0) params.prefix = opts.prefix;
1706
+ if (opts.limit !== void 0) params.limit = String(opts.limit);
1707
+ if (opts.startAfter !== void 0) params.start_after = opts.startAfter;
1708
+ const rows = await this.http.get(
1709
+ `${this.bucketBase()}/objects`,
1710
+ { params }
1711
+ );
1712
+ return {
1713
+ data: rows.map((r) => objectFromWire(r, this.bucketName)),
1714
+ error: null
1715
+ };
1716
+ } catch (err) {
1717
+ return wrapError2(err, "List failed");
1718
+ }
1719
+ }
1720
+ async copy(fromKey, toKey, toBucket) {
1721
+ try {
1722
+ const row = await this.http.post(
1723
+ `${this.objectPath(fromKey)}/copy`,
1724
+ {
1725
+ dest_bucket: toBucket ?? this.bucketName,
1726
+ dest_key: toKey
1727
+ }
1728
+ );
1729
+ return {
1730
+ data: objectFromWire(row, toBucket ?? this.bucketName),
1731
+ error: null
1732
+ };
1733
+ } catch (err) {
1734
+ return wrapError2(err, "Copy failed");
1735
+ }
1736
+ }
1737
+ async move(fromKey, toKey, toBucket) {
1738
+ try {
1739
+ const row = await this.http.post(
1740
+ `${this.objectPath(fromKey)}/move`,
1741
+ {
1742
+ dest_bucket: toBucket ?? this.bucketName,
1743
+ dest_key: toKey
1744
+ }
1745
+ );
1746
+ return {
1747
+ data: objectFromWire(row, toBucket ?? this.bucketName),
1748
+ error: null
1749
+ };
1750
+ } catch (err) {
1751
+ return wrapError2(err, "Move failed");
1752
+ }
1753
+ }
1754
+ async createSignedUrl(key, opts = {}) {
1755
+ try {
1756
+ const body = {};
1757
+ if (opts.expiresIn !== void 0) body.expires_in = opts.expiresIn;
1758
+ const wire = await this.http.post(`${this.objectPath(key)}/sign`, body);
1759
+ return {
1760
+ data: {
1761
+ url: wire.url,
1762
+ token: wire.token,
1763
+ expiresAt: wire.expiresAt
1764
+ },
1765
+ error: null
1766
+ };
1767
+ } catch (err) {
1768
+ return wrapError2(err, "Sign failed");
1769
+ }
1770
+ }
1771
+ getPublicUrl(key) {
1772
+ const url = `${this.http.baseUrl.replace(/\/$/, "")}${this.objectPath(key)}`;
1773
+ return { data: { url } };
1774
+ }
1775
+ };
1776
+ var Storage = class {
1777
+ constructor(http) {
1778
+ this.http = http;
1779
+ }
1780
+ http;
1781
+ /** Scope subsequent operations to a single bucket. */
1782
+ from(bucketName) {
1783
+ return new StorageBucketClient(this.http, bucketName);
1784
+ }
1785
+ // --- Admin (require service_role) ---
1786
+ async listBuckets() {
1787
+ try {
1788
+ const rows = await this.http.get("/api/storage/buckets");
1789
+ return { data: rows.map(bucketFromWire), error: null };
1790
+ } catch (err) {
1791
+ return wrapError2(err, "listBuckets failed");
1792
+ }
1793
+ }
1794
+ async getBucket(name) {
1795
+ try {
1796
+ const row = await this.http.get(
1797
+ `/api/storage/buckets/${encodeURIComponent(name)}`
1798
+ );
1799
+ return { data: bucketFromWire(row), error: null };
1800
+ } catch (err) {
1801
+ return wrapError2(err, "getBucket failed");
1802
+ }
1803
+ }
1804
+ async createBucket(name, opts = {}) {
1805
+ try {
1806
+ const body = { name };
1807
+ if (opts.public !== void 0) body.public = opts.public;
1808
+ if (opts.fileSizeLimitBytes !== void 0)
1809
+ body.file_size_limit_bytes = opts.fileSizeLimitBytes;
1810
+ if (opts.allowedMimeTypes !== void 0)
1811
+ body.allowed_mime_types = opts.allowedMimeTypes;
1812
+ const row = await this.http.post(
1813
+ "/api/storage/buckets",
1814
+ body
1815
+ );
1816
+ return { data: bucketFromWire(row), error: null };
1817
+ } catch (err) {
1818
+ return wrapError2(err, "createBucket failed");
1819
+ }
1820
+ }
1821
+ async updateBucket(name, opts) {
1822
+ try {
1823
+ const body = {};
1824
+ if (opts.public !== void 0) body.public = opts.public;
1825
+ if (opts.fileSizeLimitBytes !== void 0)
1826
+ body.file_size_limit_bytes = opts.fileSizeLimitBytes;
1827
+ if (opts.allowedMimeTypes !== void 0)
1828
+ body.allowed_mime_types = opts.allowedMimeTypes;
1829
+ const row = await this.http.patch(
1830
+ `/api/storage/buckets/${encodeURIComponent(name)}`,
1831
+ body
1832
+ );
1833
+ return { data: bucketFromWire(row), error: null };
1834
+ } catch (err) {
1835
+ return wrapError2(err, "updateBucket failed");
1836
+ }
1837
+ }
1838
+ async deleteBucket(name) {
1839
+ try {
1840
+ await this.http.delete(
1841
+ `/api/storage/buckets/${encodeURIComponent(name)}`
1842
+ );
1843
+ return { data: null, error: null };
1844
+ } catch (err) {
1845
+ return wrapError2(err, "deleteBucket failed");
1846
+ }
1847
+ }
1848
+ async emptyBucket(name) {
1849
+ try {
1850
+ const result = await this.http.post(
1851
+ `/api/storage/buckets/${encodeURIComponent(name)}/empty`,
1852
+ {}
1853
+ );
1854
+ return { data: result, error: null };
1855
+ } catch (err) {
1856
+ return wrapError2(err, "emptyBucket failed");
1857
+ }
1858
+ }
1859
+ // --- Storage config (service_role) ---
1860
+ async getConfig() {
1861
+ try {
1862
+ const row = await this.http.get("/api/storage/config");
1863
+ return { data: configFromWire(row), error: null };
1864
+ } catch (err) {
1865
+ return wrapError2(err, "getConfig failed");
1866
+ }
1867
+ }
1868
+ };
1869
+
1480
1870
  // src/client.ts
1481
1871
  var MitwayBaasClient = class {
1482
1872
  http;
@@ -1484,6 +1874,7 @@ var MitwayBaasClient = class {
1484
1874
  auth;
1485
1875
  database;
1486
1876
  realtime;
1877
+ storage;
1487
1878
  constructor(config = {}) {
1488
1879
  const logger = new Logger(config.debug);
1489
1880
  this.tokenManager = new TokenManager();
@@ -1496,6 +1887,7 @@ var MitwayBaasClient = class {
1496
1887
  config.anonKey,
1497
1888
  config.realtime
1498
1889
  );
1890
+ this.storage = new Storage(this.http);
1499
1891
  }
1500
1892
  /**
1501
1893
  * Escape hatch for callers that need to make custom requests against the
@@ -1520,6 +1912,8 @@ export {
1520
1912
  MitwayBaasError,
1521
1913
  Realtime,
1522
1914
  RealtimeChannel,
1915
+ Storage,
1916
+ StorageBucketClient,
1523
1917
  TokenManager,
1524
1918
  createClient,
1525
1919
  index_default as default