@giaeulate/baas-sdk 1.3.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 +12 -0
- package/dist/cli.js +32 -0
- package/dist/index.cjs +308 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +51 -8
- package/dist/index.d.ts +51 -8
- package/dist/index.js +308 -33
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,8 +4,10 @@ var HttpClient = class {
|
|
|
4
4
|
apiKey = "";
|
|
5
5
|
keyType;
|
|
6
6
|
token = null;
|
|
7
|
+
refreshToken = null;
|
|
7
8
|
environment = "prod";
|
|
8
9
|
_onForceLogout = null;
|
|
10
|
+
_refreshing = null;
|
|
9
11
|
constructor(url, apiKey, options) {
|
|
10
12
|
let baseUrl = url || (typeof window !== "undefined" ? window.location.origin : "http://localhost:8080");
|
|
11
13
|
this.url = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
@@ -13,6 +15,7 @@ var HttpClient = class {
|
|
|
13
15
|
this.keyType = options?.keyType ?? "service_role";
|
|
14
16
|
this.warnIfUnsafeKey();
|
|
15
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;
|
|
16
19
|
}
|
|
17
20
|
/** SECURITY: a service_role key bypasses RLS and grants admin over every
|
|
18
21
|
* tenant. If it ends up in a browser bundle, anyone can extract it. Warn
|
|
@@ -31,12 +34,59 @@ var HttpClient = class {
|
|
|
31
34
|
*/
|
|
32
35
|
setToken(token) {
|
|
33
36
|
this.token = token;
|
|
37
|
+
if (typeof localStorage === "undefined") return;
|
|
34
38
|
if (token) {
|
|
35
39
|
localStorage.setItem("baas_token", token);
|
|
36
40
|
} else {
|
|
37
41
|
localStorage.removeItem("baas_token");
|
|
38
42
|
}
|
|
39
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
|
+
}
|
|
40
90
|
/**
|
|
41
91
|
* Register a callback invoked on forced logout (401).
|
|
42
92
|
* Use this in React Native instead of the default window.location redirect.
|
|
@@ -77,7 +127,7 @@ var HttpClient = class {
|
|
|
77
127
|
/**
|
|
78
128
|
* Core HTTP request method - DRY principle
|
|
79
129
|
*/
|
|
80
|
-
async request(endpoint, options = {}) {
|
|
130
|
+
async request(endpoint, options = {}, _isRetry = false) {
|
|
81
131
|
const { method = "GET", body, headers: customHeaders, skipAuth = false } = options;
|
|
82
132
|
const headers = { ...this.getHeaders(), ...customHeaders };
|
|
83
133
|
const fetchOptions = {
|
|
@@ -95,6 +145,9 @@ var HttpClient = class {
|
|
|
95
145
|
const data = await res.json().catch(() => null);
|
|
96
146
|
if (!res.ok) {
|
|
97
147
|
if (res.status === 401 && !skipAuth) {
|
|
148
|
+
if (!_isRetry && await this.tryRefresh()) {
|
|
149
|
+
return this.request(endpoint, options, true);
|
|
150
|
+
}
|
|
98
151
|
this.forceLogout();
|
|
99
152
|
}
|
|
100
153
|
if (res.status === 403 && data?.error === "ip_not_allowed") {
|
|
@@ -139,7 +192,11 @@ var HttpClient = class {
|
|
|
139
192
|
}
|
|
140
193
|
logout() {
|
|
141
194
|
this.token = null;
|
|
142
|
-
|
|
195
|
+
this.refreshToken = null;
|
|
196
|
+
if (typeof localStorage !== "undefined") {
|
|
197
|
+
localStorage.removeItem("baas_token");
|
|
198
|
+
localStorage.removeItem("baas_refresh_token");
|
|
199
|
+
}
|
|
143
200
|
}
|
|
144
201
|
forceLogout() {
|
|
145
202
|
this.logout();
|
|
@@ -380,17 +437,27 @@ function createAuthModule(client) {
|
|
|
380
437
|
throw new Error(data?.error || `Login failed: ${res.status}`);
|
|
381
438
|
}
|
|
382
439
|
if (data.token) {
|
|
383
|
-
client.
|
|
384
|
-
localStorage.setItem("baas_token", data.token);
|
|
440
|
+
client.setToken(data.token);
|
|
385
441
|
}
|
|
442
|
+
if (data.refresh_token) {
|
|
443
|
+
client.setRefreshToken(data.refresh_token);
|
|
444
|
+
}
|
|
445
|
+
return data;
|
|
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);
|
|
386
454
|
return data;
|
|
387
455
|
},
|
|
388
456
|
async logout() {
|
|
389
457
|
try {
|
|
390
458
|
await post("/api/logout");
|
|
391
459
|
} finally {
|
|
392
|
-
client.
|
|
393
|
-
localStorage.removeItem("baas_token");
|
|
460
|
+
client.logout();
|
|
394
461
|
}
|
|
395
462
|
},
|
|
396
463
|
// Anonymous sign-in (Supabase parity). Response carries access_token (+ a
|
|
@@ -675,11 +742,11 @@ function createStorageModule(client) {
|
|
|
675
742
|
async deleteFile(fileId) {
|
|
676
743
|
return del(`/api/storage/files/${fileId}`);
|
|
677
744
|
},
|
|
678
|
-
async createBucket(name, isPublic = false) {
|
|
679
|
-
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 });
|
|
680
747
|
},
|
|
681
748
|
async updateBucket(bucketId, opts) {
|
|
682
|
-
return put(`/api/storage/buckets/${bucketId}`, { is_public: opts.isPublic });
|
|
749
|
+
return put(`/api/storage/buckets/${bucketId}`, { is_public: opts.isPublic, max_bytes: opts.maxBytes });
|
|
683
750
|
},
|
|
684
751
|
async listBuckets() {
|
|
685
752
|
return get("/api/storage/buckets");
|
|
@@ -896,9 +963,18 @@ function createEmailModule(client) {
|
|
|
896
963
|
async getConfig() {
|
|
897
964
|
return get("/api/email/config");
|
|
898
965
|
},
|
|
966
|
+
async listConfigs() {
|
|
967
|
+
return get("/api/email/configs");
|
|
968
|
+
},
|
|
899
969
|
async saveConfig(config) {
|
|
900
970
|
return post("/api/email/config", config);
|
|
901
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
|
+
},
|
|
902
978
|
async createTemplate(template) {
|
|
903
979
|
return post("/api/email/templates", template);
|
|
904
980
|
},
|
|
@@ -1050,6 +1126,9 @@ function createAuditModule(client) {
|
|
|
1050
1126
|
},
|
|
1051
1127
|
async purge(olderThanDays) {
|
|
1052
1128
|
return del(`/api/audit/purge?older_than_days=${olderThanDays}`);
|
|
1129
|
+
},
|
|
1130
|
+
async clearAll() {
|
|
1131
|
+
return del(`/api/audit/clear`);
|
|
1053
1132
|
}
|
|
1054
1133
|
};
|
|
1055
1134
|
}
|
|
@@ -1150,6 +1229,16 @@ var RealtimeService = class {
|
|
|
1150
1229
|
}
|
|
1151
1230
|
socket = null;
|
|
1152
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();
|
|
1153
1242
|
reconnectAttempts = 0;
|
|
1154
1243
|
maxReconnectAttempts = 5;
|
|
1155
1244
|
reconnectInterval = 3e3;
|
|
@@ -1178,6 +1267,12 @@ var RealtimeService = class {
|
|
|
1178
1267
|
for (const table of this.subscribers.keys()) {
|
|
1179
1268
|
this.sendSub("subscribe", table);
|
|
1180
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
|
+
}
|
|
1181
1276
|
resolve();
|
|
1182
1277
|
};
|
|
1183
1278
|
socket.onmessage = (event) => {
|
|
@@ -1213,17 +1308,15 @@ var RealtimeService = class {
|
|
|
1213
1308
|
/**
|
|
1214
1309
|
* Subscribe to changes on a specific table
|
|
1215
1310
|
*/
|
|
1216
|
-
async subscribe(table, action, callback) {
|
|
1311
|
+
async subscribe(table, action, callback, filter) {
|
|
1217
1312
|
await this.connect();
|
|
1218
1313
|
const isNewTable = !this.subscribers.has(table);
|
|
1219
1314
|
if (isNewTable) {
|
|
1220
1315
|
this.subscribers.set(table, /* @__PURE__ */ new Set());
|
|
1221
1316
|
}
|
|
1222
|
-
const sub = { action, callback };
|
|
1317
|
+
const sub = { action, callback, filter: filter && Object.keys(filter).length > 0 ? filter : void 0 };
|
|
1223
1318
|
this.subscribers.get(table).add(sub);
|
|
1224
|
-
if (
|
|
1225
|
-
this.sendSub("subscribe", table);
|
|
1226
|
-
}
|
|
1319
|
+
if (table !== "*") this.syncServerFilter(table, isNewTable);
|
|
1227
1320
|
return {
|
|
1228
1321
|
unsubscribe: () => {
|
|
1229
1322
|
const tableSubs = this.subscribers.get(table);
|
|
@@ -1231,39 +1324,194 @@ var RealtimeService = class {
|
|
|
1231
1324
|
tableSubs.delete(sub);
|
|
1232
1325
|
if (tableSubs.size === 0) {
|
|
1233
1326
|
this.subscribers.delete(table);
|
|
1327
|
+
this.filters.delete(table);
|
|
1234
1328
|
if (table !== "*") this.sendSub("unsubscribe", table);
|
|
1329
|
+
} else if (table !== "*") {
|
|
1330
|
+
this.syncServerFilter(table, false);
|
|
1235
1331
|
}
|
|
1236
1332
|
}
|
|
1237
1333
|
}
|
|
1238
1334
|
};
|
|
1239
1335
|
}
|
|
1240
|
-
/**
|
|
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). */
|
|
1241
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) {
|
|
1242
1380
|
if (this.socket?.readyState === WebSocket.OPEN) {
|
|
1243
|
-
this.socket.send(JSON.stringify(
|
|
1381
|
+
this.socket.send(JSON.stringify(frame));
|
|
1244
1382
|
}
|
|
1245
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
|
+
}
|
|
1246
1431
|
/**
|
|
1247
1432
|
* Handle incoming CDC events from the server
|
|
1248
1433
|
*/
|
|
1249
1434
|
handleEvent(payload) {
|
|
1250
|
-
const
|
|
1251
|
-
if (
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
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);
|
|
1255
1475
|
}
|
|
1256
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;
|
|
1257
1481
|
}
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
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;
|
|
1264
1505
|
}
|
|
1506
|
+
default:
|
|
1507
|
+
return false;
|
|
1265
1508
|
}
|
|
1266
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
|
+
}
|
|
1267
1515
|
/**
|
|
1268
1516
|
* Attempt to reconnect on connection loss
|
|
1269
1517
|
*/
|
|
@@ -1301,8 +1549,17 @@ function createRealtimeModule(client) {
|
|
|
1301
1549
|
typeof WebSocket !== "undefined" ? WebSocket : void 0
|
|
1302
1550
|
);
|
|
1303
1551
|
return {
|
|
1304
|
-
async subscribe(table, action, callback) {
|
|
1305
|
-
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);
|
|
1306
1563
|
}
|
|
1307
1564
|
};
|
|
1308
1565
|
}
|
|
@@ -1325,6 +1582,9 @@ function createApiKeysModule(client) {
|
|
|
1325
1582
|
async delete(keyId) {
|
|
1326
1583
|
return del(`/api/api-keys/${keyId}`);
|
|
1327
1584
|
},
|
|
1585
|
+
async setBrand(keyId, emailConfigId) {
|
|
1586
|
+
return post(`/api/api-keys/${keyId}/brand`, { email_config_id: emailConfigId });
|
|
1587
|
+
},
|
|
1328
1588
|
async getInstanceToken() {
|
|
1329
1589
|
return get("/api/api-keys/instance");
|
|
1330
1590
|
},
|
|
@@ -1643,8 +1903,8 @@ var BaasClient = class extends HttpClient {
|
|
|
1643
1903
|
async deleteStorageFile(fileId) {
|
|
1644
1904
|
return this.storage.deleteFile(fileId);
|
|
1645
1905
|
}
|
|
1646
|
-
async createStorageBucket(name, isPublic) {
|
|
1647
|
-
return this.storage.createBucket(name, isPublic);
|
|
1906
|
+
async createStorageBucket(name, isPublic, maxBytes) {
|
|
1907
|
+
return this.storage.createBucket(name, isPublic, maxBytes);
|
|
1648
1908
|
}
|
|
1649
1909
|
async listStorageBuckets() {
|
|
1650
1910
|
return this.storage.listBuckets();
|
|
@@ -1675,6 +1935,9 @@ var BaasClient = class extends HttpClient {
|
|
|
1675
1935
|
async deleteApiKey(keyId) {
|
|
1676
1936
|
return this.apiKeys.delete(keyId);
|
|
1677
1937
|
}
|
|
1938
|
+
async setApiKeyBrand(keyId, emailConfigId) {
|
|
1939
|
+
return this.apiKeys.setBrand(keyId, emailConfigId);
|
|
1940
|
+
}
|
|
1678
1941
|
async getInstanceToken() {
|
|
1679
1942
|
return this.apiKeys.getInstanceToken();
|
|
1680
1943
|
}
|
|
@@ -1749,9 +2012,18 @@ var BaasClient = class extends HttpClient {
|
|
|
1749
2012
|
async getEmailConfig() {
|
|
1750
2013
|
return this.email.getConfig();
|
|
1751
2014
|
}
|
|
2015
|
+
async listEmailConfigs() {
|
|
2016
|
+
return this.email.listConfigs();
|
|
2017
|
+
}
|
|
1752
2018
|
async saveEmailConfig(config) {
|
|
1753
2019
|
return this.email.saveConfig(config);
|
|
1754
2020
|
}
|
|
2021
|
+
async setDefaultEmailConfig(id) {
|
|
2022
|
+
return this.email.setDefaultConfig(id);
|
|
2023
|
+
}
|
|
2024
|
+
async deleteEmailConfig(id) {
|
|
2025
|
+
return this.email.deleteConfig(id);
|
|
2026
|
+
}
|
|
1755
2027
|
async createEmailTemplate(template) {
|
|
1756
2028
|
return this.email.createTemplate(template);
|
|
1757
2029
|
}
|
|
@@ -1900,9 +2172,12 @@ var BaasClient = class extends HttpClient {
|
|
|
1900
2172
|
async purgeAuditLogs(olderThanDays) {
|
|
1901
2173
|
return this.audit.purge(olderThanDays);
|
|
1902
2174
|
}
|
|
2175
|
+
async clearAllAuditLogs() {
|
|
2176
|
+
return this.audit.clearAll();
|
|
2177
|
+
}
|
|
1903
2178
|
// Realtime shortcuts
|
|
1904
|
-
subscribe(table, action, callback) {
|
|
1905
|
-
return this.realtime.subscribe(table, action, callback);
|
|
2179
|
+
subscribe(table, action, callback, filter) {
|
|
2180
|
+
return this.realtime.subscribe(table, action, callback, filter);
|
|
1906
2181
|
}
|
|
1907
2182
|
// Environment shortcuts
|
|
1908
2183
|
async getEnvironmentStatus() {
|