@giaeulate/baas-sdk 1.2.0 → 1.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/CHANGELOG.md +38 -0
- package/LICENSE +21 -0
- package/README.md +54 -0
- package/dist/cli.js +448 -0
- package/dist/index.cjs +574 -37
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +200 -12
- package/dist/index.d.ts +200 -12
- package/dist/index.js +574 -37
- package/dist/index.js.map +1 -0
- package/package.json +43 -18
package/dist/index.cjs
CHANGED
|
@@ -30,14 +30,31 @@ module.exports = __toCommonJS(index_exports);
|
|
|
30
30
|
var HttpClient = class {
|
|
31
31
|
url;
|
|
32
32
|
apiKey = "";
|
|
33
|
+
keyType;
|
|
33
34
|
token = null;
|
|
35
|
+
refreshToken = null;
|
|
34
36
|
environment = "prod";
|
|
35
37
|
_onForceLogout = null;
|
|
36
|
-
|
|
38
|
+
_refreshing = null;
|
|
39
|
+
constructor(url, apiKey, options) {
|
|
37
40
|
let baseUrl = url || (typeof window !== "undefined" ? window.location.origin : "http://localhost:8080");
|
|
38
41
|
this.url = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
39
42
|
if (apiKey) this.apiKey = apiKey;
|
|
40
|
-
this.
|
|
43
|
+
this.keyType = options?.keyType ?? "service_role";
|
|
44
|
+
this.warnIfUnsafeKey();
|
|
45
|
+
this.token = typeof localStorage !== "undefined" ? this.cleanValue(localStorage.getItem("baas_token")) : null;
|
|
46
|
+
this.refreshToken = typeof localStorage !== "undefined" ? this.cleanValue(localStorage.getItem("baas_refresh_token")) : null;
|
|
47
|
+
}
|
|
48
|
+
/** SECURITY: a service_role key bypasses RLS and grants admin over every
|
|
49
|
+
* tenant. If it ends up in a browser bundle, anyone can extract it. Warn
|
|
50
|
+
* loudly when a non-anon key is constructed in a browser context. */
|
|
51
|
+
warnIfUnsafeKey() {
|
|
52
|
+
const inBrowser = typeof window !== "undefined" && typeof document !== "undefined";
|
|
53
|
+
if (inBrowser && this.apiKey && this.keyType !== "anon") {
|
|
54
|
+
console.warn(
|
|
55
|
+
'[baas-sdk] SECURITY WARNING: a non-anon (service_role) key is running in a browser. It bypasses Row-Level Security and grants admin over ALL tenants, and is extractable from the bundle. Mint an anon key in the dashboard and construct the client with { keyType: "anon" }.'
|
|
56
|
+
);
|
|
57
|
+
}
|
|
41
58
|
}
|
|
42
59
|
/**
|
|
43
60
|
* Set the auth token manually (e.g. restoring from SecureStore in React Native).
|
|
@@ -45,12 +62,59 @@ var HttpClient = class {
|
|
|
45
62
|
*/
|
|
46
63
|
setToken(token) {
|
|
47
64
|
this.token = token;
|
|
65
|
+
if (typeof localStorage === "undefined") return;
|
|
48
66
|
if (token) {
|
|
49
67
|
localStorage.setItem("baas_token", token);
|
|
50
68
|
} else {
|
|
51
69
|
localStorage.removeItem("baas_token");
|
|
52
70
|
}
|
|
53
71
|
}
|
|
72
|
+
/** Persist the rotating refresh token (used by auto-refresh on 401). In the
|
|
73
|
+
* browser the server also sets an HttpOnly `baas_refresh` cookie, so storing
|
|
74
|
+
* it here is mainly for non-cookie clients (React Native). */
|
|
75
|
+
setRefreshToken(token) {
|
|
76
|
+
this.refreshToken = token;
|
|
77
|
+
if (typeof localStorage === "undefined") return;
|
|
78
|
+
if (token) {
|
|
79
|
+
localStorage.setItem("baas_refresh_token", token);
|
|
80
|
+
} else {
|
|
81
|
+
localStorage.removeItem("baas_refresh_token");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
getRefreshToken() {
|
|
85
|
+
const ls = typeof localStorage !== "undefined" ? localStorage.getItem("baas_refresh_token") : null;
|
|
86
|
+
return this.cleanValue(this.refreshToken || ls);
|
|
87
|
+
}
|
|
88
|
+
/** Attempts a single token refresh, deduped across concurrent 401s. Sends the
|
|
89
|
+
* stored refresh token (falls back to the HttpOnly cookie in browsers). */
|
|
90
|
+
tryRefresh() {
|
|
91
|
+
if (!this._refreshing) {
|
|
92
|
+
this._refreshing = this.doRefresh().finally(() => {
|
|
93
|
+
this._refreshing = null;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return this._refreshing;
|
|
97
|
+
}
|
|
98
|
+
async doRefresh() {
|
|
99
|
+
try {
|
|
100
|
+
const body = {};
|
|
101
|
+
const rt = this.getRefreshToken();
|
|
102
|
+
if (rt) body.refresh_token = rt;
|
|
103
|
+
const res = await fetch(`${this.url}/api/auth/refresh`, {
|
|
104
|
+
method: "POST",
|
|
105
|
+
headers: { "Content-Type": "application/json", apikey: this.apiKey },
|
|
106
|
+
credentials: "include",
|
|
107
|
+
body: JSON.stringify(body)
|
|
108
|
+
});
|
|
109
|
+
if (!res.ok) return false;
|
|
110
|
+
const data = await res.json().catch(() => null);
|
|
111
|
+
if (data?.token) this.setToken(data.token);
|
|
112
|
+
if (data?.refresh_token) this.setRefreshToken(data.refresh_token);
|
|
113
|
+
return Boolean(data?.token);
|
|
114
|
+
} catch {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
54
118
|
/**
|
|
55
119
|
* Register a callback invoked on forced logout (401).
|
|
56
120
|
* Use this in React Native instead of the default window.location redirect.
|
|
@@ -91,7 +155,7 @@ var HttpClient = class {
|
|
|
91
155
|
/**
|
|
92
156
|
* Core HTTP request method - DRY principle
|
|
93
157
|
*/
|
|
94
|
-
async request(endpoint, options = {}) {
|
|
158
|
+
async request(endpoint, options = {}, _isRetry = false) {
|
|
95
159
|
const { method = "GET", body, headers: customHeaders, skipAuth = false } = options;
|
|
96
160
|
const headers = { ...this.getHeaders(), ...customHeaders };
|
|
97
161
|
const fetchOptions = {
|
|
@@ -109,12 +173,22 @@ var HttpClient = class {
|
|
|
109
173
|
const data = await res.json().catch(() => null);
|
|
110
174
|
if (!res.ok) {
|
|
111
175
|
if (res.status === 401 && !skipAuth) {
|
|
176
|
+
if (!_isRetry && await this.tryRefresh()) {
|
|
177
|
+
return this.request(endpoint, options, true);
|
|
178
|
+
}
|
|
112
179
|
this.forceLogout();
|
|
113
180
|
}
|
|
114
181
|
if (res.status === 403 && data?.error === "ip_not_allowed") {
|
|
115
182
|
this.handleIPBlocked(data.ip);
|
|
116
183
|
}
|
|
117
|
-
|
|
184
|
+
const rateLimited = res.status === 429;
|
|
185
|
+
const retryHint = res.headers.get("retry-after") || res.headers.get("x-ratelimit-reset");
|
|
186
|
+
return {
|
|
187
|
+
error: data?.error || `Request failed: ${res.status}`,
|
|
188
|
+
status: res.status,
|
|
189
|
+
...rateLimited ? { rateLimited: true, retryAfter: retryHint ? parseInt(retryHint, 10) : void 0 } : {},
|
|
190
|
+
...data
|
|
191
|
+
};
|
|
118
192
|
}
|
|
119
193
|
return data;
|
|
120
194
|
}
|
|
@@ -146,7 +220,11 @@ var HttpClient = class {
|
|
|
146
220
|
}
|
|
147
221
|
logout() {
|
|
148
222
|
this.token = null;
|
|
149
|
-
|
|
223
|
+
this.refreshToken = null;
|
|
224
|
+
if (typeof localStorage !== "undefined") {
|
|
225
|
+
localStorage.removeItem("baas_token");
|
|
226
|
+
localStorage.removeItem("baas_refresh_token");
|
|
227
|
+
}
|
|
150
228
|
}
|
|
151
229
|
forceLogout() {
|
|
152
230
|
this.logout();
|
|
@@ -231,10 +309,49 @@ var QueryBuilder = class {
|
|
|
231
309
|
this.queryParams.set(column, `not.${operator}.${value}`);
|
|
232
310
|
return this;
|
|
233
311
|
}
|
|
312
|
+
/** Array/jsonb/range contains (@>). Arrays become a `{a,b}` literal. */
|
|
313
|
+
contains(column, values) {
|
|
314
|
+
this.queryParams.set(column, `cs.${this.arrayLiteral(values)}`);
|
|
315
|
+
return this;
|
|
316
|
+
}
|
|
317
|
+
/** Array/jsonb/range contained-by (<@). */
|
|
318
|
+
containedBy(column, values) {
|
|
319
|
+
this.queryParams.set(column, `cd.${this.arrayLiteral(values)}`);
|
|
320
|
+
return this;
|
|
321
|
+
}
|
|
322
|
+
/** Array/range overlap (&&). */
|
|
323
|
+
overlaps(column, values) {
|
|
324
|
+
this.queryParams.set(column, `ov.${this.arrayLiteral(values)}`);
|
|
325
|
+
return this;
|
|
326
|
+
}
|
|
327
|
+
/** Full-text search (@@). type: fts|plfts|phfts|wfts (tsquery flavour). */
|
|
328
|
+
textSearch(column, query, type = "fts") {
|
|
329
|
+
this.queryParams.set(column, `${type}.${query}`);
|
|
330
|
+
return this;
|
|
331
|
+
}
|
|
332
|
+
arrayLiteral(values) {
|
|
333
|
+
return Array.isArray(values) ? `{${values.join(",")}}` : `${values}`;
|
|
334
|
+
}
|
|
335
|
+
/** OR group: `or=(c1,c2)`. Members are dotted `col.op.value` strings. */
|
|
234
336
|
or(filters) {
|
|
235
337
|
this.queryParams.set("or", `(${filters})`);
|
|
236
338
|
return this;
|
|
237
339
|
}
|
|
340
|
+
/** AND group: `and=(c1,c2)` — e.g. a range `qty.gt.5,qty.lt.20`. */
|
|
341
|
+
and(filters) {
|
|
342
|
+
this.queryParams.set("and", `(${filters})`);
|
|
343
|
+
return this;
|
|
344
|
+
}
|
|
345
|
+
/** Negated AND group: `not.and=(...)`. */
|
|
346
|
+
notAnd(filters) {
|
|
347
|
+
this.queryParams.set("not.and", `(${filters})`);
|
|
348
|
+
return this;
|
|
349
|
+
}
|
|
350
|
+
/** Negated OR group: `not.or=(...)`. */
|
|
351
|
+
notOr(filters) {
|
|
352
|
+
this.queryParams.set("not.or", `(${filters})`);
|
|
353
|
+
return this;
|
|
354
|
+
}
|
|
238
355
|
order(column, { ascending = true } = {}) {
|
|
239
356
|
this.queryParams.set("order", `${column}.${ascending ? "asc" : "desc"}`);
|
|
240
357
|
return this;
|
|
@@ -278,6 +395,34 @@ var QueryBuilder = class {
|
|
|
278
395
|
if (res.status === 204) return { success: true };
|
|
279
396
|
return await this.handleResponse(res);
|
|
280
397
|
}
|
|
398
|
+
/** Insert-or-update on conflict. [onConflict] is the conflict-target column(s). */
|
|
399
|
+
async upsert(data, onConflict) {
|
|
400
|
+
const res = await fetch(`${this.url}/api/v1/data/${this.table}/upsert`, {
|
|
401
|
+
method: "POST",
|
|
402
|
+
headers: this.getHeaders(),
|
|
403
|
+
body: JSON.stringify({ ...data, on_conflict: onConflict })
|
|
404
|
+
});
|
|
405
|
+
return await this.handleResponse(res);
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Count rows matching the current filters (PostgREST parity). Sends
|
|
409
|
+
* `Prefer: count=exact` and parses the total from the `Content-Range`
|
|
410
|
+
* response header. Returns `{ count, error, status }`.
|
|
411
|
+
*/
|
|
412
|
+
async count() {
|
|
413
|
+
const res = await fetch(`${this.url}/api/v1/data/${this.table}?${this.queryParams.toString()}`, {
|
|
414
|
+
headers: { ...this.getHeaders(), Prefer: "count=exact", "Cache-Control": "no-cache" },
|
|
415
|
+
cache: "no-store"
|
|
416
|
+
});
|
|
417
|
+
if (!res.ok) {
|
|
418
|
+
if (res.status === 401) this.client.forceLogout();
|
|
419
|
+
const body = await res.json().catch(() => null);
|
|
420
|
+
return { count: 0, error: body?.error || "Request failed", status: res.status };
|
|
421
|
+
}
|
|
422
|
+
const cr = res.headers.get("content-range");
|
|
423
|
+
const total = cr && cr.includes("/") ? parseInt(cr.split("/").pop() || "", 10) : NaN;
|
|
424
|
+
return { count: Number.isNaN(total) ? 0 : total, error: null, status: res.status };
|
|
425
|
+
}
|
|
281
426
|
getHeaders() {
|
|
282
427
|
return this.client.getHeaders();
|
|
283
428
|
}
|
|
@@ -320,18 +465,65 @@ function createAuthModule(client) {
|
|
|
320
465
|
throw new Error(data?.error || `Login failed: ${res.status}`);
|
|
321
466
|
}
|
|
322
467
|
if (data.token) {
|
|
323
|
-
client.
|
|
324
|
-
|
|
468
|
+
client.setToken(data.token);
|
|
469
|
+
}
|
|
470
|
+
if (data.refresh_token) {
|
|
471
|
+
client.setRefreshToken(data.refresh_token);
|
|
325
472
|
}
|
|
326
473
|
return data;
|
|
327
474
|
},
|
|
475
|
+
/** Manually refresh the access token via the rotating refresh token. The
|
|
476
|
+
* HttpClient also does this automatically on a 401. */
|
|
477
|
+
async refresh() {
|
|
478
|
+
const rt = client.refreshToken;
|
|
479
|
+
const data = await post("/api/auth/refresh", rt ? { refresh_token: rt } : {});
|
|
480
|
+
if (data?.token) client.setToken(data.token);
|
|
481
|
+
if (data?.refresh_token) client.setRefreshToken(data.refresh_token);
|
|
482
|
+
return data;
|
|
483
|
+
},
|
|
328
484
|
async logout() {
|
|
329
485
|
try {
|
|
330
486
|
await post("/api/logout");
|
|
331
487
|
} finally {
|
|
332
|
-
client.
|
|
333
|
-
|
|
488
|
+
client.logout();
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
// Anonymous sign-in (Supabase parity). Response carries access_token (+ a
|
|
492
|
+
// back-compat `token`); store whichever is present.
|
|
493
|
+
async signInAnonymous() {
|
|
494
|
+
const data = await post("/api/auth/anonymous");
|
|
495
|
+
const tok = data?.access_token || data?.token;
|
|
496
|
+
if (tok) {
|
|
497
|
+
client.token = tok;
|
|
498
|
+
localStorage.setItem("baas_token", tok);
|
|
499
|
+
}
|
|
500
|
+
return data;
|
|
501
|
+
},
|
|
502
|
+
// Passwordless: request a magic link + 6-digit email OTP. Always resolves on
|
|
503
|
+
// a well-formed request (no account enumeration).
|
|
504
|
+
async requestOtp(email, createUser = true) {
|
|
505
|
+
return post("/api/auth/otp", { email, create_user: createUser });
|
|
506
|
+
},
|
|
507
|
+
// Verify a passwordless OTP/magic-link and complete sign-in.
|
|
508
|
+
async verifyOtp(type, email, token) {
|
|
509
|
+
const data = await post("/api/auth/verify", { type, email, token });
|
|
510
|
+
const tok = data?.access_token || data?.token;
|
|
511
|
+
if (tok) {
|
|
512
|
+
client.token = tok;
|
|
513
|
+
localStorage.setItem("baas_token", tok);
|
|
514
|
+
}
|
|
515
|
+
return data;
|
|
516
|
+
},
|
|
517
|
+
// Upgrade the current anonymous guest to a real account (preserves user id).
|
|
518
|
+
// Requires the anonymous Bearer to be set (call after signInAnonymous).
|
|
519
|
+
async upgradeAnonymous(email, password) {
|
|
520
|
+
const data = await post("/api/auth/upgrade", { email, password });
|
|
521
|
+
const tok = data?.access_token || data?.token;
|
|
522
|
+
if (tok) {
|
|
523
|
+
client.token = tok;
|
|
524
|
+
localStorage.setItem("baas_token", tok);
|
|
334
525
|
}
|
|
526
|
+
return data;
|
|
335
527
|
},
|
|
336
528
|
async verifyMFA(mfaToken, code) {
|
|
337
529
|
const data = await post("/api/mfa/finalize", { mfa_token: mfaToken, code });
|
|
@@ -530,6 +722,7 @@ function createDatabaseModule(client) {
|
|
|
530
722
|
function createStorageModule(client) {
|
|
531
723
|
const get = (endpoint) => client.get(endpoint);
|
|
532
724
|
const post = (endpoint, body) => client.post(endpoint, body);
|
|
725
|
+
const put = (endpoint, body) => client.put(endpoint, body);
|
|
533
726
|
const del = (endpoint) => client.delete(endpoint);
|
|
534
727
|
async function uploadFile(fileOrTable, fileOrBucketId) {
|
|
535
728
|
let file;
|
|
@@ -564,14 +757,24 @@ function createStorageModule(client) {
|
|
|
564
757
|
async listFiles() {
|
|
565
758
|
return get("/api/storage/files");
|
|
566
759
|
},
|
|
567
|
-
async getFile(fileId) {
|
|
568
|
-
|
|
760
|
+
async getFile(fileId, expiresIn) {
|
|
761
|
+
const q = expiresIn && expiresIn > 0 ? `?expiresIn=${expiresIn}` : "";
|
|
762
|
+
return get(`/api/storage/files/${fileId}${q}`);
|
|
763
|
+
},
|
|
764
|
+
async createSignedUrl(fileId, expiresIn) {
|
|
765
|
+
const res = await get(
|
|
766
|
+
`/api/storage/files/${fileId}${expiresIn && expiresIn > 0 ? `?expiresIn=${expiresIn}` : ""}`
|
|
767
|
+
);
|
|
768
|
+
return { signedUrl: res?.url };
|
|
569
769
|
},
|
|
570
770
|
async deleteFile(fileId) {
|
|
571
771
|
return del(`/api/storage/files/${fileId}`);
|
|
572
772
|
},
|
|
573
|
-
async createBucket(name, isPublic = false) {
|
|
574
|
-
return post("/api/storage/buckets", { name, is_public: isPublic });
|
|
773
|
+
async createBucket(name, isPublic = false, maxBytes) {
|
|
774
|
+
return post("/api/storage/buckets", { name, is_public: isPublic, max_bytes: maxBytes });
|
|
775
|
+
},
|
|
776
|
+
async updateBucket(bucketId, opts) {
|
|
777
|
+
return put(`/api/storage/buckets/${bucketId}`, { is_public: opts.isPublic, max_bytes: opts.maxBytes });
|
|
575
778
|
},
|
|
576
779
|
async listBuckets() {
|
|
577
780
|
return get("/api/storage/buckets");
|
|
@@ -584,6 +787,17 @@ function createStorageModule(client) {
|
|
|
584
787
|
},
|
|
585
788
|
async listBucketFiles(bucketId) {
|
|
586
789
|
return get(`/api/storage/buckets/${bucketId}/files`);
|
|
790
|
+
},
|
|
791
|
+
async download(fileId) {
|
|
792
|
+
const res = await fetch(`${client.url}/api/storage/files/${fileId}/download`, {
|
|
793
|
+
headers: client.getHeaders("")
|
|
794
|
+
});
|
|
795
|
+
if (!res.ok) {
|
|
796
|
+
if (res.status === 401) client.forceLogout();
|
|
797
|
+
const err = await res.json().catch(() => ({ error: `Download failed: ${res.status}` }));
|
|
798
|
+
throw new Error(err.error ?? `Download failed: ${res.status}`);
|
|
799
|
+
}
|
|
800
|
+
return res.blob();
|
|
587
801
|
}
|
|
588
802
|
};
|
|
589
803
|
}
|
|
@@ -684,7 +898,9 @@ function createFunctionsModule(client) {
|
|
|
684
898
|
return del(`/api/functions/${id}`);
|
|
685
899
|
},
|
|
686
900
|
async update(id, code, options) {
|
|
687
|
-
|
|
901
|
+
const current = await get(`/api/functions/${id}`);
|
|
902
|
+
const name = current?.name ?? current?.data?.name;
|
|
903
|
+
return post("/api/functions", { name, code, ...options });
|
|
688
904
|
},
|
|
689
905
|
async executeCode(code, options) {
|
|
690
906
|
return post("/api/functions/execute", { code, ...options });
|
|
@@ -775,9 +991,18 @@ function createEmailModule(client) {
|
|
|
775
991
|
async getConfig() {
|
|
776
992
|
return get("/api/email/config");
|
|
777
993
|
},
|
|
994
|
+
async listConfigs() {
|
|
995
|
+
return get("/api/email/configs");
|
|
996
|
+
},
|
|
778
997
|
async saveConfig(config) {
|
|
779
998
|
return post("/api/email/config", config);
|
|
780
999
|
},
|
|
1000
|
+
async setDefaultConfig(id) {
|
|
1001
|
+
return post(`/api/email/configs/${id}/default`);
|
|
1002
|
+
},
|
|
1003
|
+
async deleteConfig(id) {
|
|
1004
|
+
return del(`/api/email/configs/${id}`);
|
|
1005
|
+
},
|
|
781
1006
|
async createTemplate(template) {
|
|
782
1007
|
return post("/api/email/templates", template);
|
|
783
1008
|
},
|
|
@@ -830,7 +1055,9 @@ function createGraphQLModule(client) {
|
|
|
830
1055
|
return post("/api/graphql", { query, variables });
|
|
831
1056
|
},
|
|
832
1057
|
async getSchema() {
|
|
833
|
-
return
|
|
1058
|
+
return post("/api/graphql", {
|
|
1059
|
+
query: "{ __schema { queryType { name } mutationType { name } types { name kind } } }"
|
|
1060
|
+
});
|
|
834
1061
|
},
|
|
835
1062
|
getPlaygroundUrl() {
|
|
836
1063
|
return `${client.url}/api/graphql/playground`;
|
|
@@ -876,6 +1103,7 @@ function createMetricsModule(client) {
|
|
|
876
1103
|
// src/modules/audit.ts
|
|
877
1104
|
function createAuditModule(client) {
|
|
878
1105
|
const get = (endpoint) => client.get(endpoint);
|
|
1106
|
+
const del = (endpoint) => client.delete(endpoint);
|
|
879
1107
|
return {
|
|
880
1108
|
async list(options) {
|
|
881
1109
|
const params = new URLSearchParams();
|
|
@@ -892,6 +1120,43 @@ function createAuditModule(client) {
|
|
|
892
1120
|
},
|
|
893
1121
|
async getActions() {
|
|
894
1122
|
return get("/api/audit/actions");
|
|
1123
|
+
},
|
|
1124
|
+
async exportLogs(format, options) {
|
|
1125
|
+
const params = new URLSearchParams();
|
|
1126
|
+
params.set("format", format);
|
|
1127
|
+
if (options?.action) params.set("action", options.action);
|
|
1128
|
+
if (options?.method) params.set("method", options.method);
|
|
1129
|
+
if (options?.path) params.set("path", options.path);
|
|
1130
|
+
if (options?.status_code) params.set("status_code", options.status_code.toString());
|
|
1131
|
+
if (options?.start_date) params.set("start_date", options.start_date);
|
|
1132
|
+
if (options?.end_date) params.set("end_date", options.end_date);
|
|
1133
|
+
const res = await fetch(`${client.url}/api/audit/export?${params.toString()}`, {
|
|
1134
|
+
headers: client.getHeaders(),
|
|
1135
|
+
credentials: "include"
|
|
1136
|
+
});
|
|
1137
|
+
if (!res.ok) {
|
|
1138
|
+
const data = await res.json();
|
|
1139
|
+
return { error: data.error || `Export failed: ${res.status}` };
|
|
1140
|
+
}
|
|
1141
|
+
const blob = await res.blob();
|
|
1142
|
+
const url = window.URL.createObjectURL(blob);
|
|
1143
|
+
const a = document.createElement("a");
|
|
1144
|
+
a.href = url;
|
|
1145
|
+
a.download = `audit_logs_${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.${format}`;
|
|
1146
|
+
document.body.appendChild(a);
|
|
1147
|
+
a.click();
|
|
1148
|
+
window.URL.revokeObjectURL(url);
|
|
1149
|
+
document.body.removeChild(a);
|
|
1150
|
+
return { success: true };
|
|
1151
|
+
},
|
|
1152
|
+
async purgePreview(olderThanDays) {
|
|
1153
|
+
return get(`/api/audit/purge/preview?older_than_days=${olderThanDays}`);
|
|
1154
|
+
},
|
|
1155
|
+
async purge(olderThanDays) {
|
|
1156
|
+
return del(`/api/audit/purge?older_than_days=${olderThanDays}`);
|
|
1157
|
+
},
|
|
1158
|
+
async clearAll() {
|
|
1159
|
+
return del(`/api/audit/clear`);
|
|
895
1160
|
}
|
|
896
1161
|
};
|
|
897
1162
|
}
|
|
@@ -992,6 +1257,16 @@ var RealtimeService = class {
|
|
|
992
1257
|
}
|
|
993
1258
|
socket = null;
|
|
994
1259
|
subscribers = /* @__PURE__ */ new Map();
|
|
1260
|
+
// The server-side value filter currently REGISTERED per table (e.g.
|
|
1261
|
+
// { status: 'eq.pending' }). The server keeps one filter per (connection,table),
|
|
1262
|
+
// so this is only set when every subscriber on the table shares the same filter;
|
|
1263
|
+
// otherwise the table runs unfiltered server-side and each subscriber's filter is
|
|
1264
|
+
// applied client-side in handleEvent (see computeServerFilter).
|
|
1265
|
+
filters = /* @__PURE__ */ new Map();
|
|
1266
|
+
// Ephemeral Broadcast / Presence channel handlers.
|
|
1267
|
+
broadcastHandlers = /* @__PURE__ */ new Map();
|
|
1268
|
+
presenceHandlers = /* @__PURE__ */ new Map();
|
|
1269
|
+
presenceStates = /* @__PURE__ */ new Map();
|
|
995
1270
|
reconnectAttempts = 0;
|
|
996
1271
|
maxReconnectAttempts = 5;
|
|
997
1272
|
reconnectInterval = 3e3;
|
|
@@ -1017,6 +1292,15 @@ var RealtimeService = class {
|
|
|
1017
1292
|
socket.onopen = () => {
|
|
1018
1293
|
console.log("BaaS Realtime: Connected");
|
|
1019
1294
|
this.reconnectAttempts = 0;
|
|
1295
|
+
for (const table of this.subscribers.keys()) {
|
|
1296
|
+
this.sendSub("subscribe", table);
|
|
1297
|
+
}
|
|
1298
|
+
for (const channel of this.broadcastHandlers.keys()) {
|
|
1299
|
+
this.send({ event: "broadcast_subscribe", channel });
|
|
1300
|
+
}
|
|
1301
|
+
for (const channel of this.presenceHandlers.keys()) {
|
|
1302
|
+
this.send({ event: "presence_subscribe", channel, state: this.presenceStates.get(channel) ?? {} });
|
|
1303
|
+
}
|
|
1020
1304
|
resolve();
|
|
1021
1305
|
};
|
|
1022
1306
|
socket.onmessage = (event) => {
|
|
@@ -1052,13 +1336,15 @@ var RealtimeService = class {
|
|
|
1052
1336
|
/**
|
|
1053
1337
|
* Subscribe to changes on a specific table
|
|
1054
1338
|
*/
|
|
1055
|
-
async subscribe(table, action, callback) {
|
|
1339
|
+
async subscribe(table, action, callback, filter) {
|
|
1056
1340
|
await this.connect();
|
|
1057
|
-
|
|
1341
|
+
const isNewTable = !this.subscribers.has(table);
|
|
1342
|
+
if (isNewTable) {
|
|
1058
1343
|
this.subscribers.set(table, /* @__PURE__ */ new Set());
|
|
1059
1344
|
}
|
|
1060
|
-
const sub = { action, callback };
|
|
1345
|
+
const sub = { action, callback, filter: filter && Object.keys(filter).length > 0 ? filter : void 0 };
|
|
1061
1346
|
this.subscribers.get(table).add(sub);
|
|
1347
|
+
if (table !== "*") this.syncServerFilter(table, isNewTable);
|
|
1062
1348
|
return {
|
|
1063
1349
|
unsubscribe: () => {
|
|
1064
1350
|
const tableSubs = this.subscribers.get(table);
|
|
@@ -1066,32 +1352,194 @@ var RealtimeService = class {
|
|
|
1066
1352
|
tableSubs.delete(sub);
|
|
1067
1353
|
if (tableSubs.size === 0) {
|
|
1068
1354
|
this.subscribers.delete(table);
|
|
1355
|
+
this.filters.delete(table);
|
|
1356
|
+
if (table !== "*") this.sendSub("unsubscribe", table);
|
|
1357
|
+
} else if (table !== "*") {
|
|
1358
|
+
this.syncServerFilter(table, false);
|
|
1069
1359
|
}
|
|
1070
1360
|
}
|
|
1071
1361
|
}
|
|
1072
1362
|
};
|
|
1073
1363
|
}
|
|
1364
|
+
/**
|
|
1365
|
+
* Recompute the effective server-side filter for a table and (re)register it
|
|
1366
|
+
* when it changed (or when force is set, e.g. a brand-new table). The server
|
|
1367
|
+
* stores exactly one filter per (connection, table): a plain `subscribe`
|
|
1368
|
+
* clears it, a filtered `subscribe` replaces it.
|
|
1369
|
+
*/
|
|
1370
|
+
syncServerFilter(table, force) {
|
|
1371
|
+
const desired = this.computeServerFilter(table);
|
|
1372
|
+
const current = this.filters.get(table);
|
|
1373
|
+
const changed = JSON.stringify(desired ?? null) !== JSON.stringify(current ?? null);
|
|
1374
|
+
if (!force && !changed) return;
|
|
1375
|
+
if (desired) this.filters.set(table, desired);
|
|
1376
|
+
else this.filters.delete(table);
|
|
1377
|
+
this.sendSub("subscribe", table);
|
|
1378
|
+
}
|
|
1379
|
+
/**
|
|
1380
|
+
* The server-side filter safe to apply for a table: the shared filter iff
|
|
1381
|
+
* EVERY subscriber requested the identical (non-empty) filter. If any
|
|
1382
|
+
* subscriber is unfiltered or filters conflict, returns undefined so the
|
|
1383
|
+
* server delivers all rows and each subscriber filters client-side.
|
|
1384
|
+
*/
|
|
1385
|
+
computeServerFilter(table) {
|
|
1386
|
+
const subs = this.subscribers.get(table);
|
|
1387
|
+
if (!subs || subs.size === 0) return void 0;
|
|
1388
|
+
let chosen;
|
|
1389
|
+
for (const sub of subs) {
|
|
1390
|
+
const key = sub.filter && Object.keys(sub.filter).length > 0 ? JSON.stringify(sub.filter) : "";
|
|
1391
|
+
if (key === "") return void 0;
|
|
1392
|
+
if (chosen === void 0) chosen = key;
|
|
1393
|
+
else if (chosen !== key) return void 0;
|
|
1394
|
+
}
|
|
1395
|
+
return chosen ? JSON.parse(chosen) : void 0;
|
|
1396
|
+
}
|
|
1397
|
+
/** Send a subscribe/unsubscribe control frame to the server (with any filter). */
|
|
1398
|
+
sendSub(event, table) {
|
|
1399
|
+
const frame = { event, table };
|
|
1400
|
+
if (event === "subscribe") {
|
|
1401
|
+
const filter = this.filters.get(table);
|
|
1402
|
+
if (filter) frame.filter = filter;
|
|
1403
|
+
}
|
|
1404
|
+
this.send(frame);
|
|
1405
|
+
}
|
|
1406
|
+
/** Send an arbitrary control frame if the socket is open. */
|
|
1407
|
+
send(frame) {
|
|
1408
|
+
if (this.socket?.readyState === WebSocket.OPEN) {
|
|
1409
|
+
this.socket.send(JSON.stringify(frame));
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
/**
|
|
1413
|
+
* Subscribe to an ephemeral Broadcast channel (no DB, no RLS — UI sync/chat).
|
|
1414
|
+
*/
|
|
1415
|
+
async subscribeBroadcast(channel, callback) {
|
|
1416
|
+
await this.connect();
|
|
1417
|
+
const isNew = !this.broadcastHandlers.has(channel);
|
|
1418
|
+
if (isNew) this.broadcastHandlers.set(channel, /* @__PURE__ */ new Set());
|
|
1419
|
+
const handlers = this.broadcastHandlers.get(channel);
|
|
1420
|
+
handlers.add(callback);
|
|
1421
|
+
if (isNew) this.send({ event: "broadcast_subscribe", channel });
|
|
1422
|
+
return {
|
|
1423
|
+
unsubscribe: () => {
|
|
1424
|
+
handlers.delete(callback);
|
|
1425
|
+
if (handlers.size === 0) {
|
|
1426
|
+
this.broadcastHandlers.delete(channel);
|
|
1427
|
+
this.send({ event: "broadcast_unsubscribe", channel });
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
};
|
|
1431
|
+
}
|
|
1432
|
+
/** Publish a message to a Broadcast channel. */
|
|
1433
|
+
broadcast(channel, payload) {
|
|
1434
|
+
this.send({ event: "broadcast", channel, payload });
|
|
1435
|
+
}
|
|
1436
|
+
/**
|
|
1437
|
+
* Join a Presence channel. The callback receives sync/join/leave events with
|
|
1438
|
+
* the current member list. Returns a leave() handle.
|
|
1439
|
+
*/
|
|
1440
|
+
async subscribePresence(channel, state, callback) {
|
|
1441
|
+
await this.connect();
|
|
1442
|
+
const isNew = !this.presenceHandlers.has(channel);
|
|
1443
|
+
if (isNew) this.presenceHandlers.set(channel, /* @__PURE__ */ new Set());
|
|
1444
|
+
this.presenceStates.set(channel, state ?? {});
|
|
1445
|
+
const handlers = this.presenceHandlers.get(channel);
|
|
1446
|
+
handlers.add(callback);
|
|
1447
|
+
if (isNew) this.send({ event: "presence_subscribe", channel, state: state ?? {} });
|
|
1448
|
+
return {
|
|
1449
|
+
leave: () => {
|
|
1450
|
+
handlers.delete(callback);
|
|
1451
|
+
if (handlers.size === 0) {
|
|
1452
|
+
this.presenceHandlers.delete(channel);
|
|
1453
|
+
this.presenceStates.delete(channel);
|
|
1454
|
+
this.send({ event: "presence_unsubscribe", channel });
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1074
1459
|
/**
|
|
1075
1460
|
* Handle incoming CDC events from the server
|
|
1076
1461
|
*/
|
|
1077
1462
|
handleEvent(payload) {
|
|
1078
|
-
const
|
|
1079
|
-
if (
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1463
|
+
const kind = payload.type;
|
|
1464
|
+
if (kind === "broadcast") {
|
|
1465
|
+
const handlers = this.broadcastHandlers.get(payload.channel);
|
|
1466
|
+
if (handlers) for (const cb of handlers) cb(payload.payload);
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
if (kind === "presence") {
|
|
1470
|
+
const handlers = this.presenceHandlers.get(payload.channel);
|
|
1471
|
+
if (handlers) for (const cb of handlers) cb(payload);
|
|
1472
|
+
return;
|
|
1473
|
+
}
|
|
1474
|
+
this.dispatchToSubs(this.subscribers.get(payload.table), payload);
|
|
1475
|
+
this.dispatchToSubs(this.subscribers.get("*"), payload);
|
|
1476
|
+
}
|
|
1477
|
+
/**
|
|
1478
|
+
* Fire matching subscribers. Each subscriber's own filter is re-checked
|
|
1479
|
+
* client-side: when subscribers on a table disagree, the server delivers all
|
|
1480
|
+
* rows unfiltered, so client-side filtering is what keeps each callback scoped.
|
|
1481
|
+
*/
|
|
1482
|
+
dispatchToSubs(subs, payload) {
|
|
1483
|
+
if (!subs) return;
|
|
1484
|
+
for (const sub of subs) {
|
|
1485
|
+
if (sub.action !== "*" && sub.action.toLowerCase() !== payload.action.toLowerCase()) continue;
|
|
1486
|
+
if (sub.filter && !this.matchFilter(payload.record, sub.filter)) continue;
|
|
1487
|
+
sub.callback(payload);
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
/** Evaluate a PostgREST-style filter against a record (mirrors hub_filter.go). */
|
|
1491
|
+
matchFilter(record, filter) {
|
|
1492
|
+
if (record == null) return false;
|
|
1493
|
+
for (const col of Object.keys(filter)) {
|
|
1494
|
+
const spec = filter[col];
|
|
1495
|
+
let op = "eq";
|
|
1496
|
+
let val = spec;
|
|
1497
|
+
const i = spec.indexOf(".");
|
|
1498
|
+
if (i > 0) {
|
|
1499
|
+
const cand = spec.slice(0, i);
|
|
1500
|
+
if (["eq", "neq", "gt", "gte", "lt", "lte", "in", "like"].includes(cand)) {
|
|
1501
|
+
op = cand;
|
|
1502
|
+
val = spec.slice(i + 1);
|
|
1083
1503
|
}
|
|
1084
1504
|
}
|
|
1505
|
+
const actualRaw = record[col];
|
|
1506
|
+
if (actualRaw === void 0) return false;
|
|
1507
|
+
const actual = actualRaw === null ? "" : String(actualRaw);
|
|
1508
|
+
if (!this.evalPred(op, actual, val)) return false;
|
|
1085
1509
|
}
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1510
|
+
return true;
|
|
1511
|
+
}
|
|
1512
|
+
evalPred(op, actual, val) {
|
|
1513
|
+
switch (op) {
|
|
1514
|
+
case "eq":
|
|
1515
|
+
return actual === val;
|
|
1516
|
+
case "neq":
|
|
1517
|
+
return actual !== val;
|
|
1518
|
+
case "in":
|
|
1519
|
+
return val.split(",").includes(actual);
|
|
1520
|
+
case "like":
|
|
1521
|
+
return this.likeMatch(actual, val);
|
|
1522
|
+
case "gt":
|
|
1523
|
+
case "gte":
|
|
1524
|
+
case "lt":
|
|
1525
|
+
case "lte": {
|
|
1526
|
+
const a = parseFloat(actual);
|
|
1527
|
+
const b = parseFloat(val);
|
|
1528
|
+
if (Number.isNaN(a) || Number.isNaN(b)) return false;
|
|
1529
|
+
if (op === "gt") return a > b;
|
|
1530
|
+
if (op === "gte") return a >= b;
|
|
1531
|
+
if (op === "lt") return a < b;
|
|
1532
|
+
return a <= b;
|
|
1092
1533
|
}
|
|
1534
|
+
default:
|
|
1535
|
+
return false;
|
|
1093
1536
|
}
|
|
1094
1537
|
}
|
|
1538
|
+
/** SQL LIKE with '%' (any run) and '_' (any single char), anchored, case-sensitive. */
|
|
1539
|
+
likeMatch(s, pattern) {
|
|
1540
|
+
const re = "^" + pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/%/g, ".*").replace(/_/g, ".") + "$";
|
|
1541
|
+
return new RegExp(re).test(s);
|
|
1542
|
+
}
|
|
1095
1543
|
/**
|
|
1096
1544
|
* Attempt to reconnect on connection loss
|
|
1097
1545
|
*/
|
|
@@ -1129,8 +1577,17 @@ function createRealtimeModule(client) {
|
|
|
1129
1577
|
typeof WebSocket !== "undefined" ? WebSocket : void 0
|
|
1130
1578
|
);
|
|
1131
1579
|
return {
|
|
1132
|
-
async subscribe(table, action, callback) {
|
|
1133
|
-
return service.subscribe(table, action, callback);
|
|
1580
|
+
async subscribe(table, action, callback, filter) {
|
|
1581
|
+
return service.subscribe(table, action, callback, filter);
|
|
1582
|
+
},
|
|
1583
|
+
async subscribeBroadcast(channel, callback) {
|
|
1584
|
+
return service.subscribeBroadcast(channel, callback);
|
|
1585
|
+
},
|
|
1586
|
+
broadcast(channel, payload) {
|
|
1587
|
+
service.broadcast(channel, payload);
|
|
1588
|
+
},
|
|
1589
|
+
async subscribePresence(channel, state, callback) {
|
|
1590
|
+
return service.subscribePresence(channel, state, callback);
|
|
1134
1591
|
}
|
|
1135
1592
|
};
|
|
1136
1593
|
}
|
|
@@ -1153,6 +1610,9 @@ function createApiKeysModule(client) {
|
|
|
1153
1610
|
async delete(keyId) {
|
|
1154
1611
|
return del(`/api/api-keys/${keyId}`);
|
|
1155
1612
|
},
|
|
1613
|
+
async setBrand(keyId, emailConfigId) {
|
|
1614
|
+
return post(`/api/api-keys/${keyId}/brand`, { email_config_id: emailConfigId });
|
|
1615
|
+
},
|
|
1156
1616
|
async getInstanceToken() {
|
|
1157
1617
|
return get("/api/api-keys/instance");
|
|
1158
1618
|
},
|
|
@@ -1242,6 +1702,44 @@ function createIPWhitelistModule(client) {
|
|
|
1242
1702
|
};
|
|
1243
1703
|
}
|
|
1244
1704
|
|
|
1705
|
+
// src/modules/cache.ts
|
|
1706
|
+
function createCacheModule(client) {
|
|
1707
|
+
const get = (endpoint) => client.get(endpoint);
|
|
1708
|
+
const post = (endpoint, body) => client.post(endpoint, body);
|
|
1709
|
+
const patch = (endpoint, body) => client.patch(endpoint, body);
|
|
1710
|
+
const del = (endpoint) => client.delete(endpoint);
|
|
1711
|
+
return {
|
|
1712
|
+
async getStatus() {
|
|
1713
|
+
return get("/api/cache/status");
|
|
1714
|
+
},
|
|
1715
|
+
async getStats() {
|
|
1716
|
+
return get("/api/cache/stats");
|
|
1717
|
+
},
|
|
1718
|
+
async listPolicies() {
|
|
1719
|
+
return get("/api/cache/policies");
|
|
1720
|
+
},
|
|
1721
|
+
async updatePolicy(namespace, body) {
|
|
1722
|
+
return patch(`/api/cache/policies/${encodeURIComponent(namespace)}`, body);
|
|
1723
|
+
},
|
|
1724
|
+
async removePolicy(namespace) {
|
|
1725
|
+
return del(`/api/cache/policies/${encodeURIComponent(namespace)}`);
|
|
1726
|
+
},
|
|
1727
|
+
async invalidateNamespace(namespace) {
|
|
1728
|
+
return post(`/api/cache/invalidate/${encodeURIComponent(namespace)}`);
|
|
1729
|
+
},
|
|
1730
|
+
async invalidateAll() {
|
|
1731
|
+
return post("/api/cache/invalidate-all");
|
|
1732
|
+
},
|
|
1733
|
+
async listKeys(prefix, cursor = 0, count = 100) {
|
|
1734
|
+
const params = new URLSearchParams({ prefix, cursor: String(cursor), count: String(count) });
|
|
1735
|
+
return get(`/api/cache/keys?${params}`);
|
|
1736
|
+
},
|
|
1737
|
+
async inspectKey(key) {
|
|
1738
|
+
return get(`/api/cache/keys/inspect?key=${encodeURIComponent(key)}`);
|
|
1739
|
+
}
|
|
1740
|
+
};
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1245
1743
|
// src/client.ts
|
|
1246
1744
|
var BaasClient = class extends HttpClient {
|
|
1247
1745
|
// Feature modules
|
|
@@ -1268,8 +1766,9 @@ var BaasClient = class extends HttpClient {
|
|
|
1268
1766
|
corsOrigins;
|
|
1269
1767
|
policies;
|
|
1270
1768
|
ipWhitelist;
|
|
1271
|
-
|
|
1272
|
-
|
|
1769
|
+
cache;
|
|
1770
|
+
constructor(url, apiKey, options) {
|
|
1771
|
+
super(url, apiKey, options);
|
|
1273
1772
|
this.auth = createAuthModule(this);
|
|
1274
1773
|
this.users = createUsersModule(this);
|
|
1275
1774
|
this.database = createDatabaseModule(this);
|
|
@@ -1293,6 +1792,7 @@ var BaasClient = class extends HttpClient {
|
|
|
1293
1792
|
this.corsOrigins = createCorsOriginsModule(this);
|
|
1294
1793
|
this.policies = createPoliciesModule(this);
|
|
1295
1794
|
this.ipWhitelist = createIPWhitelistModule(this);
|
|
1795
|
+
this.cache = createCacheModule(this);
|
|
1296
1796
|
}
|
|
1297
1797
|
/**
|
|
1298
1798
|
* Create a query builder for fluent data queries
|
|
@@ -1300,6 +1800,18 @@ var BaasClient = class extends HttpClient {
|
|
|
1300
1800
|
from(table) {
|
|
1301
1801
|
return new QueryBuilder(table, this.url, this);
|
|
1302
1802
|
}
|
|
1803
|
+
/**
|
|
1804
|
+
* Invoke a PL/pgSQL function (PostgREST `.rpc()` parity). Runs under the
|
|
1805
|
+
* caller's RLS context; args bind by name and are $N-safe. Pass [params] for
|
|
1806
|
+
* POST (named args in the body) or set `opts.get = true` for the GET variant.
|
|
1807
|
+
*/
|
|
1808
|
+
async rpc(fn, params, opts) {
|
|
1809
|
+
if (opts?.get) {
|
|
1810
|
+
const qs = params ? "?" + new URLSearchParams(Object.entries(params).map(([k, v]) => [k, String(v)])).toString() : "";
|
|
1811
|
+
return this.get(`/api/v1/rpc/${fn}${qs}`);
|
|
1812
|
+
}
|
|
1813
|
+
return this.post(`/api/v1/rpc/${fn}`, params ?? {});
|
|
1814
|
+
}
|
|
1303
1815
|
// ============================================
|
|
1304
1816
|
// BACKWARD COMPATIBILITY METHODS
|
|
1305
1817
|
// These delegate to the appropriate modules
|
|
@@ -1419,8 +1931,8 @@ var BaasClient = class extends HttpClient {
|
|
|
1419
1931
|
async deleteStorageFile(fileId) {
|
|
1420
1932
|
return this.storage.deleteFile(fileId);
|
|
1421
1933
|
}
|
|
1422
|
-
async createStorageBucket(name, isPublic) {
|
|
1423
|
-
return this.storage.createBucket(name, isPublic);
|
|
1934
|
+
async createStorageBucket(name, isPublic, maxBytes) {
|
|
1935
|
+
return this.storage.createBucket(name, isPublic, maxBytes);
|
|
1424
1936
|
}
|
|
1425
1937
|
async listStorageBuckets() {
|
|
1426
1938
|
return this.storage.listBuckets();
|
|
@@ -1451,6 +1963,9 @@ var BaasClient = class extends HttpClient {
|
|
|
1451
1963
|
async deleteApiKey(keyId) {
|
|
1452
1964
|
return this.apiKeys.delete(keyId);
|
|
1453
1965
|
}
|
|
1966
|
+
async setApiKeyBrand(keyId, emailConfigId) {
|
|
1967
|
+
return this.apiKeys.setBrand(keyId, emailConfigId);
|
|
1968
|
+
}
|
|
1454
1969
|
async getInstanceToken() {
|
|
1455
1970
|
return this.apiKeys.getInstanceToken();
|
|
1456
1971
|
}
|
|
@@ -1525,9 +2040,18 @@ var BaasClient = class extends HttpClient {
|
|
|
1525
2040
|
async getEmailConfig() {
|
|
1526
2041
|
return this.email.getConfig();
|
|
1527
2042
|
}
|
|
2043
|
+
async listEmailConfigs() {
|
|
2044
|
+
return this.email.listConfigs();
|
|
2045
|
+
}
|
|
1528
2046
|
async saveEmailConfig(config) {
|
|
1529
2047
|
return this.email.saveConfig(config);
|
|
1530
2048
|
}
|
|
2049
|
+
async setDefaultEmailConfig(id) {
|
|
2050
|
+
return this.email.setDefaultConfig(id);
|
|
2051
|
+
}
|
|
2052
|
+
async deleteEmailConfig(id) {
|
|
2053
|
+
return this.email.deleteConfig(id);
|
|
2054
|
+
}
|
|
1531
2055
|
async createEmailTemplate(template) {
|
|
1532
2056
|
return this.email.createTemplate(template);
|
|
1533
2057
|
}
|
|
@@ -1667,9 +2191,21 @@ var BaasClient = class extends HttpClient {
|
|
|
1667
2191
|
async getAuditActions() {
|
|
1668
2192
|
return this.audit.getActions();
|
|
1669
2193
|
}
|
|
2194
|
+
async exportAuditLogs(format, options) {
|
|
2195
|
+
return this.audit.exportLogs(format, options);
|
|
2196
|
+
}
|
|
2197
|
+
async previewPurgeAuditLogs(olderThanDays) {
|
|
2198
|
+
return this.audit.purgePreview(olderThanDays);
|
|
2199
|
+
}
|
|
2200
|
+
async purgeAuditLogs(olderThanDays) {
|
|
2201
|
+
return this.audit.purge(olderThanDays);
|
|
2202
|
+
}
|
|
2203
|
+
async clearAllAuditLogs() {
|
|
2204
|
+
return this.audit.clearAll();
|
|
2205
|
+
}
|
|
1670
2206
|
// Realtime shortcuts
|
|
1671
|
-
subscribe(table, action, callback) {
|
|
1672
|
-
return this.realtime.subscribe(table, action, callback);
|
|
2207
|
+
subscribe(table, action, callback, filter) {
|
|
2208
|
+
return this.realtime.subscribe(table, action, callback, filter);
|
|
1673
2209
|
}
|
|
1674
2210
|
// Environment shortcuts
|
|
1675
2211
|
async getEnvironmentStatus() {
|
|
@@ -1721,3 +2257,4 @@ var BaasClient = class extends HttpClient {
|
|
|
1721
2257
|
HttpClient,
|
|
1722
2258
|
QueryBuilder
|
|
1723
2259
|
});
|
|
2260
|
+
//# sourceMappingURL=index.cjs.map
|