@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.js
CHANGED
|
@@ -2,14 +2,31 @@
|
|
|
2
2
|
var HttpClient = class {
|
|
3
3
|
url;
|
|
4
4
|
apiKey = "";
|
|
5
|
+
keyType;
|
|
5
6
|
token = null;
|
|
7
|
+
refreshToken = null;
|
|
6
8
|
environment = "prod";
|
|
7
9
|
_onForceLogout = null;
|
|
8
|
-
|
|
10
|
+
_refreshing = null;
|
|
11
|
+
constructor(url, apiKey, options) {
|
|
9
12
|
let baseUrl = url || (typeof window !== "undefined" ? window.location.origin : "http://localhost:8080");
|
|
10
13
|
this.url = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
11
14
|
if (apiKey) this.apiKey = apiKey;
|
|
12
|
-
this.
|
|
15
|
+
this.keyType = options?.keyType ?? "service_role";
|
|
16
|
+
this.warnIfUnsafeKey();
|
|
17
|
+
this.token = typeof localStorage !== "undefined" ? this.cleanValue(localStorage.getItem("baas_token")) : null;
|
|
18
|
+
this.refreshToken = typeof localStorage !== "undefined" ? this.cleanValue(localStorage.getItem("baas_refresh_token")) : null;
|
|
19
|
+
}
|
|
20
|
+
/** SECURITY: a service_role key bypasses RLS and grants admin over every
|
|
21
|
+
* tenant. If it ends up in a browser bundle, anyone can extract it. Warn
|
|
22
|
+
* loudly when a non-anon key is constructed in a browser context. */
|
|
23
|
+
warnIfUnsafeKey() {
|
|
24
|
+
const inBrowser = typeof window !== "undefined" && typeof document !== "undefined";
|
|
25
|
+
if (inBrowser && this.apiKey && this.keyType !== "anon") {
|
|
26
|
+
console.warn(
|
|
27
|
+
'[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" }.'
|
|
28
|
+
);
|
|
29
|
+
}
|
|
13
30
|
}
|
|
14
31
|
/**
|
|
15
32
|
* Set the auth token manually (e.g. restoring from SecureStore in React Native).
|
|
@@ -17,12 +34,59 @@ var HttpClient = class {
|
|
|
17
34
|
*/
|
|
18
35
|
setToken(token) {
|
|
19
36
|
this.token = token;
|
|
37
|
+
if (typeof localStorage === "undefined") return;
|
|
20
38
|
if (token) {
|
|
21
39
|
localStorage.setItem("baas_token", token);
|
|
22
40
|
} else {
|
|
23
41
|
localStorage.removeItem("baas_token");
|
|
24
42
|
}
|
|
25
43
|
}
|
|
44
|
+
/** Persist the rotating refresh token (used by auto-refresh on 401). In the
|
|
45
|
+
* browser the server also sets an HttpOnly `baas_refresh` cookie, so storing
|
|
46
|
+
* it here is mainly for non-cookie clients (React Native). */
|
|
47
|
+
setRefreshToken(token) {
|
|
48
|
+
this.refreshToken = token;
|
|
49
|
+
if (typeof localStorage === "undefined") return;
|
|
50
|
+
if (token) {
|
|
51
|
+
localStorage.setItem("baas_refresh_token", token);
|
|
52
|
+
} else {
|
|
53
|
+
localStorage.removeItem("baas_refresh_token");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
getRefreshToken() {
|
|
57
|
+
const ls = typeof localStorage !== "undefined" ? localStorage.getItem("baas_refresh_token") : null;
|
|
58
|
+
return this.cleanValue(this.refreshToken || ls);
|
|
59
|
+
}
|
|
60
|
+
/** Attempts a single token refresh, deduped across concurrent 401s. Sends the
|
|
61
|
+
* stored refresh token (falls back to the HttpOnly cookie in browsers). */
|
|
62
|
+
tryRefresh() {
|
|
63
|
+
if (!this._refreshing) {
|
|
64
|
+
this._refreshing = this.doRefresh().finally(() => {
|
|
65
|
+
this._refreshing = null;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return this._refreshing;
|
|
69
|
+
}
|
|
70
|
+
async doRefresh() {
|
|
71
|
+
try {
|
|
72
|
+
const body = {};
|
|
73
|
+
const rt = this.getRefreshToken();
|
|
74
|
+
if (rt) body.refresh_token = rt;
|
|
75
|
+
const res = await fetch(`${this.url}/api/auth/refresh`, {
|
|
76
|
+
method: "POST",
|
|
77
|
+
headers: { "Content-Type": "application/json", apikey: this.apiKey },
|
|
78
|
+
credentials: "include",
|
|
79
|
+
body: JSON.stringify(body)
|
|
80
|
+
});
|
|
81
|
+
if (!res.ok) return false;
|
|
82
|
+
const data = await res.json().catch(() => null);
|
|
83
|
+
if (data?.token) this.setToken(data.token);
|
|
84
|
+
if (data?.refresh_token) this.setRefreshToken(data.refresh_token);
|
|
85
|
+
return Boolean(data?.token);
|
|
86
|
+
} catch {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
26
90
|
/**
|
|
27
91
|
* Register a callback invoked on forced logout (401).
|
|
28
92
|
* Use this in React Native instead of the default window.location redirect.
|
|
@@ -63,7 +127,7 @@ var HttpClient = class {
|
|
|
63
127
|
/**
|
|
64
128
|
* Core HTTP request method - DRY principle
|
|
65
129
|
*/
|
|
66
|
-
async request(endpoint, options = {}) {
|
|
130
|
+
async request(endpoint, options = {}, _isRetry = false) {
|
|
67
131
|
const { method = "GET", body, headers: customHeaders, skipAuth = false } = options;
|
|
68
132
|
const headers = { ...this.getHeaders(), ...customHeaders };
|
|
69
133
|
const fetchOptions = {
|
|
@@ -81,12 +145,22 @@ var HttpClient = class {
|
|
|
81
145
|
const data = await res.json().catch(() => null);
|
|
82
146
|
if (!res.ok) {
|
|
83
147
|
if (res.status === 401 && !skipAuth) {
|
|
148
|
+
if (!_isRetry && await this.tryRefresh()) {
|
|
149
|
+
return this.request(endpoint, options, true);
|
|
150
|
+
}
|
|
84
151
|
this.forceLogout();
|
|
85
152
|
}
|
|
86
153
|
if (res.status === 403 && data?.error === "ip_not_allowed") {
|
|
87
154
|
this.handleIPBlocked(data.ip);
|
|
88
155
|
}
|
|
89
|
-
|
|
156
|
+
const rateLimited = res.status === 429;
|
|
157
|
+
const retryHint = res.headers.get("retry-after") || res.headers.get("x-ratelimit-reset");
|
|
158
|
+
return {
|
|
159
|
+
error: data?.error || `Request failed: ${res.status}`,
|
|
160
|
+
status: res.status,
|
|
161
|
+
...rateLimited ? { rateLimited: true, retryAfter: retryHint ? parseInt(retryHint, 10) : void 0 } : {},
|
|
162
|
+
...data
|
|
163
|
+
};
|
|
90
164
|
}
|
|
91
165
|
return data;
|
|
92
166
|
}
|
|
@@ -118,7 +192,11 @@ var HttpClient = class {
|
|
|
118
192
|
}
|
|
119
193
|
logout() {
|
|
120
194
|
this.token = null;
|
|
121
|
-
|
|
195
|
+
this.refreshToken = null;
|
|
196
|
+
if (typeof localStorage !== "undefined") {
|
|
197
|
+
localStorage.removeItem("baas_token");
|
|
198
|
+
localStorage.removeItem("baas_refresh_token");
|
|
199
|
+
}
|
|
122
200
|
}
|
|
123
201
|
forceLogout() {
|
|
124
202
|
this.logout();
|
|
@@ -203,10 +281,49 @@ var QueryBuilder = class {
|
|
|
203
281
|
this.queryParams.set(column, `not.${operator}.${value}`);
|
|
204
282
|
return this;
|
|
205
283
|
}
|
|
284
|
+
/** Array/jsonb/range contains (@>). Arrays become a `{a,b}` literal. */
|
|
285
|
+
contains(column, values) {
|
|
286
|
+
this.queryParams.set(column, `cs.${this.arrayLiteral(values)}`);
|
|
287
|
+
return this;
|
|
288
|
+
}
|
|
289
|
+
/** Array/jsonb/range contained-by (<@). */
|
|
290
|
+
containedBy(column, values) {
|
|
291
|
+
this.queryParams.set(column, `cd.${this.arrayLiteral(values)}`);
|
|
292
|
+
return this;
|
|
293
|
+
}
|
|
294
|
+
/** Array/range overlap (&&). */
|
|
295
|
+
overlaps(column, values) {
|
|
296
|
+
this.queryParams.set(column, `ov.${this.arrayLiteral(values)}`);
|
|
297
|
+
return this;
|
|
298
|
+
}
|
|
299
|
+
/** Full-text search (@@). type: fts|plfts|phfts|wfts (tsquery flavour). */
|
|
300
|
+
textSearch(column, query, type = "fts") {
|
|
301
|
+
this.queryParams.set(column, `${type}.${query}`);
|
|
302
|
+
return this;
|
|
303
|
+
}
|
|
304
|
+
arrayLiteral(values) {
|
|
305
|
+
return Array.isArray(values) ? `{${values.join(",")}}` : `${values}`;
|
|
306
|
+
}
|
|
307
|
+
/** OR group: `or=(c1,c2)`. Members are dotted `col.op.value` strings. */
|
|
206
308
|
or(filters) {
|
|
207
309
|
this.queryParams.set("or", `(${filters})`);
|
|
208
310
|
return this;
|
|
209
311
|
}
|
|
312
|
+
/** AND group: `and=(c1,c2)` — e.g. a range `qty.gt.5,qty.lt.20`. */
|
|
313
|
+
and(filters) {
|
|
314
|
+
this.queryParams.set("and", `(${filters})`);
|
|
315
|
+
return this;
|
|
316
|
+
}
|
|
317
|
+
/** Negated AND group: `not.and=(...)`. */
|
|
318
|
+
notAnd(filters) {
|
|
319
|
+
this.queryParams.set("not.and", `(${filters})`);
|
|
320
|
+
return this;
|
|
321
|
+
}
|
|
322
|
+
/** Negated OR group: `not.or=(...)`. */
|
|
323
|
+
notOr(filters) {
|
|
324
|
+
this.queryParams.set("not.or", `(${filters})`);
|
|
325
|
+
return this;
|
|
326
|
+
}
|
|
210
327
|
order(column, { ascending = true } = {}) {
|
|
211
328
|
this.queryParams.set("order", `${column}.${ascending ? "asc" : "desc"}`);
|
|
212
329
|
return this;
|
|
@@ -250,6 +367,34 @@ var QueryBuilder = class {
|
|
|
250
367
|
if (res.status === 204) return { success: true };
|
|
251
368
|
return await this.handleResponse(res);
|
|
252
369
|
}
|
|
370
|
+
/** Insert-or-update on conflict. [onConflict] is the conflict-target column(s). */
|
|
371
|
+
async upsert(data, onConflict) {
|
|
372
|
+
const res = await fetch(`${this.url}/api/v1/data/${this.table}/upsert`, {
|
|
373
|
+
method: "POST",
|
|
374
|
+
headers: this.getHeaders(),
|
|
375
|
+
body: JSON.stringify({ ...data, on_conflict: onConflict })
|
|
376
|
+
});
|
|
377
|
+
return await this.handleResponse(res);
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Count rows matching the current filters (PostgREST parity). Sends
|
|
381
|
+
* `Prefer: count=exact` and parses the total from the `Content-Range`
|
|
382
|
+
* response header. Returns `{ count, error, status }`.
|
|
383
|
+
*/
|
|
384
|
+
async count() {
|
|
385
|
+
const res = await fetch(`${this.url}/api/v1/data/${this.table}?${this.queryParams.toString()}`, {
|
|
386
|
+
headers: { ...this.getHeaders(), Prefer: "count=exact", "Cache-Control": "no-cache" },
|
|
387
|
+
cache: "no-store"
|
|
388
|
+
});
|
|
389
|
+
if (!res.ok) {
|
|
390
|
+
if (res.status === 401) this.client.forceLogout();
|
|
391
|
+
const body = await res.json().catch(() => null);
|
|
392
|
+
return { count: 0, error: body?.error || "Request failed", status: res.status };
|
|
393
|
+
}
|
|
394
|
+
const cr = res.headers.get("content-range");
|
|
395
|
+
const total = cr && cr.includes("/") ? parseInt(cr.split("/").pop() || "", 10) : NaN;
|
|
396
|
+
return { count: Number.isNaN(total) ? 0 : total, error: null, status: res.status };
|
|
397
|
+
}
|
|
253
398
|
getHeaders() {
|
|
254
399
|
return this.client.getHeaders();
|
|
255
400
|
}
|
|
@@ -292,18 +437,65 @@ function createAuthModule(client) {
|
|
|
292
437
|
throw new Error(data?.error || `Login failed: ${res.status}`);
|
|
293
438
|
}
|
|
294
439
|
if (data.token) {
|
|
295
|
-
client.
|
|
296
|
-
|
|
440
|
+
client.setToken(data.token);
|
|
441
|
+
}
|
|
442
|
+
if (data.refresh_token) {
|
|
443
|
+
client.setRefreshToken(data.refresh_token);
|
|
297
444
|
}
|
|
298
445
|
return data;
|
|
299
446
|
},
|
|
447
|
+
/** Manually refresh the access token via the rotating refresh token. The
|
|
448
|
+
* HttpClient also does this automatically on a 401. */
|
|
449
|
+
async refresh() {
|
|
450
|
+
const rt = client.refreshToken;
|
|
451
|
+
const data = await post("/api/auth/refresh", rt ? { refresh_token: rt } : {});
|
|
452
|
+
if (data?.token) client.setToken(data.token);
|
|
453
|
+
if (data?.refresh_token) client.setRefreshToken(data.refresh_token);
|
|
454
|
+
return data;
|
|
455
|
+
},
|
|
300
456
|
async logout() {
|
|
301
457
|
try {
|
|
302
458
|
await post("/api/logout");
|
|
303
459
|
} finally {
|
|
304
|
-
client.
|
|
305
|
-
|
|
460
|
+
client.logout();
|
|
461
|
+
}
|
|
462
|
+
},
|
|
463
|
+
// Anonymous sign-in (Supabase parity). Response carries access_token (+ a
|
|
464
|
+
// back-compat `token`); store whichever is present.
|
|
465
|
+
async signInAnonymous() {
|
|
466
|
+
const data = await post("/api/auth/anonymous");
|
|
467
|
+
const tok = data?.access_token || data?.token;
|
|
468
|
+
if (tok) {
|
|
469
|
+
client.token = tok;
|
|
470
|
+
localStorage.setItem("baas_token", tok);
|
|
471
|
+
}
|
|
472
|
+
return data;
|
|
473
|
+
},
|
|
474
|
+
// Passwordless: request a magic link + 6-digit email OTP. Always resolves on
|
|
475
|
+
// a well-formed request (no account enumeration).
|
|
476
|
+
async requestOtp(email, createUser = true) {
|
|
477
|
+
return post("/api/auth/otp", { email, create_user: createUser });
|
|
478
|
+
},
|
|
479
|
+
// Verify a passwordless OTP/magic-link and complete sign-in.
|
|
480
|
+
async verifyOtp(type, email, token) {
|
|
481
|
+
const data = await post("/api/auth/verify", { type, email, token });
|
|
482
|
+
const tok = data?.access_token || data?.token;
|
|
483
|
+
if (tok) {
|
|
484
|
+
client.token = tok;
|
|
485
|
+
localStorage.setItem("baas_token", tok);
|
|
486
|
+
}
|
|
487
|
+
return data;
|
|
488
|
+
},
|
|
489
|
+
// Upgrade the current anonymous guest to a real account (preserves user id).
|
|
490
|
+
// Requires the anonymous Bearer to be set (call after signInAnonymous).
|
|
491
|
+
async upgradeAnonymous(email, password) {
|
|
492
|
+
const data = await post("/api/auth/upgrade", { email, password });
|
|
493
|
+
const tok = data?.access_token || data?.token;
|
|
494
|
+
if (tok) {
|
|
495
|
+
client.token = tok;
|
|
496
|
+
localStorage.setItem("baas_token", tok);
|
|
306
497
|
}
|
|
498
|
+
return data;
|
|
307
499
|
},
|
|
308
500
|
async verifyMFA(mfaToken, code) {
|
|
309
501
|
const data = await post("/api/mfa/finalize", { mfa_token: mfaToken, code });
|
|
@@ -502,6 +694,7 @@ function createDatabaseModule(client) {
|
|
|
502
694
|
function createStorageModule(client) {
|
|
503
695
|
const get = (endpoint) => client.get(endpoint);
|
|
504
696
|
const post = (endpoint, body) => client.post(endpoint, body);
|
|
697
|
+
const put = (endpoint, body) => client.put(endpoint, body);
|
|
505
698
|
const del = (endpoint) => client.delete(endpoint);
|
|
506
699
|
async function uploadFile(fileOrTable, fileOrBucketId) {
|
|
507
700
|
let file;
|
|
@@ -536,14 +729,24 @@ function createStorageModule(client) {
|
|
|
536
729
|
async listFiles() {
|
|
537
730
|
return get("/api/storage/files");
|
|
538
731
|
},
|
|
539
|
-
async getFile(fileId) {
|
|
540
|
-
|
|
732
|
+
async getFile(fileId, expiresIn) {
|
|
733
|
+
const q = expiresIn && expiresIn > 0 ? `?expiresIn=${expiresIn}` : "";
|
|
734
|
+
return get(`/api/storage/files/${fileId}${q}`);
|
|
735
|
+
},
|
|
736
|
+
async createSignedUrl(fileId, expiresIn) {
|
|
737
|
+
const res = await get(
|
|
738
|
+
`/api/storage/files/${fileId}${expiresIn && expiresIn > 0 ? `?expiresIn=${expiresIn}` : ""}`
|
|
739
|
+
);
|
|
740
|
+
return { signedUrl: res?.url };
|
|
541
741
|
},
|
|
542
742
|
async deleteFile(fileId) {
|
|
543
743
|
return del(`/api/storage/files/${fileId}`);
|
|
544
744
|
},
|
|
545
|
-
async createBucket(name, isPublic = false) {
|
|
546
|
-
return post("/api/storage/buckets", { name, is_public: isPublic });
|
|
745
|
+
async createBucket(name, isPublic = false, maxBytes) {
|
|
746
|
+
return post("/api/storage/buckets", { name, is_public: isPublic, max_bytes: maxBytes });
|
|
747
|
+
},
|
|
748
|
+
async updateBucket(bucketId, opts) {
|
|
749
|
+
return put(`/api/storage/buckets/${bucketId}`, { is_public: opts.isPublic, max_bytes: opts.maxBytes });
|
|
547
750
|
},
|
|
548
751
|
async listBuckets() {
|
|
549
752
|
return get("/api/storage/buckets");
|
|
@@ -556,6 +759,17 @@ function createStorageModule(client) {
|
|
|
556
759
|
},
|
|
557
760
|
async listBucketFiles(bucketId) {
|
|
558
761
|
return get(`/api/storage/buckets/${bucketId}/files`);
|
|
762
|
+
},
|
|
763
|
+
async download(fileId) {
|
|
764
|
+
const res = await fetch(`${client.url}/api/storage/files/${fileId}/download`, {
|
|
765
|
+
headers: client.getHeaders("")
|
|
766
|
+
});
|
|
767
|
+
if (!res.ok) {
|
|
768
|
+
if (res.status === 401) client.forceLogout();
|
|
769
|
+
const err = await res.json().catch(() => ({ error: `Download failed: ${res.status}` }));
|
|
770
|
+
throw new Error(err.error ?? `Download failed: ${res.status}`);
|
|
771
|
+
}
|
|
772
|
+
return res.blob();
|
|
559
773
|
}
|
|
560
774
|
};
|
|
561
775
|
}
|
|
@@ -656,7 +870,9 @@ function createFunctionsModule(client) {
|
|
|
656
870
|
return del(`/api/functions/${id}`);
|
|
657
871
|
},
|
|
658
872
|
async update(id, code, options) {
|
|
659
|
-
|
|
873
|
+
const current = await get(`/api/functions/${id}`);
|
|
874
|
+
const name = current?.name ?? current?.data?.name;
|
|
875
|
+
return post("/api/functions", { name, code, ...options });
|
|
660
876
|
},
|
|
661
877
|
async executeCode(code, options) {
|
|
662
878
|
return post("/api/functions/execute", { code, ...options });
|
|
@@ -747,9 +963,18 @@ function createEmailModule(client) {
|
|
|
747
963
|
async getConfig() {
|
|
748
964
|
return get("/api/email/config");
|
|
749
965
|
},
|
|
966
|
+
async listConfigs() {
|
|
967
|
+
return get("/api/email/configs");
|
|
968
|
+
},
|
|
750
969
|
async saveConfig(config) {
|
|
751
970
|
return post("/api/email/config", config);
|
|
752
971
|
},
|
|
972
|
+
async setDefaultConfig(id) {
|
|
973
|
+
return post(`/api/email/configs/${id}/default`);
|
|
974
|
+
},
|
|
975
|
+
async deleteConfig(id) {
|
|
976
|
+
return del(`/api/email/configs/${id}`);
|
|
977
|
+
},
|
|
753
978
|
async createTemplate(template) {
|
|
754
979
|
return post("/api/email/templates", template);
|
|
755
980
|
},
|
|
@@ -802,7 +1027,9 @@ function createGraphQLModule(client) {
|
|
|
802
1027
|
return post("/api/graphql", { query, variables });
|
|
803
1028
|
},
|
|
804
1029
|
async getSchema() {
|
|
805
|
-
return
|
|
1030
|
+
return post("/api/graphql", {
|
|
1031
|
+
query: "{ __schema { queryType { name } mutationType { name } types { name kind } } }"
|
|
1032
|
+
});
|
|
806
1033
|
},
|
|
807
1034
|
getPlaygroundUrl() {
|
|
808
1035
|
return `${client.url}/api/graphql/playground`;
|
|
@@ -848,6 +1075,7 @@ function createMetricsModule(client) {
|
|
|
848
1075
|
// src/modules/audit.ts
|
|
849
1076
|
function createAuditModule(client) {
|
|
850
1077
|
const get = (endpoint) => client.get(endpoint);
|
|
1078
|
+
const del = (endpoint) => client.delete(endpoint);
|
|
851
1079
|
return {
|
|
852
1080
|
async list(options) {
|
|
853
1081
|
const params = new URLSearchParams();
|
|
@@ -864,6 +1092,43 @@ function createAuditModule(client) {
|
|
|
864
1092
|
},
|
|
865
1093
|
async getActions() {
|
|
866
1094
|
return get("/api/audit/actions");
|
|
1095
|
+
},
|
|
1096
|
+
async exportLogs(format, options) {
|
|
1097
|
+
const params = new URLSearchParams();
|
|
1098
|
+
params.set("format", format);
|
|
1099
|
+
if (options?.action) params.set("action", options.action);
|
|
1100
|
+
if (options?.method) params.set("method", options.method);
|
|
1101
|
+
if (options?.path) params.set("path", options.path);
|
|
1102
|
+
if (options?.status_code) params.set("status_code", options.status_code.toString());
|
|
1103
|
+
if (options?.start_date) params.set("start_date", options.start_date);
|
|
1104
|
+
if (options?.end_date) params.set("end_date", options.end_date);
|
|
1105
|
+
const res = await fetch(`${client.url}/api/audit/export?${params.toString()}`, {
|
|
1106
|
+
headers: client.getHeaders(),
|
|
1107
|
+
credentials: "include"
|
|
1108
|
+
});
|
|
1109
|
+
if (!res.ok) {
|
|
1110
|
+
const data = await res.json();
|
|
1111
|
+
return { error: data.error || `Export failed: ${res.status}` };
|
|
1112
|
+
}
|
|
1113
|
+
const blob = await res.blob();
|
|
1114
|
+
const url = window.URL.createObjectURL(blob);
|
|
1115
|
+
const a = document.createElement("a");
|
|
1116
|
+
a.href = url;
|
|
1117
|
+
a.download = `audit_logs_${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.${format}`;
|
|
1118
|
+
document.body.appendChild(a);
|
|
1119
|
+
a.click();
|
|
1120
|
+
window.URL.revokeObjectURL(url);
|
|
1121
|
+
document.body.removeChild(a);
|
|
1122
|
+
return { success: true };
|
|
1123
|
+
},
|
|
1124
|
+
async purgePreview(olderThanDays) {
|
|
1125
|
+
return get(`/api/audit/purge/preview?older_than_days=${olderThanDays}`);
|
|
1126
|
+
},
|
|
1127
|
+
async purge(olderThanDays) {
|
|
1128
|
+
return del(`/api/audit/purge?older_than_days=${olderThanDays}`);
|
|
1129
|
+
},
|
|
1130
|
+
async clearAll() {
|
|
1131
|
+
return del(`/api/audit/clear`);
|
|
867
1132
|
}
|
|
868
1133
|
};
|
|
869
1134
|
}
|
|
@@ -964,6 +1229,16 @@ var RealtimeService = class {
|
|
|
964
1229
|
}
|
|
965
1230
|
socket = null;
|
|
966
1231
|
subscribers = /* @__PURE__ */ new Map();
|
|
1232
|
+
// The server-side value filter currently REGISTERED per table (e.g.
|
|
1233
|
+
// { status: 'eq.pending' }). The server keeps one filter per (connection,table),
|
|
1234
|
+
// so this is only set when every subscriber on the table shares the same filter;
|
|
1235
|
+
// otherwise the table runs unfiltered server-side and each subscriber's filter is
|
|
1236
|
+
// applied client-side in handleEvent (see computeServerFilter).
|
|
1237
|
+
filters = /* @__PURE__ */ new Map();
|
|
1238
|
+
// Ephemeral Broadcast / Presence channel handlers.
|
|
1239
|
+
broadcastHandlers = /* @__PURE__ */ new Map();
|
|
1240
|
+
presenceHandlers = /* @__PURE__ */ new Map();
|
|
1241
|
+
presenceStates = /* @__PURE__ */ new Map();
|
|
967
1242
|
reconnectAttempts = 0;
|
|
968
1243
|
maxReconnectAttempts = 5;
|
|
969
1244
|
reconnectInterval = 3e3;
|
|
@@ -989,6 +1264,15 @@ var RealtimeService = class {
|
|
|
989
1264
|
socket.onopen = () => {
|
|
990
1265
|
console.log("BaaS Realtime: Connected");
|
|
991
1266
|
this.reconnectAttempts = 0;
|
|
1267
|
+
for (const table of this.subscribers.keys()) {
|
|
1268
|
+
this.sendSub("subscribe", table);
|
|
1269
|
+
}
|
|
1270
|
+
for (const channel of this.broadcastHandlers.keys()) {
|
|
1271
|
+
this.send({ event: "broadcast_subscribe", channel });
|
|
1272
|
+
}
|
|
1273
|
+
for (const channel of this.presenceHandlers.keys()) {
|
|
1274
|
+
this.send({ event: "presence_subscribe", channel, state: this.presenceStates.get(channel) ?? {} });
|
|
1275
|
+
}
|
|
992
1276
|
resolve();
|
|
993
1277
|
};
|
|
994
1278
|
socket.onmessage = (event) => {
|
|
@@ -1024,13 +1308,15 @@ var RealtimeService = class {
|
|
|
1024
1308
|
/**
|
|
1025
1309
|
* Subscribe to changes on a specific table
|
|
1026
1310
|
*/
|
|
1027
|
-
async subscribe(table, action, callback) {
|
|
1311
|
+
async subscribe(table, action, callback, filter) {
|
|
1028
1312
|
await this.connect();
|
|
1029
|
-
|
|
1313
|
+
const isNewTable = !this.subscribers.has(table);
|
|
1314
|
+
if (isNewTable) {
|
|
1030
1315
|
this.subscribers.set(table, /* @__PURE__ */ new Set());
|
|
1031
1316
|
}
|
|
1032
|
-
const sub = { action, callback };
|
|
1317
|
+
const sub = { action, callback, filter: filter && Object.keys(filter).length > 0 ? filter : void 0 };
|
|
1033
1318
|
this.subscribers.get(table).add(sub);
|
|
1319
|
+
if (table !== "*") this.syncServerFilter(table, isNewTable);
|
|
1034
1320
|
return {
|
|
1035
1321
|
unsubscribe: () => {
|
|
1036
1322
|
const tableSubs = this.subscribers.get(table);
|
|
@@ -1038,32 +1324,194 @@ var RealtimeService = class {
|
|
|
1038
1324
|
tableSubs.delete(sub);
|
|
1039
1325
|
if (tableSubs.size === 0) {
|
|
1040
1326
|
this.subscribers.delete(table);
|
|
1327
|
+
this.filters.delete(table);
|
|
1328
|
+
if (table !== "*") this.sendSub("unsubscribe", table);
|
|
1329
|
+
} else if (table !== "*") {
|
|
1330
|
+
this.syncServerFilter(table, false);
|
|
1041
1331
|
}
|
|
1042
1332
|
}
|
|
1043
1333
|
}
|
|
1044
1334
|
};
|
|
1045
1335
|
}
|
|
1336
|
+
/**
|
|
1337
|
+
* Recompute the effective server-side filter for a table and (re)register it
|
|
1338
|
+
* when it changed (or when force is set, e.g. a brand-new table). The server
|
|
1339
|
+
* stores exactly one filter per (connection, table): a plain `subscribe`
|
|
1340
|
+
* clears it, a filtered `subscribe` replaces it.
|
|
1341
|
+
*/
|
|
1342
|
+
syncServerFilter(table, force) {
|
|
1343
|
+
const desired = this.computeServerFilter(table);
|
|
1344
|
+
const current = this.filters.get(table);
|
|
1345
|
+
const changed = JSON.stringify(desired ?? null) !== JSON.stringify(current ?? null);
|
|
1346
|
+
if (!force && !changed) return;
|
|
1347
|
+
if (desired) this.filters.set(table, desired);
|
|
1348
|
+
else this.filters.delete(table);
|
|
1349
|
+
this.sendSub("subscribe", table);
|
|
1350
|
+
}
|
|
1351
|
+
/**
|
|
1352
|
+
* The server-side filter safe to apply for a table: the shared filter iff
|
|
1353
|
+
* EVERY subscriber requested the identical (non-empty) filter. If any
|
|
1354
|
+
* subscriber is unfiltered or filters conflict, returns undefined so the
|
|
1355
|
+
* server delivers all rows and each subscriber filters client-side.
|
|
1356
|
+
*/
|
|
1357
|
+
computeServerFilter(table) {
|
|
1358
|
+
const subs = this.subscribers.get(table);
|
|
1359
|
+
if (!subs || subs.size === 0) return void 0;
|
|
1360
|
+
let chosen;
|
|
1361
|
+
for (const sub of subs) {
|
|
1362
|
+
const key = sub.filter && Object.keys(sub.filter).length > 0 ? JSON.stringify(sub.filter) : "";
|
|
1363
|
+
if (key === "") return void 0;
|
|
1364
|
+
if (chosen === void 0) chosen = key;
|
|
1365
|
+
else if (chosen !== key) return void 0;
|
|
1366
|
+
}
|
|
1367
|
+
return chosen ? JSON.parse(chosen) : void 0;
|
|
1368
|
+
}
|
|
1369
|
+
/** Send a subscribe/unsubscribe control frame to the server (with any filter). */
|
|
1370
|
+
sendSub(event, table) {
|
|
1371
|
+
const frame = { event, table };
|
|
1372
|
+
if (event === "subscribe") {
|
|
1373
|
+
const filter = this.filters.get(table);
|
|
1374
|
+
if (filter) frame.filter = filter;
|
|
1375
|
+
}
|
|
1376
|
+
this.send(frame);
|
|
1377
|
+
}
|
|
1378
|
+
/** Send an arbitrary control frame if the socket is open. */
|
|
1379
|
+
send(frame) {
|
|
1380
|
+
if (this.socket?.readyState === WebSocket.OPEN) {
|
|
1381
|
+
this.socket.send(JSON.stringify(frame));
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
/**
|
|
1385
|
+
* Subscribe to an ephemeral Broadcast channel (no DB, no RLS — UI sync/chat).
|
|
1386
|
+
*/
|
|
1387
|
+
async subscribeBroadcast(channel, callback) {
|
|
1388
|
+
await this.connect();
|
|
1389
|
+
const isNew = !this.broadcastHandlers.has(channel);
|
|
1390
|
+
if (isNew) this.broadcastHandlers.set(channel, /* @__PURE__ */ new Set());
|
|
1391
|
+
const handlers = this.broadcastHandlers.get(channel);
|
|
1392
|
+
handlers.add(callback);
|
|
1393
|
+
if (isNew) this.send({ event: "broadcast_subscribe", channel });
|
|
1394
|
+
return {
|
|
1395
|
+
unsubscribe: () => {
|
|
1396
|
+
handlers.delete(callback);
|
|
1397
|
+
if (handlers.size === 0) {
|
|
1398
|
+
this.broadcastHandlers.delete(channel);
|
|
1399
|
+
this.send({ event: "broadcast_unsubscribe", channel });
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
};
|
|
1403
|
+
}
|
|
1404
|
+
/** Publish a message to a Broadcast channel. */
|
|
1405
|
+
broadcast(channel, payload) {
|
|
1406
|
+
this.send({ event: "broadcast", channel, payload });
|
|
1407
|
+
}
|
|
1408
|
+
/**
|
|
1409
|
+
* Join a Presence channel. The callback receives sync/join/leave events with
|
|
1410
|
+
* the current member list. Returns a leave() handle.
|
|
1411
|
+
*/
|
|
1412
|
+
async subscribePresence(channel, state, callback) {
|
|
1413
|
+
await this.connect();
|
|
1414
|
+
const isNew = !this.presenceHandlers.has(channel);
|
|
1415
|
+
if (isNew) this.presenceHandlers.set(channel, /* @__PURE__ */ new Set());
|
|
1416
|
+
this.presenceStates.set(channel, state ?? {});
|
|
1417
|
+
const handlers = this.presenceHandlers.get(channel);
|
|
1418
|
+
handlers.add(callback);
|
|
1419
|
+
if (isNew) this.send({ event: "presence_subscribe", channel, state: state ?? {} });
|
|
1420
|
+
return {
|
|
1421
|
+
leave: () => {
|
|
1422
|
+
handlers.delete(callback);
|
|
1423
|
+
if (handlers.size === 0) {
|
|
1424
|
+
this.presenceHandlers.delete(channel);
|
|
1425
|
+
this.presenceStates.delete(channel);
|
|
1426
|
+
this.send({ event: "presence_unsubscribe", channel });
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1046
1431
|
/**
|
|
1047
1432
|
* Handle incoming CDC events from the server
|
|
1048
1433
|
*/
|
|
1049
1434
|
handleEvent(payload) {
|
|
1050
|
-
const
|
|
1051
|
-
if (
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1435
|
+
const kind = payload.type;
|
|
1436
|
+
if (kind === "broadcast") {
|
|
1437
|
+
const handlers = this.broadcastHandlers.get(payload.channel);
|
|
1438
|
+
if (handlers) for (const cb of handlers) cb(payload.payload);
|
|
1439
|
+
return;
|
|
1440
|
+
}
|
|
1441
|
+
if (kind === "presence") {
|
|
1442
|
+
const handlers = this.presenceHandlers.get(payload.channel);
|
|
1443
|
+
if (handlers) for (const cb of handlers) cb(payload);
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1446
|
+
this.dispatchToSubs(this.subscribers.get(payload.table), payload);
|
|
1447
|
+
this.dispatchToSubs(this.subscribers.get("*"), payload);
|
|
1448
|
+
}
|
|
1449
|
+
/**
|
|
1450
|
+
* Fire matching subscribers. Each subscriber's own filter is re-checked
|
|
1451
|
+
* client-side: when subscribers on a table disagree, the server delivers all
|
|
1452
|
+
* rows unfiltered, so client-side filtering is what keeps each callback scoped.
|
|
1453
|
+
*/
|
|
1454
|
+
dispatchToSubs(subs, payload) {
|
|
1455
|
+
if (!subs) return;
|
|
1456
|
+
for (const sub of subs) {
|
|
1457
|
+
if (sub.action !== "*" && sub.action.toLowerCase() !== payload.action.toLowerCase()) continue;
|
|
1458
|
+
if (sub.filter && !this.matchFilter(payload.record, sub.filter)) continue;
|
|
1459
|
+
sub.callback(payload);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
/** Evaluate a PostgREST-style filter against a record (mirrors hub_filter.go). */
|
|
1463
|
+
matchFilter(record, filter) {
|
|
1464
|
+
if (record == null) return false;
|
|
1465
|
+
for (const col of Object.keys(filter)) {
|
|
1466
|
+
const spec = filter[col];
|
|
1467
|
+
let op = "eq";
|
|
1468
|
+
let val = spec;
|
|
1469
|
+
const i = spec.indexOf(".");
|
|
1470
|
+
if (i > 0) {
|
|
1471
|
+
const cand = spec.slice(0, i);
|
|
1472
|
+
if (["eq", "neq", "gt", "gte", "lt", "lte", "in", "like"].includes(cand)) {
|
|
1473
|
+
op = cand;
|
|
1474
|
+
val = spec.slice(i + 1);
|
|
1055
1475
|
}
|
|
1056
1476
|
}
|
|
1477
|
+
const actualRaw = record[col];
|
|
1478
|
+
if (actualRaw === void 0) return false;
|
|
1479
|
+
const actual = actualRaw === null ? "" : String(actualRaw);
|
|
1480
|
+
if (!this.evalPred(op, actual, val)) return false;
|
|
1057
1481
|
}
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1482
|
+
return true;
|
|
1483
|
+
}
|
|
1484
|
+
evalPred(op, actual, val) {
|
|
1485
|
+
switch (op) {
|
|
1486
|
+
case "eq":
|
|
1487
|
+
return actual === val;
|
|
1488
|
+
case "neq":
|
|
1489
|
+
return actual !== val;
|
|
1490
|
+
case "in":
|
|
1491
|
+
return val.split(",").includes(actual);
|
|
1492
|
+
case "like":
|
|
1493
|
+
return this.likeMatch(actual, val);
|
|
1494
|
+
case "gt":
|
|
1495
|
+
case "gte":
|
|
1496
|
+
case "lt":
|
|
1497
|
+
case "lte": {
|
|
1498
|
+
const a = parseFloat(actual);
|
|
1499
|
+
const b = parseFloat(val);
|
|
1500
|
+
if (Number.isNaN(a) || Number.isNaN(b)) return false;
|
|
1501
|
+
if (op === "gt") return a > b;
|
|
1502
|
+
if (op === "gte") return a >= b;
|
|
1503
|
+
if (op === "lt") return a < b;
|
|
1504
|
+
return a <= b;
|
|
1064
1505
|
}
|
|
1506
|
+
default:
|
|
1507
|
+
return false;
|
|
1065
1508
|
}
|
|
1066
1509
|
}
|
|
1510
|
+
/** SQL LIKE with '%' (any run) and '_' (any single char), anchored, case-sensitive. */
|
|
1511
|
+
likeMatch(s, pattern) {
|
|
1512
|
+
const re = "^" + pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/%/g, ".*").replace(/_/g, ".") + "$";
|
|
1513
|
+
return new RegExp(re).test(s);
|
|
1514
|
+
}
|
|
1067
1515
|
/**
|
|
1068
1516
|
* Attempt to reconnect on connection loss
|
|
1069
1517
|
*/
|
|
@@ -1101,8 +1549,17 @@ function createRealtimeModule(client) {
|
|
|
1101
1549
|
typeof WebSocket !== "undefined" ? WebSocket : void 0
|
|
1102
1550
|
);
|
|
1103
1551
|
return {
|
|
1104
|
-
async subscribe(table, action, callback) {
|
|
1105
|
-
return service.subscribe(table, action, callback);
|
|
1552
|
+
async subscribe(table, action, callback, filter) {
|
|
1553
|
+
return service.subscribe(table, action, callback, filter);
|
|
1554
|
+
},
|
|
1555
|
+
async subscribeBroadcast(channel, callback) {
|
|
1556
|
+
return service.subscribeBroadcast(channel, callback);
|
|
1557
|
+
},
|
|
1558
|
+
broadcast(channel, payload) {
|
|
1559
|
+
service.broadcast(channel, payload);
|
|
1560
|
+
},
|
|
1561
|
+
async subscribePresence(channel, state, callback) {
|
|
1562
|
+
return service.subscribePresence(channel, state, callback);
|
|
1106
1563
|
}
|
|
1107
1564
|
};
|
|
1108
1565
|
}
|
|
@@ -1125,6 +1582,9 @@ function createApiKeysModule(client) {
|
|
|
1125
1582
|
async delete(keyId) {
|
|
1126
1583
|
return del(`/api/api-keys/${keyId}`);
|
|
1127
1584
|
},
|
|
1585
|
+
async setBrand(keyId, emailConfigId) {
|
|
1586
|
+
return post(`/api/api-keys/${keyId}/brand`, { email_config_id: emailConfigId });
|
|
1587
|
+
},
|
|
1128
1588
|
async getInstanceToken() {
|
|
1129
1589
|
return get("/api/api-keys/instance");
|
|
1130
1590
|
},
|
|
@@ -1214,6 +1674,44 @@ function createIPWhitelistModule(client) {
|
|
|
1214
1674
|
};
|
|
1215
1675
|
}
|
|
1216
1676
|
|
|
1677
|
+
// src/modules/cache.ts
|
|
1678
|
+
function createCacheModule(client) {
|
|
1679
|
+
const get = (endpoint) => client.get(endpoint);
|
|
1680
|
+
const post = (endpoint, body) => client.post(endpoint, body);
|
|
1681
|
+
const patch = (endpoint, body) => client.patch(endpoint, body);
|
|
1682
|
+
const del = (endpoint) => client.delete(endpoint);
|
|
1683
|
+
return {
|
|
1684
|
+
async getStatus() {
|
|
1685
|
+
return get("/api/cache/status");
|
|
1686
|
+
},
|
|
1687
|
+
async getStats() {
|
|
1688
|
+
return get("/api/cache/stats");
|
|
1689
|
+
},
|
|
1690
|
+
async listPolicies() {
|
|
1691
|
+
return get("/api/cache/policies");
|
|
1692
|
+
},
|
|
1693
|
+
async updatePolicy(namespace, body) {
|
|
1694
|
+
return patch(`/api/cache/policies/${encodeURIComponent(namespace)}`, body);
|
|
1695
|
+
},
|
|
1696
|
+
async removePolicy(namespace) {
|
|
1697
|
+
return del(`/api/cache/policies/${encodeURIComponent(namespace)}`);
|
|
1698
|
+
},
|
|
1699
|
+
async invalidateNamespace(namespace) {
|
|
1700
|
+
return post(`/api/cache/invalidate/${encodeURIComponent(namespace)}`);
|
|
1701
|
+
},
|
|
1702
|
+
async invalidateAll() {
|
|
1703
|
+
return post("/api/cache/invalidate-all");
|
|
1704
|
+
},
|
|
1705
|
+
async listKeys(prefix, cursor = 0, count = 100) {
|
|
1706
|
+
const params = new URLSearchParams({ prefix, cursor: String(cursor), count: String(count) });
|
|
1707
|
+
return get(`/api/cache/keys?${params}`);
|
|
1708
|
+
},
|
|
1709
|
+
async inspectKey(key) {
|
|
1710
|
+
return get(`/api/cache/keys/inspect?key=${encodeURIComponent(key)}`);
|
|
1711
|
+
}
|
|
1712
|
+
};
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1217
1715
|
// src/client.ts
|
|
1218
1716
|
var BaasClient = class extends HttpClient {
|
|
1219
1717
|
// Feature modules
|
|
@@ -1240,8 +1738,9 @@ var BaasClient = class extends HttpClient {
|
|
|
1240
1738
|
corsOrigins;
|
|
1241
1739
|
policies;
|
|
1242
1740
|
ipWhitelist;
|
|
1243
|
-
|
|
1244
|
-
|
|
1741
|
+
cache;
|
|
1742
|
+
constructor(url, apiKey, options) {
|
|
1743
|
+
super(url, apiKey, options);
|
|
1245
1744
|
this.auth = createAuthModule(this);
|
|
1246
1745
|
this.users = createUsersModule(this);
|
|
1247
1746
|
this.database = createDatabaseModule(this);
|
|
@@ -1265,6 +1764,7 @@ var BaasClient = class extends HttpClient {
|
|
|
1265
1764
|
this.corsOrigins = createCorsOriginsModule(this);
|
|
1266
1765
|
this.policies = createPoliciesModule(this);
|
|
1267
1766
|
this.ipWhitelist = createIPWhitelistModule(this);
|
|
1767
|
+
this.cache = createCacheModule(this);
|
|
1268
1768
|
}
|
|
1269
1769
|
/**
|
|
1270
1770
|
* Create a query builder for fluent data queries
|
|
@@ -1272,6 +1772,18 @@ var BaasClient = class extends HttpClient {
|
|
|
1272
1772
|
from(table) {
|
|
1273
1773
|
return new QueryBuilder(table, this.url, this);
|
|
1274
1774
|
}
|
|
1775
|
+
/**
|
|
1776
|
+
* Invoke a PL/pgSQL function (PostgREST `.rpc()` parity). Runs under the
|
|
1777
|
+
* caller's RLS context; args bind by name and are $N-safe. Pass [params] for
|
|
1778
|
+
* POST (named args in the body) or set `opts.get = true` for the GET variant.
|
|
1779
|
+
*/
|
|
1780
|
+
async rpc(fn, params, opts) {
|
|
1781
|
+
if (opts?.get) {
|
|
1782
|
+
const qs = params ? "?" + new URLSearchParams(Object.entries(params).map(([k, v]) => [k, String(v)])).toString() : "";
|
|
1783
|
+
return this.get(`/api/v1/rpc/${fn}${qs}`);
|
|
1784
|
+
}
|
|
1785
|
+
return this.post(`/api/v1/rpc/${fn}`, params ?? {});
|
|
1786
|
+
}
|
|
1275
1787
|
// ============================================
|
|
1276
1788
|
// BACKWARD COMPATIBILITY METHODS
|
|
1277
1789
|
// These delegate to the appropriate modules
|
|
@@ -1391,8 +1903,8 @@ var BaasClient = class extends HttpClient {
|
|
|
1391
1903
|
async deleteStorageFile(fileId) {
|
|
1392
1904
|
return this.storage.deleteFile(fileId);
|
|
1393
1905
|
}
|
|
1394
|
-
async createStorageBucket(name, isPublic) {
|
|
1395
|
-
return this.storage.createBucket(name, isPublic);
|
|
1906
|
+
async createStorageBucket(name, isPublic, maxBytes) {
|
|
1907
|
+
return this.storage.createBucket(name, isPublic, maxBytes);
|
|
1396
1908
|
}
|
|
1397
1909
|
async listStorageBuckets() {
|
|
1398
1910
|
return this.storage.listBuckets();
|
|
@@ -1423,6 +1935,9 @@ var BaasClient = class extends HttpClient {
|
|
|
1423
1935
|
async deleteApiKey(keyId) {
|
|
1424
1936
|
return this.apiKeys.delete(keyId);
|
|
1425
1937
|
}
|
|
1938
|
+
async setApiKeyBrand(keyId, emailConfigId) {
|
|
1939
|
+
return this.apiKeys.setBrand(keyId, emailConfigId);
|
|
1940
|
+
}
|
|
1426
1941
|
async getInstanceToken() {
|
|
1427
1942
|
return this.apiKeys.getInstanceToken();
|
|
1428
1943
|
}
|
|
@@ -1497,9 +2012,18 @@ var BaasClient = class extends HttpClient {
|
|
|
1497
2012
|
async getEmailConfig() {
|
|
1498
2013
|
return this.email.getConfig();
|
|
1499
2014
|
}
|
|
2015
|
+
async listEmailConfigs() {
|
|
2016
|
+
return this.email.listConfigs();
|
|
2017
|
+
}
|
|
1500
2018
|
async saveEmailConfig(config) {
|
|
1501
2019
|
return this.email.saveConfig(config);
|
|
1502
2020
|
}
|
|
2021
|
+
async setDefaultEmailConfig(id) {
|
|
2022
|
+
return this.email.setDefaultConfig(id);
|
|
2023
|
+
}
|
|
2024
|
+
async deleteEmailConfig(id) {
|
|
2025
|
+
return this.email.deleteConfig(id);
|
|
2026
|
+
}
|
|
1503
2027
|
async createEmailTemplate(template) {
|
|
1504
2028
|
return this.email.createTemplate(template);
|
|
1505
2029
|
}
|
|
@@ -1639,9 +2163,21 @@ var BaasClient = class extends HttpClient {
|
|
|
1639
2163
|
async getAuditActions() {
|
|
1640
2164
|
return this.audit.getActions();
|
|
1641
2165
|
}
|
|
2166
|
+
async exportAuditLogs(format, options) {
|
|
2167
|
+
return this.audit.exportLogs(format, options);
|
|
2168
|
+
}
|
|
2169
|
+
async previewPurgeAuditLogs(olderThanDays) {
|
|
2170
|
+
return this.audit.purgePreview(olderThanDays);
|
|
2171
|
+
}
|
|
2172
|
+
async purgeAuditLogs(olderThanDays) {
|
|
2173
|
+
return this.audit.purge(olderThanDays);
|
|
2174
|
+
}
|
|
2175
|
+
async clearAllAuditLogs() {
|
|
2176
|
+
return this.audit.clearAll();
|
|
2177
|
+
}
|
|
1642
2178
|
// Realtime shortcuts
|
|
1643
|
-
subscribe(table, action, callback) {
|
|
1644
|
-
return this.realtime.subscribe(table, action, callback);
|
|
2179
|
+
subscribe(table, action, callback, filter) {
|
|
2180
|
+
return this.realtime.subscribe(table, action, callback, filter);
|
|
1645
2181
|
}
|
|
1646
2182
|
// Environment shortcuts
|
|
1647
2183
|
async getEnvironmentStatus() {
|
|
@@ -1692,3 +2228,4 @@ export {
|
|
|
1692
2228
|
HttpClient,
|
|
1693
2229
|
QueryBuilder
|
|
1694
2230
|
};
|
|
2231
|
+
//# sourceMappingURL=index.js.map
|