@mitway/sdk 0.3.0 → 0.5.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/README.md CHANGED
@@ -73,6 +73,22 @@ const { data: newPost } = await client.database
73
73
  const { data: stats } = await client.database
74
74
  .rpc('get_user_stats', { user_id: session.user.id });
75
75
 
76
+ // Upload a file
77
+ const { data: uploaded, error: upErr } = await client.storage
78
+ .from('avatars')
79
+ .upload('user-123/avatar.png', file, { contentType: 'image/png' });
80
+
81
+ // Share a link that expires in an hour
82
+ const { data: signed } = await client.storage
83
+ .from('private-docs')
84
+ .createSignedUrl('contract.pdf', { expiresIn: 3600 });
85
+ console.log(signed?.url);
86
+
87
+ // Public download URL (for public buckets)
88
+ const { data: pub } = client.storage
89
+ .from('avatars')
90
+ .getPublicUrl('user-123/avatar.png');
91
+
76
92
  // Sign out
77
93
  await client.auth.signOut();
78
94
  ```
package/dist/index.cjs CHANGED
@@ -28,6 +28,8 @@ __export(index_exports, {
28
28
  MitwayBaasError: () => MitwayBaasError,
29
29
  Realtime: () => Realtime,
30
30
  RealtimeChannel: () => RealtimeChannel,
31
+ Storage: () => Storage,
32
+ StorageBucketClient: () => StorageBucketClient,
31
33
  TokenManager: () => TokenManager,
32
34
  createClient: () => createClient,
33
35
  default: () => index_default
@@ -197,6 +199,7 @@ var Logger = class {
197
199
 
198
200
  // src/lib/token-manager.ts
199
201
  var CSRF_TOKEN_COOKIE = "mitway_baas_csrf_token";
202
+ var DEFAULT_STORAGE_KEY = "mitway_baas_session";
200
203
  function getCsrfToken() {
201
204
  if (typeof document === "undefined") return null;
202
205
  const match = document.cookie.split(";").find((c) => c.trim().startsWith(`${CSRF_TOKEN_COOKIE}=`));
@@ -216,13 +219,24 @@ function clearCsrfToken() {
216
219
  }
217
220
  var TokenManager = class {
218
221
  accessToken = null;
222
+ refreshToken = null;
219
223
  user = null;
224
+ persistSession;
225
+ storageKey;
220
226
  /** Fired when the access token changes (used by long-lived consumers). */
221
227
  onTokenChange = null;
228
+ constructor(opts) {
229
+ this.persistSession = opts?.persistSession ?? true;
230
+ this.storageKey = opts?.storageKey ?? DEFAULT_STORAGE_KEY;
231
+ }
222
232
  saveSession(session) {
223
233
  const tokenChanged = session.accessToken !== this.accessToken;
224
234
  this.accessToken = session.accessToken;
225
235
  this.user = session.user;
236
+ if (session.refreshToken !== void 0) {
237
+ this.refreshToken = session.refreshToken ?? null;
238
+ }
239
+ this.persist();
226
240
  if (tokenChanged && this.onTokenChange) {
227
241
  this.onTokenChange();
228
242
  }
@@ -231,6 +245,7 @@ var TokenManager = class {
231
245
  if (!this.accessToken || !this.user) return null;
232
246
  return {
233
247
  accessToken: this.accessToken,
248
+ refreshToken: this.refreshToken ?? void 0,
234
249
  user: this.user
235
250
  };
236
251
  }
@@ -240,24 +255,76 @@ var TokenManager = class {
240
255
  setAccessToken(token) {
241
256
  const tokenChanged = token !== this.accessToken;
242
257
  this.accessToken = token;
258
+ this.persist();
243
259
  if (tokenChanged && this.onTokenChange) {
244
260
  this.onTokenChange();
245
261
  }
246
262
  }
263
+ getRefreshToken() {
264
+ return this.refreshToken;
265
+ }
266
+ setRefreshToken(token) {
267
+ this.refreshToken = token;
268
+ this.persist();
269
+ }
247
270
  getUser() {
248
271
  return this.user;
249
272
  }
250
273
  setUser(user) {
251
274
  this.user = user;
275
+ this.persist();
252
276
  }
253
277
  clearSession() {
254
278
  const hadToken = this.accessToken !== null;
255
279
  this.accessToken = null;
280
+ this.refreshToken = null;
256
281
  this.user = null;
282
+ this.removePersisted();
257
283
  if (hadToken && this.onTokenChange) {
258
284
  this.onTokenChange();
259
285
  }
260
286
  }
287
+ /**
288
+ * Restore the session from localStorage. Returns true if a persisted
289
+ * session was found and loaded into memory.
290
+ */
291
+ restoreSession() {
292
+ if (!this.persistSession || typeof localStorage === "undefined") return false;
293
+ try {
294
+ const raw = localStorage.getItem(this.storageKey);
295
+ if (!raw) return false;
296
+ const stored = JSON.parse(raw);
297
+ if (!stored.accessToken || !stored.user) return false;
298
+ this.accessToken = stored.accessToken;
299
+ this.refreshToken = stored.refreshToken ?? null;
300
+ this.user = stored.user;
301
+ return true;
302
+ } catch {
303
+ return false;
304
+ }
305
+ }
306
+ persist() {
307
+ if (!this.persistSession || typeof localStorage === "undefined") return;
308
+ if (!this.accessToken || !this.user) return;
309
+ try {
310
+ const data = {
311
+ accessToken: this.accessToken,
312
+ user: this.user
313
+ };
314
+ if (this.refreshToken) {
315
+ data.refreshToken = this.refreshToken;
316
+ }
317
+ localStorage.setItem(this.storageKey, JSON.stringify(data));
318
+ } catch {
319
+ }
320
+ }
321
+ removePersisted() {
322
+ if (!this.persistSession || typeof localStorage === "undefined") return;
323
+ try {
324
+ localStorage.removeItem(this.storageKey);
325
+ } catch {
326
+ }
327
+ }
261
328
  };
262
329
 
263
330
  // src/lib/auth-envelope.ts
@@ -564,6 +631,29 @@ var HttpClient = class {
564
631
  throw error;
565
632
  }
566
633
  }
634
+ /**
635
+ * Low-level fetch helper for binary bodies (uploads) and streamed responses
636
+ * (downloads). Applies the current Bearer token (user session → anon key
637
+ * fallback) plus any configured default headers, resolves `path` against
638
+ * `baseUrl`, and returns the raw `Response` — it does NOT unwrap the
639
+ * `{ data, error }` envelope, so the caller is responsible for status
640
+ * checking and parsing.
641
+ *
642
+ * Used by the storage module for object upload/download paths where the
643
+ * body or response is not JSON.
644
+ */
645
+ async rawFetch(path, init = {}) {
646
+ const url = this.buildUrl(path);
647
+ const headers = new Headers(init.headers ?? {});
648
+ for (const [k, v] of Object.entries(this.defaultHeaders)) {
649
+ if (!headers.has(k)) headers.set(k, v);
650
+ }
651
+ if (!headers.has("Authorization")) {
652
+ const token = this.userToken ?? this.anonKey;
653
+ if (token) headers.set("Authorization", `Bearer ${token}`);
654
+ }
655
+ return this.fetch(url, { ...init, headers });
656
+ }
567
657
  get(path, options) {
568
658
  return this.request("GET", path, options);
569
659
  }
@@ -654,6 +744,7 @@ var Auth = class {
654
744
  saveSessionFromResponse(response) {
655
745
  const session = {
656
746
  accessToken: response.accessToken,
747
+ refreshToken: response.refreshToken,
657
748
  user: response.user
658
749
  };
659
750
  if (response.csrfToken) {
@@ -747,6 +838,70 @@ var Auth = class {
747
838
  return wrapError(error, "Session refresh failed");
748
839
  }
749
840
  }
841
+ /**
842
+ * Restore the session from localStorage and validate it with the backend.
843
+ * Call this once on app startup (e.g. in a React AuthProvider useEffect).
844
+ *
845
+ * Flow:
846
+ * 1. Read persisted session from localStorage.
847
+ * 2. Populate in-memory state (TokenManager + HttpClient).
848
+ * 3. Validate with `GET /api/auth/sessions/current`.
849
+ * - If the access token expired, the HttpClient auto-refresh kicks in
850
+ * using the persisted refresh token (sent in the POST body, not
851
+ * cookies — works cross-site).
852
+ * 4. Return the validated user or an error.
853
+ *
854
+ * If no persisted session exists, returns `{ data: null, error }` — the
855
+ * app should show the login page.
856
+ */
857
+ async initialize() {
858
+ const restored = this.tokenManager.restoreSession();
859
+ if (!restored) {
860
+ return {
861
+ data: null,
862
+ error: new MitwayBaasError("No persisted session", 0, "NO_SESSION")
863
+ };
864
+ }
865
+ const session = this.tokenManager.getSession();
866
+ if (!session) {
867
+ return {
868
+ data: null,
869
+ error: new MitwayBaasError("No persisted session", 0, "NO_SESSION")
870
+ };
871
+ }
872
+ this.http.setAuthToken(session.accessToken);
873
+ const refreshToken = this.tokenManager.getRefreshToken();
874
+ if (refreshToken) {
875
+ this.http.setRefreshToken(refreshToken);
876
+ }
877
+ try {
878
+ const response = await this.http.get(
879
+ "/api/auth/sessions/current"
880
+ );
881
+ if (response?.user) {
882
+ this.tokenManager.setUser(response.user);
883
+ return {
884
+ data: {
885
+ user: response.user,
886
+ accessToken: session.accessToken
887
+ },
888
+ error: null
889
+ };
890
+ }
891
+ this.tokenManager.clearSession();
892
+ this.http.setAuthToken(null);
893
+ this.http.setRefreshToken(null);
894
+ return {
895
+ data: null,
896
+ error: new MitwayBaasError("Invalid session", 401, "INVALID_SESSION")
897
+ };
898
+ } catch (error) {
899
+ this.tokenManager.clearSession();
900
+ this.http.setAuthToken(null);
901
+ this.http.setRefreshToken(null);
902
+ return wrapError(error, "Session restore failed");
903
+ }
904
+ }
750
905
  /**
751
906
  * Get the current in-memory session, or null if the user is not signed in.
752
907
  * Synchronous — does not hit the network.
@@ -1513,6 +1668,373 @@ var Realtime = class {
1513
1668
  }
1514
1669
  };
1515
1670
 
1671
+ // src/modules/storage.ts
1672
+ function bucketFromWire(row) {
1673
+ return {
1674
+ id: row.id,
1675
+ name: row.name,
1676
+ public: row.public,
1677
+ fileSizeLimitBytes: row.file_size_limit_bytes,
1678
+ allowedMimeTypes: row.allowed_mime_types,
1679
+ createdAt: row.created_at,
1680
+ updatedAt: row.updated_at
1681
+ };
1682
+ }
1683
+ function objectFromWire(row, bucketName) {
1684
+ return {
1685
+ id: row.id,
1686
+ bucket: bucketName,
1687
+ key: row.key,
1688
+ size: row.size,
1689
+ mimeType: row.mime_type,
1690
+ etag: row.etag,
1691
+ cacheControl: row.cache_control,
1692
+ contentDisposition: row.content_disposition,
1693
+ uploadedBy: row.uploaded_by,
1694
+ uploadedAt: row.uploaded_at,
1695
+ updatedAt: row.updated_at
1696
+ };
1697
+ }
1698
+ function configFromWire(row) {
1699
+ return {
1700
+ defaultFileSizeLimitBytes: row.default_file_size_limit_bytes,
1701
+ maxFileSizeLimitBytes: row.max_file_size_limit_bytes,
1702
+ tenantStorageQuotaBytes: row.tenant_storage_quota_bytes,
1703
+ reservedSpaceBytes: row.reserved_space_bytes,
1704
+ signedUrlDefaultTtlSec: row.signed_url_default_ttl_sec,
1705
+ signedUrlMaxTtlSec: row.signed_url_max_ttl_sec
1706
+ };
1707
+ }
1708
+ function encodeKey(key) {
1709
+ return key.split("/").map(encodeURIComponent).join("/");
1710
+ }
1711
+ function wrapError2(err, fallback) {
1712
+ if (err instanceof MitwayBaasError) return { data: null, error: err };
1713
+ return {
1714
+ data: null,
1715
+ error: new MitwayBaasError(
1716
+ err instanceof Error ? err.message : fallback,
1717
+ 0,
1718
+ "STORAGE_ERROR"
1719
+ )
1720
+ };
1721
+ }
1722
+ async function readEnvelopeError(response) {
1723
+ let code = "STORAGE_ERROR";
1724
+ let message = `HTTP ${response.status}`;
1725
+ try {
1726
+ const body = await response.json();
1727
+ if (body && body.error) {
1728
+ code = body.error.code ?? code;
1729
+ message = body.error.message ?? message;
1730
+ }
1731
+ } catch {
1732
+ }
1733
+ return new MitwayBaasError(message, response.status, code);
1734
+ }
1735
+ var StorageBucketClient = class {
1736
+ constructor(http, bucketName) {
1737
+ this.http = http;
1738
+ this.bucketName = bucketName;
1739
+ }
1740
+ http;
1741
+ bucketName;
1742
+ bucketBase() {
1743
+ return `/api/storage/buckets/${encodeURIComponent(this.bucketName)}`;
1744
+ }
1745
+ objectPath(key) {
1746
+ return `${this.bucketBase()}/objects/${encodeKey(key)}`;
1747
+ }
1748
+ async upload(key, body, opts = {}) {
1749
+ try {
1750
+ const method = opts.upsert ? "PUT" : "POST";
1751
+ const headers = {
1752
+ "Content-Type": opts.contentType ?? "application/octet-stream"
1753
+ };
1754
+ if (opts.cacheControl) headers["Cache-Control"] = opts.cacheControl;
1755
+ if (opts.contentDisposition)
1756
+ headers["Content-Disposition"] = opts.contentDisposition;
1757
+ const response = await this.http.rawFetch(this.objectPath(key), {
1758
+ method,
1759
+ headers,
1760
+ body,
1761
+ signal: opts.abortSignal
1762
+ });
1763
+ if (!response.ok) {
1764
+ return { data: null, error: await readEnvelopeError(response) };
1765
+ }
1766
+ const parsed = await response.json();
1767
+ if (parsed.error || !parsed.data) {
1768
+ return {
1769
+ data: null,
1770
+ error: new MitwayBaasError(
1771
+ parsed.error?.message ?? "Upload failed",
1772
+ response.status,
1773
+ parsed.error?.code ?? "STORAGE_ERROR"
1774
+ )
1775
+ };
1776
+ }
1777
+ return { data: objectFromWire(parsed.data, this.bucketName), error: null };
1778
+ } catch (err) {
1779
+ return wrapError2(err, "Upload failed");
1780
+ }
1781
+ }
1782
+ async download(key, opts = {}) {
1783
+ try {
1784
+ const headers = {};
1785
+ if (opts.range) {
1786
+ headers["Range"] = `bytes=${opts.range.start}-${opts.range.end}`;
1787
+ }
1788
+ const response = await this.http.rawFetch(this.objectPath(key), {
1789
+ method: "GET",
1790
+ headers,
1791
+ signal: opts.abortSignal
1792
+ });
1793
+ if (!response.ok) {
1794
+ return { data: null, error: await readEnvelopeError(response) };
1795
+ }
1796
+ const blob = await response.blob();
1797
+ return { data: blob, error: null };
1798
+ } catch (err) {
1799
+ return wrapError2(err, "Download failed");
1800
+ }
1801
+ }
1802
+ async getStream(key, opts = {}) {
1803
+ try {
1804
+ const headers = {};
1805
+ if (opts.range) {
1806
+ headers["Range"] = `bytes=${opts.range.start}-${opts.range.end}`;
1807
+ }
1808
+ const response = await this.http.rawFetch(this.objectPath(key), {
1809
+ method: "GET",
1810
+ headers,
1811
+ signal: opts.abortSignal
1812
+ });
1813
+ if (!response.ok) {
1814
+ return { data: null, error: await readEnvelopeError(response) };
1815
+ }
1816
+ if (!response.body) {
1817
+ return {
1818
+ data: null,
1819
+ error: new MitwayBaasError(
1820
+ "Response body is not a stream",
1821
+ response.status,
1822
+ "STORAGE_ERROR"
1823
+ )
1824
+ };
1825
+ }
1826
+ return {
1827
+ data: response.body,
1828
+ error: null
1829
+ };
1830
+ } catch (err) {
1831
+ return wrapError2(err, "Download failed");
1832
+ }
1833
+ }
1834
+ async remove(keys) {
1835
+ try {
1836
+ const results = await Promise.allSettled(
1837
+ keys.map(
1838
+ (key) => this.http.rawFetch(this.objectPath(key), { method: "DELETE" })
1839
+ )
1840
+ );
1841
+ const removed = [];
1842
+ const errors = [];
1843
+ for (let i = 0; i < keys.length; i++) {
1844
+ const key = keys[i];
1845
+ const r = results[i];
1846
+ if (r.status === "fulfilled" && r.value.ok) {
1847
+ removed.push(key);
1848
+ } else if (r.status === "fulfilled") {
1849
+ errors.push(`${key}: HTTP ${r.value.status}`);
1850
+ } else {
1851
+ const msg = r.reason instanceof Error ? r.reason.message : String(r.reason);
1852
+ errors.push(`${key}: ${msg}`);
1853
+ }
1854
+ }
1855
+ if (errors.length > 0) {
1856
+ return {
1857
+ data: null,
1858
+ error: new MitwayBaasError(
1859
+ `Failed to delete some objects: ${errors.join("; ")}`,
1860
+ 0,
1861
+ "STORAGE_ERROR"
1862
+ )
1863
+ };
1864
+ }
1865
+ return { data: { removed }, error: null };
1866
+ } catch (err) {
1867
+ return wrapError2(err, "Delete failed");
1868
+ }
1869
+ }
1870
+ async list(opts = {}) {
1871
+ try {
1872
+ const params = {};
1873
+ if (opts.prefix !== void 0) params.prefix = opts.prefix;
1874
+ if (opts.limit !== void 0) params.limit = String(opts.limit);
1875
+ if (opts.startAfter !== void 0) params.start_after = opts.startAfter;
1876
+ const rows = await this.http.get(
1877
+ `${this.bucketBase()}/objects`,
1878
+ { params }
1879
+ );
1880
+ return {
1881
+ data: rows.map((r) => objectFromWire(r, this.bucketName)),
1882
+ error: null
1883
+ };
1884
+ } catch (err) {
1885
+ return wrapError2(err, "List failed");
1886
+ }
1887
+ }
1888
+ async copy(fromKey, toKey, toBucket) {
1889
+ try {
1890
+ const row = await this.http.post(
1891
+ `${this.objectPath(fromKey)}/copy`,
1892
+ {
1893
+ dest_bucket: toBucket ?? this.bucketName,
1894
+ dest_key: toKey
1895
+ }
1896
+ );
1897
+ return {
1898
+ data: objectFromWire(row, toBucket ?? this.bucketName),
1899
+ error: null
1900
+ };
1901
+ } catch (err) {
1902
+ return wrapError2(err, "Copy failed");
1903
+ }
1904
+ }
1905
+ async move(fromKey, toKey, toBucket) {
1906
+ try {
1907
+ const row = await this.http.post(
1908
+ `${this.objectPath(fromKey)}/move`,
1909
+ {
1910
+ dest_bucket: toBucket ?? this.bucketName,
1911
+ dest_key: toKey
1912
+ }
1913
+ );
1914
+ return {
1915
+ data: objectFromWire(row, toBucket ?? this.bucketName),
1916
+ error: null
1917
+ };
1918
+ } catch (err) {
1919
+ return wrapError2(err, "Move failed");
1920
+ }
1921
+ }
1922
+ async createSignedUrl(key, opts = {}) {
1923
+ try {
1924
+ const body = {};
1925
+ if (opts.expiresIn !== void 0) body.expires_in = opts.expiresIn;
1926
+ const wire = await this.http.post(`${this.objectPath(key)}/sign`, body);
1927
+ return {
1928
+ data: {
1929
+ url: wire.url,
1930
+ token: wire.token,
1931
+ expiresAt: wire.expiresAt
1932
+ },
1933
+ error: null
1934
+ };
1935
+ } catch (err) {
1936
+ return wrapError2(err, "Sign failed");
1937
+ }
1938
+ }
1939
+ getPublicUrl(key) {
1940
+ const url = `${this.http.baseUrl.replace(/\/$/, "")}${this.objectPath(key)}`;
1941
+ return { data: { url } };
1942
+ }
1943
+ };
1944
+ var Storage = class {
1945
+ constructor(http) {
1946
+ this.http = http;
1947
+ }
1948
+ http;
1949
+ /** Scope subsequent operations to a single bucket. */
1950
+ from(bucketName) {
1951
+ return new StorageBucketClient(this.http, bucketName);
1952
+ }
1953
+ // --- Admin (require service_role) ---
1954
+ async listBuckets() {
1955
+ try {
1956
+ const rows = await this.http.get("/api/storage/buckets");
1957
+ return { data: rows.map(bucketFromWire), error: null };
1958
+ } catch (err) {
1959
+ return wrapError2(err, "listBuckets failed");
1960
+ }
1961
+ }
1962
+ async getBucket(name) {
1963
+ try {
1964
+ const row = await this.http.get(
1965
+ `/api/storage/buckets/${encodeURIComponent(name)}`
1966
+ );
1967
+ return { data: bucketFromWire(row), error: null };
1968
+ } catch (err) {
1969
+ return wrapError2(err, "getBucket failed");
1970
+ }
1971
+ }
1972
+ async createBucket(name, opts = {}) {
1973
+ try {
1974
+ const body = { name };
1975
+ if (opts.public !== void 0) body.public = opts.public;
1976
+ if (opts.fileSizeLimitBytes !== void 0)
1977
+ body.file_size_limit_bytes = opts.fileSizeLimitBytes;
1978
+ if (opts.allowedMimeTypes !== void 0)
1979
+ body.allowed_mime_types = opts.allowedMimeTypes;
1980
+ const row = await this.http.post(
1981
+ "/api/storage/buckets",
1982
+ body
1983
+ );
1984
+ return { data: bucketFromWire(row), error: null };
1985
+ } catch (err) {
1986
+ return wrapError2(err, "createBucket failed");
1987
+ }
1988
+ }
1989
+ async updateBucket(name, opts) {
1990
+ try {
1991
+ const body = {};
1992
+ if (opts.public !== void 0) body.public = opts.public;
1993
+ if (opts.fileSizeLimitBytes !== void 0)
1994
+ body.file_size_limit_bytes = opts.fileSizeLimitBytes;
1995
+ if (opts.allowedMimeTypes !== void 0)
1996
+ body.allowed_mime_types = opts.allowedMimeTypes;
1997
+ const row = await this.http.patch(
1998
+ `/api/storage/buckets/${encodeURIComponent(name)}`,
1999
+ body
2000
+ );
2001
+ return { data: bucketFromWire(row), error: null };
2002
+ } catch (err) {
2003
+ return wrapError2(err, "updateBucket failed");
2004
+ }
2005
+ }
2006
+ async deleteBucket(name) {
2007
+ try {
2008
+ await this.http.delete(
2009
+ `/api/storage/buckets/${encodeURIComponent(name)}`
2010
+ );
2011
+ return { data: null, error: null };
2012
+ } catch (err) {
2013
+ return wrapError2(err, "deleteBucket failed");
2014
+ }
2015
+ }
2016
+ async emptyBucket(name) {
2017
+ try {
2018
+ const result = await this.http.post(
2019
+ `/api/storage/buckets/${encodeURIComponent(name)}/empty`,
2020
+ {}
2021
+ );
2022
+ return { data: result, error: null };
2023
+ } catch (err) {
2024
+ return wrapError2(err, "emptyBucket failed");
2025
+ }
2026
+ }
2027
+ // --- Storage config (service_role) ---
2028
+ async getConfig() {
2029
+ try {
2030
+ const row = await this.http.get("/api/storage/config");
2031
+ return { data: configFromWire(row), error: null };
2032
+ } catch (err) {
2033
+ return wrapError2(err, "getConfig failed");
2034
+ }
2035
+ }
2036
+ };
2037
+
1516
2038
  // src/client.ts
1517
2039
  var MitwayBaasClient = class {
1518
2040
  http;
@@ -1520,9 +2042,13 @@ var MitwayBaasClient = class {
1520
2042
  auth;
1521
2043
  database;
1522
2044
  realtime;
2045
+ storage;
1523
2046
  constructor(config = {}) {
1524
2047
  const logger = new Logger(config.debug);
1525
- this.tokenManager = new TokenManager();
2048
+ this.tokenManager = new TokenManager({
2049
+ persistSession: config.persistSession,
2050
+ storageKey: config.storageKey
2051
+ });
1526
2052
  this.http = new HttpClient(config, this.tokenManager, logger);
1527
2053
  this.auth = new Auth(this.http, this.tokenManager);
1528
2054
  this.database = new Database(this.http, this.tokenManager, config.anonKey);
@@ -1532,6 +2058,7 @@ var MitwayBaasClient = class {
1532
2058
  config.anonKey,
1533
2059
  config.realtime
1534
2060
  );
2061
+ this.storage = new Storage(this.http);
1535
2062
  }
1536
2063
  /**
1537
2064
  * Escape hatch for callers that need to make custom requests against the
@@ -1557,6 +2084,8 @@ var index_default = MitwayBaasClient;
1557
2084
  MitwayBaasError,
1558
2085
  Realtime,
1559
2086
  RealtimeChannel,
2087
+ Storage,
2088
+ StorageBucketClient,
1560
2089
  TokenManager,
1561
2090
  createClient
1562
2091
  });