@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 +16 -0
- package/dist/index.cjs +530 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +196 -3
- package/dist/index.d.ts +196 -3
- package/dist/index.js +528 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -161,6 +161,7 @@ var Logger = class {
|
|
|
161
161
|
|
|
162
162
|
// src/lib/token-manager.ts
|
|
163
163
|
var CSRF_TOKEN_COOKIE = "mitway_baas_csrf_token";
|
|
164
|
+
var DEFAULT_STORAGE_KEY = "mitway_baas_session";
|
|
164
165
|
function getCsrfToken() {
|
|
165
166
|
if (typeof document === "undefined") return null;
|
|
166
167
|
const match = document.cookie.split(";").find((c) => c.trim().startsWith(`${CSRF_TOKEN_COOKIE}=`));
|
|
@@ -180,13 +181,24 @@ function clearCsrfToken() {
|
|
|
180
181
|
}
|
|
181
182
|
var TokenManager = class {
|
|
182
183
|
accessToken = null;
|
|
184
|
+
refreshToken = null;
|
|
183
185
|
user = null;
|
|
186
|
+
persistSession;
|
|
187
|
+
storageKey;
|
|
184
188
|
/** Fired when the access token changes (used by long-lived consumers). */
|
|
185
189
|
onTokenChange = null;
|
|
190
|
+
constructor(opts) {
|
|
191
|
+
this.persistSession = opts?.persistSession ?? true;
|
|
192
|
+
this.storageKey = opts?.storageKey ?? DEFAULT_STORAGE_KEY;
|
|
193
|
+
}
|
|
186
194
|
saveSession(session) {
|
|
187
195
|
const tokenChanged = session.accessToken !== this.accessToken;
|
|
188
196
|
this.accessToken = session.accessToken;
|
|
189
197
|
this.user = session.user;
|
|
198
|
+
if (session.refreshToken !== void 0) {
|
|
199
|
+
this.refreshToken = session.refreshToken ?? null;
|
|
200
|
+
}
|
|
201
|
+
this.persist();
|
|
190
202
|
if (tokenChanged && this.onTokenChange) {
|
|
191
203
|
this.onTokenChange();
|
|
192
204
|
}
|
|
@@ -195,6 +207,7 @@ var TokenManager = class {
|
|
|
195
207
|
if (!this.accessToken || !this.user) return null;
|
|
196
208
|
return {
|
|
197
209
|
accessToken: this.accessToken,
|
|
210
|
+
refreshToken: this.refreshToken ?? void 0,
|
|
198
211
|
user: this.user
|
|
199
212
|
};
|
|
200
213
|
}
|
|
@@ -204,24 +217,76 @@ var TokenManager = class {
|
|
|
204
217
|
setAccessToken(token) {
|
|
205
218
|
const tokenChanged = token !== this.accessToken;
|
|
206
219
|
this.accessToken = token;
|
|
220
|
+
this.persist();
|
|
207
221
|
if (tokenChanged && this.onTokenChange) {
|
|
208
222
|
this.onTokenChange();
|
|
209
223
|
}
|
|
210
224
|
}
|
|
225
|
+
getRefreshToken() {
|
|
226
|
+
return this.refreshToken;
|
|
227
|
+
}
|
|
228
|
+
setRefreshToken(token) {
|
|
229
|
+
this.refreshToken = token;
|
|
230
|
+
this.persist();
|
|
231
|
+
}
|
|
211
232
|
getUser() {
|
|
212
233
|
return this.user;
|
|
213
234
|
}
|
|
214
235
|
setUser(user) {
|
|
215
236
|
this.user = user;
|
|
237
|
+
this.persist();
|
|
216
238
|
}
|
|
217
239
|
clearSession() {
|
|
218
240
|
const hadToken = this.accessToken !== null;
|
|
219
241
|
this.accessToken = null;
|
|
242
|
+
this.refreshToken = null;
|
|
220
243
|
this.user = null;
|
|
244
|
+
this.removePersisted();
|
|
221
245
|
if (hadToken && this.onTokenChange) {
|
|
222
246
|
this.onTokenChange();
|
|
223
247
|
}
|
|
224
248
|
}
|
|
249
|
+
/**
|
|
250
|
+
* Restore the session from localStorage. Returns true if a persisted
|
|
251
|
+
* session was found and loaded into memory.
|
|
252
|
+
*/
|
|
253
|
+
restoreSession() {
|
|
254
|
+
if (!this.persistSession || typeof localStorage === "undefined") return false;
|
|
255
|
+
try {
|
|
256
|
+
const raw = localStorage.getItem(this.storageKey);
|
|
257
|
+
if (!raw) return false;
|
|
258
|
+
const stored = JSON.parse(raw);
|
|
259
|
+
if (!stored.accessToken || !stored.user) return false;
|
|
260
|
+
this.accessToken = stored.accessToken;
|
|
261
|
+
this.refreshToken = stored.refreshToken ?? null;
|
|
262
|
+
this.user = stored.user;
|
|
263
|
+
return true;
|
|
264
|
+
} catch {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
persist() {
|
|
269
|
+
if (!this.persistSession || typeof localStorage === "undefined") return;
|
|
270
|
+
if (!this.accessToken || !this.user) return;
|
|
271
|
+
try {
|
|
272
|
+
const data = {
|
|
273
|
+
accessToken: this.accessToken,
|
|
274
|
+
user: this.user
|
|
275
|
+
};
|
|
276
|
+
if (this.refreshToken) {
|
|
277
|
+
data.refreshToken = this.refreshToken;
|
|
278
|
+
}
|
|
279
|
+
localStorage.setItem(this.storageKey, JSON.stringify(data));
|
|
280
|
+
} catch {
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
removePersisted() {
|
|
284
|
+
if (!this.persistSession || typeof localStorage === "undefined") return;
|
|
285
|
+
try {
|
|
286
|
+
localStorage.removeItem(this.storageKey);
|
|
287
|
+
} catch {
|
|
288
|
+
}
|
|
289
|
+
}
|
|
225
290
|
};
|
|
226
291
|
|
|
227
292
|
// src/lib/auth-envelope.ts
|
|
@@ -528,6 +593,29 @@ var HttpClient = class {
|
|
|
528
593
|
throw error;
|
|
529
594
|
}
|
|
530
595
|
}
|
|
596
|
+
/**
|
|
597
|
+
* Low-level fetch helper for binary bodies (uploads) and streamed responses
|
|
598
|
+
* (downloads). Applies the current Bearer token (user session → anon key
|
|
599
|
+
* fallback) plus any configured default headers, resolves `path` against
|
|
600
|
+
* `baseUrl`, and returns the raw `Response` — it does NOT unwrap the
|
|
601
|
+
* `{ data, error }` envelope, so the caller is responsible for status
|
|
602
|
+
* checking and parsing.
|
|
603
|
+
*
|
|
604
|
+
* Used by the storage module for object upload/download paths where the
|
|
605
|
+
* body or response is not JSON.
|
|
606
|
+
*/
|
|
607
|
+
async rawFetch(path, init = {}) {
|
|
608
|
+
const url = this.buildUrl(path);
|
|
609
|
+
const headers = new Headers(init.headers ?? {});
|
|
610
|
+
for (const [k, v] of Object.entries(this.defaultHeaders)) {
|
|
611
|
+
if (!headers.has(k)) headers.set(k, v);
|
|
612
|
+
}
|
|
613
|
+
if (!headers.has("Authorization")) {
|
|
614
|
+
const token = this.userToken ?? this.anonKey;
|
|
615
|
+
if (token) headers.set("Authorization", `Bearer ${token}`);
|
|
616
|
+
}
|
|
617
|
+
return this.fetch(url, { ...init, headers });
|
|
618
|
+
}
|
|
531
619
|
get(path, options) {
|
|
532
620
|
return this.request("GET", path, options);
|
|
533
621
|
}
|
|
@@ -618,6 +706,7 @@ var Auth = class {
|
|
|
618
706
|
saveSessionFromResponse(response) {
|
|
619
707
|
const session = {
|
|
620
708
|
accessToken: response.accessToken,
|
|
709
|
+
refreshToken: response.refreshToken,
|
|
621
710
|
user: response.user
|
|
622
711
|
};
|
|
623
712
|
if (response.csrfToken) {
|
|
@@ -711,6 +800,70 @@ var Auth = class {
|
|
|
711
800
|
return wrapError(error, "Session refresh failed");
|
|
712
801
|
}
|
|
713
802
|
}
|
|
803
|
+
/**
|
|
804
|
+
* Restore the session from localStorage and validate it with the backend.
|
|
805
|
+
* Call this once on app startup (e.g. in a React AuthProvider useEffect).
|
|
806
|
+
*
|
|
807
|
+
* Flow:
|
|
808
|
+
* 1. Read persisted session from localStorage.
|
|
809
|
+
* 2. Populate in-memory state (TokenManager + HttpClient).
|
|
810
|
+
* 3. Validate with `GET /api/auth/sessions/current`.
|
|
811
|
+
* - If the access token expired, the HttpClient auto-refresh kicks in
|
|
812
|
+
* using the persisted refresh token (sent in the POST body, not
|
|
813
|
+
* cookies — works cross-site).
|
|
814
|
+
* 4. Return the validated user or an error.
|
|
815
|
+
*
|
|
816
|
+
* If no persisted session exists, returns `{ data: null, error }` — the
|
|
817
|
+
* app should show the login page.
|
|
818
|
+
*/
|
|
819
|
+
async initialize() {
|
|
820
|
+
const restored = this.tokenManager.restoreSession();
|
|
821
|
+
if (!restored) {
|
|
822
|
+
return {
|
|
823
|
+
data: null,
|
|
824
|
+
error: new MitwayBaasError("No persisted session", 0, "NO_SESSION")
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
const session = this.tokenManager.getSession();
|
|
828
|
+
if (!session) {
|
|
829
|
+
return {
|
|
830
|
+
data: null,
|
|
831
|
+
error: new MitwayBaasError("No persisted session", 0, "NO_SESSION")
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
this.http.setAuthToken(session.accessToken);
|
|
835
|
+
const refreshToken = this.tokenManager.getRefreshToken();
|
|
836
|
+
if (refreshToken) {
|
|
837
|
+
this.http.setRefreshToken(refreshToken);
|
|
838
|
+
}
|
|
839
|
+
try {
|
|
840
|
+
const response = await this.http.get(
|
|
841
|
+
"/api/auth/sessions/current"
|
|
842
|
+
);
|
|
843
|
+
if (response?.user) {
|
|
844
|
+
this.tokenManager.setUser(response.user);
|
|
845
|
+
return {
|
|
846
|
+
data: {
|
|
847
|
+
user: response.user,
|
|
848
|
+
accessToken: session.accessToken
|
|
849
|
+
},
|
|
850
|
+
error: null
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
this.tokenManager.clearSession();
|
|
854
|
+
this.http.setAuthToken(null);
|
|
855
|
+
this.http.setRefreshToken(null);
|
|
856
|
+
return {
|
|
857
|
+
data: null,
|
|
858
|
+
error: new MitwayBaasError("Invalid session", 401, "INVALID_SESSION")
|
|
859
|
+
};
|
|
860
|
+
} catch (error) {
|
|
861
|
+
this.tokenManager.clearSession();
|
|
862
|
+
this.http.setAuthToken(null);
|
|
863
|
+
this.http.setRefreshToken(null);
|
|
864
|
+
return wrapError(error, "Session restore failed");
|
|
865
|
+
}
|
|
866
|
+
}
|
|
714
867
|
/**
|
|
715
868
|
* Get the current in-memory session, or null if the user is not signed in.
|
|
716
869
|
* Synchronous — does not hit the network.
|
|
@@ -1477,6 +1630,373 @@ var Realtime = class {
|
|
|
1477
1630
|
}
|
|
1478
1631
|
};
|
|
1479
1632
|
|
|
1633
|
+
// src/modules/storage.ts
|
|
1634
|
+
function bucketFromWire(row) {
|
|
1635
|
+
return {
|
|
1636
|
+
id: row.id,
|
|
1637
|
+
name: row.name,
|
|
1638
|
+
public: row.public,
|
|
1639
|
+
fileSizeLimitBytes: row.file_size_limit_bytes,
|
|
1640
|
+
allowedMimeTypes: row.allowed_mime_types,
|
|
1641
|
+
createdAt: row.created_at,
|
|
1642
|
+
updatedAt: row.updated_at
|
|
1643
|
+
};
|
|
1644
|
+
}
|
|
1645
|
+
function objectFromWire(row, bucketName) {
|
|
1646
|
+
return {
|
|
1647
|
+
id: row.id,
|
|
1648
|
+
bucket: bucketName,
|
|
1649
|
+
key: row.key,
|
|
1650
|
+
size: row.size,
|
|
1651
|
+
mimeType: row.mime_type,
|
|
1652
|
+
etag: row.etag,
|
|
1653
|
+
cacheControl: row.cache_control,
|
|
1654
|
+
contentDisposition: row.content_disposition,
|
|
1655
|
+
uploadedBy: row.uploaded_by,
|
|
1656
|
+
uploadedAt: row.uploaded_at,
|
|
1657
|
+
updatedAt: row.updated_at
|
|
1658
|
+
};
|
|
1659
|
+
}
|
|
1660
|
+
function configFromWire(row) {
|
|
1661
|
+
return {
|
|
1662
|
+
defaultFileSizeLimitBytes: row.default_file_size_limit_bytes,
|
|
1663
|
+
maxFileSizeLimitBytes: row.max_file_size_limit_bytes,
|
|
1664
|
+
tenantStorageQuotaBytes: row.tenant_storage_quota_bytes,
|
|
1665
|
+
reservedSpaceBytes: row.reserved_space_bytes,
|
|
1666
|
+
signedUrlDefaultTtlSec: row.signed_url_default_ttl_sec,
|
|
1667
|
+
signedUrlMaxTtlSec: row.signed_url_max_ttl_sec
|
|
1668
|
+
};
|
|
1669
|
+
}
|
|
1670
|
+
function encodeKey(key) {
|
|
1671
|
+
return key.split("/").map(encodeURIComponent).join("/");
|
|
1672
|
+
}
|
|
1673
|
+
function wrapError2(err, fallback) {
|
|
1674
|
+
if (err instanceof MitwayBaasError) return { data: null, error: err };
|
|
1675
|
+
return {
|
|
1676
|
+
data: null,
|
|
1677
|
+
error: new MitwayBaasError(
|
|
1678
|
+
err instanceof Error ? err.message : fallback,
|
|
1679
|
+
0,
|
|
1680
|
+
"STORAGE_ERROR"
|
|
1681
|
+
)
|
|
1682
|
+
};
|
|
1683
|
+
}
|
|
1684
|
+
async function readEnvelopeError(response) {
|
|
1685
|
+
let code = "STORAGE_ERROR";
|
|
1686
|
+
let message = `HTTP ${response.status}`;
|
|
1687
|
+
try {
|
|
1688
|
+
const body = await response.json();
|
|
1689
|
+
if (body && body.error) {
|
|
1690
|
+
code = body.error.code ?? code;
|
|
1691
|
+
message = body.error.message ?? message;
|
|
1692
|
+
}
|
|
1693
|
+
} catch {
|
|
1694
|
+
}
|
|
1695
|
+
return new MitwayBaasError(message, response.status, code);
|
|
1696
|
+
}
|
|
1697
|
+
var StorageBucketClient = class {
|
|
1698
|
+
constructor(http, bucketName) {
|
|
1699
|
+
this.http = http;
|
|
1700
|
+
this.bucketName = bucketName;
|
|
1701
|
+
}
|
|
1702
|
+
http;
|
|
1703
|
+
bucketName;
|
|
1704
|
+
bucketBase() {
|
|
1705
|
+
return `/api/storage/buckets/${encodeURIComponent(this.bucketName)}`;
|
|
1706
|
+
}
|
|
1707
|
+
objectPath(key) {
|
|
1708
|
+
return `${this.bucketBase()}/objects/${encodeKey(key)}`;
|
|
1709
|
+
}
|
|
1710
|
+
async upload(key, body, opts = {}) {
|
|
1711
|
+
try {
|
|
1712
|
+
const method = opts.upsert ? "PUT" : "POST";
|
|
1713
|
+
const headers = {
|
|
1714
|
+
"Content-Type": opts.contentType ?? "application/octet-stream"
|
|
1715
|
+
};
|
|
1716
|
+
if (opts.cacheControl) headers["Cache-Control"] = opts.cacheControl;
|
|
1717
|
+
if (opts.contentDisposition)
|
|
1718
|
+
headers["Content-Disposition"] = opts.contentDisposition;
|
|
1719
|
+
const response = await this.http.rawFetch(this.objectPath(key), {
|
|
1720
|
+
method,
|
|
1721
|
+
headers,
|
|
1722
|
+
body,
|
|
1723
|
+
signal: opts.abortSignal
|
|
1724
|
+
});
|
|
1725
|
+
if (!response.ok) {
|
|
1726
|
+
return { data: null, error: await readEnvelopeError(response) };
|
|
1727
|
+
}
|
|
1728
|
+
const parsed = await response.json();
|
|
1729
|
+
if (parsed.error || !parsed.data) {
|
|
1730
|
+
return {
|
|
1731
|
+
data: null,
|
|
1732
|
+
error: new MitwayBaasError(
|
|
1733
|
+
parsed.error?.message ?? "Upload failed",
|
|
1734
|
+
response.status,
|
|
1735
|
+
parsed.error?.code ?? "STORAGE_ERROR"
|
|
1736
|
+
)
|
|
1737
|
+
};
|
|
1738
|
+
}
|
|
1739
|
+
return { data: objectFromWire(parsed.data, this.bucketName), error: null };
|
|
1740
|
+
} catch (err) {
|
|
1741
|
+
return wrapError2(err, "Upload failed");
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
async download(key, opts = {}) {
|
|
1745
|
+
try {
|
|
1746
|
+
const headers = {};
|
|
1747
|
+
if (opts.range) {
|
|
1748
|
+
headers["Range"] = `bytes=${opts.range.start}-${opts.range.end}`;
|
|
1749
|
+
}
|
|
1750
|
+
const response = await this.http.rawFetch(this.objectPath(key), {
|
|
1751
|
+
method: "GET",
|
|
1752
|
+
headers,
|
|
1753
|
+
signal: opts.abortSignal
|
|
1754
|
+
});
|
|
1755
|
+
if (!response.ok) {
|
|
1756
|
+
return { data: null, error: await readEnvelopeError(response) };
|
|
1757
|
+
}
|
|
1758
|
+
const blob = await response.blob();
|
|
1759
|
+
return { data: blob, error: null };
|
|
1760
|
+
} catch (err) {
|
|
1761
|
+
return wrapError2(err, "Download failed");
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
async getStream(key, opts = {}) {
|
|
1765
|
+
try {
|
|
1766
|
+
const headers = {};
|
|
1767
|
+
if (opts.range) {
|
|
1768
|
+
headers["Range"] = `bytes=${opts.range.start}-${opts.range.end}`;
|
|
1769
|
+
}
|
|
1770
|
+
const response = await this.http.rawFetch(this.objectPath(key), {
|
|
1771
|
+
method: "GET",
|
|
1772
|
+
headers,
|
|
1773
|
+
signal: opts.abortSignal
|
|
1774
|
+
});
|
|
1775
|
+
if (!response.ok) {
|
|
1776
|
+
return { data: null, error: await readEnvelopeError(response) };
|
|
1777
|
+
}
|
|
1778
|
+
if (!response.body) {
|
|
1779
|
+
return {
|
|
1780
|
+
data: null,
|
|
1781
|
+
error: new MitwayBaasError(
|
|
1782
|
+
"Response body is not a stream",
|
|
1783
|
+
response.status,
|
|
1784
|
+
"STORAGE_ERROR"
|
|
1785
|
+
)
|
|
1786
|
+
};
|
|
1787
|
+
}
|
|
1788
|
+
return {
|
|
1789
|
+
data: response.body,
|
|
1790
|
+
error: null
|
|
1791
|
+
};
|
|
1792
|
+
} catch (err) {
|
|
1793
|
+
return wrapError2(err, "Download failed");
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
async remove(keys) {
|
|
1797
|
+
try {
|
|
1798
|
+
const results = await Promise.allSettled(
|
|
1799
|
+
keys.map(
|
|
1800
|
+
(key) => this.http.rawFetch(this.objectPath(key), { method: "DELETE" })
|
|
1801
|
+
)
|
|
1802
|
+
);
|
|
1803
|
+
const removed = [];
|
|
1804
|
+
const errors = [];
|
|
1805
|
+
for (let i = 0; i < keys.length; i++) {
|
|
1806
|
+
const key = keys[i];
|
|
1807
|
+
const r = results[i];
|
|
1808
|
+
if (r.status === "fulfilled" && r.value.ok) {
|
|
1809
|
+
removed.push(key);
|
|
1810
|
+
} else if (r.status === "fulfilled") {
|
|
1811
|
+
errors.push(`${key}: HTTP ${r.value.status}`);
|
|
1812
|
+
} else {
|
|
1813
|
+
const msg = r.reason instanceof Error ? r.reason.message : String(r.reason);
|
|
1814
|
+
errors.push(`${key}: ${msg}`);
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
if (errors.length > 0) {
|
|
1818
|
+
return {
|
|
1819
|
+
data: null,
|
|
1820
|
+
error: new MitwayBaasError(
|
|
1821
|
+
`Failed to delete some objects: ${errors.join("; ")}`,
|
|
1822
|
+
0,
|
|
1823
|
+
"STORAGE_ERROR"
|
|
1824
|
+
)
|
|
1825
|
+
};
|
|
1826
|
+
}
|
|
1827
|
+
return { data: { removed }, error: null };
|
|
1828
|
+
} catch (err) {
|
|
1829
|
+
return wrapError2(err, "Delete failed");
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
async list(opts = {}) {
|
|
1833
|
+
try {
|
|
1834
|
+
const params = {};
|
|
1835
|
+
if (opts.prefix !== void 0) params.prefix = opts.prefix;
|
|
1836
|
+
if (opts.limit !== void 0) params.limit = String(opts.limit);
|
|
1837
|
+
if (opts.startAfter !== void 0) params.start_after = opts.startAfter;
|
|
1838
|
+
const rows = await this.http.get(
|
|
1839
|
+
`${this.bucketBase()}/objects`,
|
|
1840
|
+
{ params }
|
|
1841
|
+
);
|
|
1842
|
+
return {
|
|
1843
|
+
data: rows.map((r) => objectFromWire(r, this.bucketName)),
|
|
1844
|
+
error: null
|
|
1845
|
+
};
|
|
1846
|
+
} catch (err) {
|
|
1847
|
+
return wrapError2(err, "List failed");
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
async copy(fromKey, toKey, toBucket) {
|
|
1851
|
+
try {
|
|
1852
|
+
const row = await this.http.post(
|
|
1853
|
+
`${this.objectPath(fromKey)}/copy`,
|
|
1854
|
+
{
|
|
1855
|
+
dest_bucket: toBucket ?? this.bucketName,
|
|
1856
|
+
dest_key: toKey
|
|
1857
|
+
}
|
|
1858
|
+
);
|
|
1859
|
+
return {
|
|
1860
|
+
data: objectFromWire(row, toBucket ?? this.bucketName),
|
|
1861
|
+
error: null
|
|
1862
|
+
};
|
|
1863
|
+
} catch (err) {
|
|
1864
|
+
return wrapError2(err, "Copy failed");
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
async move(fromKey, toKey, toBucket) {
|
|
1868
|
+
try {
|
|
1869
|
+
const row = await this.http.post(
|
|
1870
|
+
`${this.objectPath(fromKey)}/move`,
|
|
1871
|
+
{
|
|
1872
|
+
dest_bucket: toBucket ?? this.bucketName,
|
|
1873
|
+
dest_key: toKey
|
|
1874
|
+
}
|
|
1875
|
+
);
|
|
1876
|
+
return {
|
|
1877
|
+
data: objectFromWire(row, toBucket ?? this.bucketName),
|
|
1878
|
+
error: null
|
|
1879
|
+
};
|
|
1880
|
+
} catch (err) {
|
|
1881
|
+
return wrapError2(err, "Move failed");
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
async createSignedUrl(key, opts = {}) {
|
|
1885
|
+
try {
|
|
1886
|
+
const body = {};
|
|
1887
|
+
if (opts.expiresIn !== void 0) body.expires_in = opts.expiresIn;
|
|
1888
|
+
const wire = await this.http.post(`${this.objectPath(key)}/sign`, body);
|
|
1889
|
+
return {
|
|
1890
|
+
data: {
|
|
1891
|
+
url: wire.url,
|
|
1892
|
+
token: wire.token,
|
|
1893
|
+
expiresAt: wire.expiresAt
|
|
1894
|
+
},
|
|
1895
|
+
error: null
|
|
1896
|
+
};
|
|
1897
|
+
} catch (err) {
|
|
1898
|
+
return wrapError2(err, "Sign failed");
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
getPublicUrl(key) {
|
|
1902
|
+
const url = `${this.http.baseUrl.replace(/\/$/, "")}${this.objectPath(key)}`;
|
|
1903
|
+
return { data: { url } };
|
|
1904
|
+
}
|
|
1905
|
+
};
|
|
1906
|
+
var Storage = class {
|
|
1907
|
+
constructor(http) {
|
|
1908
|
+
this.http = http;
|
|
1909
|
+
}
|
|
1910
|
+
http;
|
|
1911
|
+
/** Scope subsequent operations to a single bucket. */
|
|
1912
|
+
from(bucketName) {
|
|
1913
|
+
return new StorageBucketClient(this.http, bucketName);
|
|
1914
|
+
}
|
|
1915
|
+
// --- Admin (require service_role) ---
|
|
1916
|
+
async listBuckets() {
|
|
1917
|
+
try {
|
|
1918
|
+
const rows = await this.http.get("/api/storage/buckets");
|
|
1919
|
+
return { data: rows.map(bucketFromWire), error: null };
|
|
1920
|
+
} catch (err) {
|
|
1921
|
+
return wrapError2(err, "listBuckets failed");
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
async getBucket(name) {
|
|
1925
|
+
try {
|
|
1926
|
+
const row = await this.http.get(
|
|
1927
|
+
`/api/storage/buckets/${encodeURIComponent(name)}`
|
|
1928
|
+
);
|
|
1929
|
+
return { data: bucketFromWire(row), error: null };
|
|
1930
|
+
} catch (err) {
|
|
1931
|
+
return wrapError2(err, "getBucket failed");
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
async createBucket(name, opts = {}) {
|
|
1935
|
+
try {
|
|
1936
|
+
const body = { name };
|
|
1937
|
+
if (opts.public !== void 0) body.public = opts.public;
|
|
1938
|
+
if (opts.fileSizeLimitBytes !== void 0)
|
|
1939
|
+
body.file_size_limit_bytes = opts.fileSizeLimitBytes;
|
|
1940
|
+
if (opts.allowedMimeTypes !== void 0)
|
|
1941
|
+
body.allowed_mime_types = opts.allowedMimeTypes;
|
|
1942
|
+
const row = await this.http.post(
|
|
1943
|
+
"/api/storage/buckets",
|
|
1944
|
+
body
|
|
1945
|
+
);
|
|
1946
|
+
return { data: bucketFromWire(row), error: null };
|
|
1947
|
+
} catch (err) {
|
|
1948
|
+
return wrapError2(err, "createBucket failed");
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
async updateBucket(name, opts) {
|
|
1952
|
+
try {
|
|
1953
|
+
const body = {};
|
|
1954
|
+
if (opts.public !== void 0) body.public = opts.public;
|
|
1955
|
+
if (opts.fileSizeLimitBytes !== void 0)
|
|
1956
|
+
body.file_size_limit_bytes = opts.fileSizeLimitBytes;
|
|
1957
|
+
if (opts.allowedMimeTypes !== void 0)
|
|
1958
|
+
body.allowed_mime_types = opts.allowedMimeTypes;
|
|
1959
|
+
const row = await this.http.patch(
|
|
1960
|
+
`/api/storage/buckets/${encodeURIComponent(name)}`,
|
|
1961
|
+
body
|
|
1962
|
+
);
|
|
1963
|
+
return { data: bucketFromWire(row), error: null };
|
|
1964
|
+
} catch (err) {
|
|
1965
|
+
return wrapError2(err, "updateBucket failed");
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
async deleteBucket(name) {
|
|
1969
|
+
try {
|
|
1970
|
+
await this.http.delete(
|
|
1971
|
+
`/api/storage/buckets/${encodeURIComponent(name)}`
|
|
1972
|
+
);
|
|
1973
|
+
return { data: null, error: null };
|
|
1974
|
+
} catch (err) {
|
|
1975
|
+
return wrapError2(err, "deleteBucket failed");
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
async emptyBucket(name) {
|
|
1979
|
+
try {
|
|
1980
|
+
const result = await this.http.post(
|
|
1981
|
+
`/api/storage/buckets/${encodeURIComponent(name)}/empty`,
|
|
1982
|
+
{}
|
|
1983
|
+
);
|
|
1984
|
+
return { data: result, error: null };
|
|
1985
|
+
} catch (err) {
|
|
1986
|
+
return wrapError2(err, "emptyBucket failed");
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
// --- Storage config (service_role) ---
|
|
1990
|
+
async getConfig() {
|
|
1991
|
+
try {
|
|
1992
|
+
const row = await this.http.get("/api/storage/config");
|
|
1993
|
+
return { data: configFromWire(row), error: null };
|
|
1994
|
+
} catch (err) {
|
|
1995
|
+
return wrapError2(err, "getConfig failed");
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
};
|
|
1999
|
+
|
|
1480
2000
|
// src/client.ts
|
|
1481
2001
|
var MitwayBaasClient = class {
|
|
1482
2002
|
http;
|
|
@@ -1484,9 +2004,13 @@ var MitwayBaasClient = class {
|
|
|
1484
2004
|
auth;
|
|
1485
2005
|
database;
|
|
1486
2006
|
realtime;
|
|
2007
|
+
storage;
|
|
1487
2008
|
constructor(config = {}) {
|
|
1488
2009
|
const logger = new Logger(config.debug);
|
|
1489
|
-
this.tokenManager = new TokenManager(
|
|
2010
|
+
this.tokenManager = new TokenManager({
|
|
2011
|
+
persistSession: config.persistSession,
|
|
2012
|
+
storageKey: config.storageKey
|
|
2013
|
+
});
|
|
1490
2014
|
this.http = new HttpClient(config, this.tokenManager, logger);
|
|
1491
2015
|
this.auth = new Auth(this.http, this.tokenManager);
|
|
1492
2016
|
this.database = new Database(this.http, this.tokenManager, config.anonKey);
|
|
@@ -1496,6 +2020,7 @@ var MitwayBaasClient = class {
|
|
|
1496
2020
|
config.anonKey,
|
|
1497
2021
|
config.realtime
|
|
1498
2022
|
);
|
|
2023
|
+
this.storage = new Storage(this.http);
|
|
1499
2024
|
}
|
|
1500
2025
|
/**
|
|
1501
2026
|
* Escape hatch for callers that need to make custom requests against the
|
|
@@ -1520,6 +2045,8 @@ export {
|
|
|
1520
2045
|
MitwayBaasError,
|
|
1521
2046
|
Realtime,
|
|
1522
2047
|
RealtimeChannel,
|
|
2048
|
+
Storage,
|
|
2049
|
+
StorageBucketClient,
|
|
1523
2050
|
TokenManager,
|
|
1524
2051
|
createClient,
|
|
1525
2052
|
index_default as default
|