@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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.4.0 — 2026-07-01
4
+
5
+ Backward-compatible additions tracking the golang-baas gap-fix release (refresh tokens, realtime broadcast/presence + server-side filters, per-bucket storage caps). All existing signatures unchanged.
6
+
7
+ ### Added
8
+
9
+ - **Auto token refresh** — `HttpClient` retries once on a 401 via `POST /api/auth/refresh` (concurrent requests share a single in-flight refresh), then replays the original request. `onForceLogout` fires only when the refresh itself fails.
10
+ - **Auth**: `auth.refresh()` (manual refresh), `setRefreshToken`/`getRefreshToken`; `login()` stores the refresh token and `logout()` clears both tokens.
11
+ - **Realtime**: `subscribe(table, action, cb, filter?)` server-side value filters (`eq/neq/gt/gte/lt/lte/in/like`, applied per-subscription — shared filter goes server-side, otherwise each subscriber filters client-side); `subscribeBroadcast(channel, cb)` + `broadcast(channel, payload)` (ephemeral, no DB/RLS); `subscribePresence(channel, state, cb)` (sync/join/leave). All re-join automatically on reconnect.
12
+ - **Storage**: `createBucket(name, isPublic?, maxBytes?)` and `updateBucket(id, { isPublic?, maxBytes? })` per-bucket upload caps; `StorageBucket.max_bytes`.
13
+ - **CLI**: `baas gen types [--out file]` generates TypeScript interfaces from the live schema (`GET /api/schemas/types`).
14
+
3
15
  ## 1.3.0 — 2026-06-04
4
16
 
5
17
  Backward-compatible additions + two dead-route fixes. Validated end-to-end (33 live checks) against a running golang-baas server.
package/dist/cli.js CHANGED
@@ -1,5 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // src/cli/index.ts
4
+ import { writeFileSync as writeFileSync2 } from "fs";
5
+
3
6
  // src/cli/config.ts
4
7
  import { readFileSync, existsSync } from "fs";
5
8
  import { resolve } from "path";
@@ -297,8 +300,10 @@ Usage:
297
300
  baas db list Alias of status
298
301
  baas db push Apply pending migrations and record them
299
302
  baas db baseline Mark existing files as applied without running them
303
+ baas gen types Generate TypeScript types from the live schema
300
304
 
301
305
  Options:
306
+ --out <file> gen types: write to a file instead of stdout
302
307
  --dir <path> Migrations directory (default: cwd or BAAS_MIGRATIONS_DIR)
303
308
  --url <url> BaaS URL (default: BAAS_URL / .env)
304
309
  --token <key> API key (default: BAAS_API_KEY / .env)
@@ -332,6 +337,33 @@ async function main() {
332
337
  console.log(HELP);
333
338
  return;
334
339
  }
340
+ if (group === "gen") {
341
+ if (sub !== "types") {
342
+ console.error(`Unknown gen subcommand: ${sub ?? "(none)"}
343
+ `);
344
+ console.log(HELP);
345
+ process.exit(1);
346
+ }
347
+ const cfg2 = resolveConfig(argv);
348
+ if (!cfg2.baasUrl || !cfg2.token) {
349
+ console.error("ERROR: missing BaaS URL or token (--url/--token, env, or .env).");
350
+ process.exit(1);
351
+ }
352
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
353
+ const res = await fetch(`${cfg2.baasUrl.replace(/\/$/, "")}/api/schemas/types`, {
354
+ headers: { Authorization: `Bearer ${cfg2.token}` }
355
+ });
356
+ const text = await res.text();
357
+ if (!res.ok) throw new Error(`HTTP ${res.status}: ${text}`);
358
+ const out = getArg(argv, "out");
359
+ if (out) {
360
+ writeFileSync2(out, text);
361
+ console.error(`Wrote ${out}`);
362
+ } else {
363
+ process.stdout.write(text);
364
+ }
365
+ return;
366
+ }
335
367
  if (group !== "db") {
336
368
  console.error(`Unknown command: ${group}
337
369
  `);
package/dist/index.cjs CHANGED
@@ -32,8 +32,10 @@ var HttpClient = class {
32
32
  apiKey = "";
33
33
  keyType;
34
34
  token = null;
35
+ refreshToken = null;
35
36
  environment = "prod";
36
37
  _onForceLogout = null;
38
+ _refreshing = null;
37
39
  constructor(url, apiKey, options) {
38
40
  let baseUrl = url || (typeof window !== "undefined" ? window.location.origin : "http://localhost:8080");
39
41
  this.url = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
@@ -41,6 +43,7 @@ var HttpClient = class {
41
43
  this.keyType = options?.keyType ?? "service_role";
42
44
  this.warnIfUnsafeKey();
43
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;
44
47
  }
45
48
  /** SECURITY: a service_role key bypasses RLS and grants admin over every
46
49
  * tenant. If it ends up in a browser bundle, anyone can extract it. Warn
@@ -59,12 +62,59 @@ var HttpClient = class {
59
62
  */
60
63
  setToken(token) {
61
64
  this.token = token;
65
+ if (typeof localStorage === "undefined") return;
62
66
  if (token) {
63
67
  localStorage.setItem("baas_token", token);
64
68
  } else {
65
69
  localStorage.removeItem("baas_token");
66
70
  }
67
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
+ }
68
118
  /**
69
119
  * Register a callback invoked on forced logout (401).
70
120
  * Use this in React Native instead of the default window.location redirect.
@@ -105,7 +155,7 @@ var HttpClient = class {
105
155
  /**
106
156
  * Core HTTP request method - DRY principle
107
157
  */
108
- async request(endpoint, options = {}) {
158
+ async request(endpoint, options = {}, _isRetry = false) {
109
159
  const { method = "GET", body, headers: customHeaders, skipAuth = false } = options;
110
160
  const headers = { ...this.getHeaders(), ...customHeaders };
111
161
  const fetchOptions = {
@@ -123,6 +173,9 @@ var HttpClient = class {
123
173
  const data = await res.json().catch(() => null);
124
174
  if (!res.ok) {
125
175
  if (res.status === 401 && !skipAuth) {
176
+ if (!_isRetry && await this.tryRefresh()) {
177
+ return this.request(endpoint, options, true);
178
+ }
126
179
  this.forceLogout();
127
180
  }
128
181
  if (res.status === 403 && data?.error === "ip_not_allowed") {
@@ -167,7 +220,11 @@ var HttpClient = class {
167
220
  }
168
221
  logout() {
169
222
  this.token = null;
170
- localStorage.removeItem("baas_token");
223
+ this.refreshToken = null;
224
+ if (typeof localStorage !== "undefined") {
225
+ localStorage.removeItem("baas_token");
226
+ localStorage.removeItem("baas_refresh_token");
227
+ }
171
228
  }
172
229
  forceLogout() {
173
230
  this.logout();
@@ -408,17 +465,27 @@ function createAuthModule(client) {
408
465
  throw new Error(data?.error || `Login failed: ${res.status}`);
409
466
  }
410
467
  if (data.token) {
411
- client.token = data.token;
412
- localStorage.setItem("baas_token", data.token);
468
+ client.setToken(data.token);
413
469
  }
470
+ if (data.refresh_token) {
471
+ client.setRefreshToken(data.refresh_token);
472
+ }
473
+ return data;
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);
414
482
  return data;
415
483
  },
416
484
  async logout() {
417
485
  try {
418
486
  await post("/api/logout");
419
487
  } finally {
420
- client.token = null;
421
- localStorage.removeItem("baas_token");
488
+ client.logout();
422
489
  }
423
490
  },
424
491
  // Anonymous sign-in (Supabase parity). Response carries access_token (+ a
@@ -703,11 +770,11 @@ function createStorageModule(client) {
703
770
  async deleteFile(fileId) {
704
771
  return del(`/api/storage/files/${fileId}`);
705
772
  },
706
- async createBucket(name, isPublic = false) {
707
- 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 });
708
775
  },
709
776
  async updateBucket(bucketId, opts) {
710
- return put(`/api/storage/buckets/${bucketId}`, { is_public: opts.isPublic });
777
+ return put(`/api/storage/buckets/${bucketId}`, { is_public: opts.isPublic, max_bytes: opts.maxBytes });
711
778
  },
712
779
  async listBuckets() {
713
780
  return get("/api/storage/buckets");
@@ -924,9 +991,18 @@ function createEmailModule(client) {
924
991
  async getConfig() {
925
992
  return get("/api/email/config");
926
993
  },
994
+ async listConfigs() {
995
+ return get("/api/email/configs");
996
+ },
927
997
  async saveConfig(config) {
928
998
  return post("/api/email/config", config);
929
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
+ },
930
1006
  async createTemplate(template) {
931
1007
  return post("/api/email/templates", template);
932
1008
  },
@@ -1078,6 +1154,9 @@ function createAuditModule(client) {
1078
1154
  },
1079
1155
  async purge(olderThanDays) {
1080
1156
  return del(`/api/audit/purge?older_than_days=${olderThanDays}`);
1157
+ },
1158
+ async clearAll() {
1159
+ return del(`/api/audit/clear`);
1081
1160
  }
1082
1161
  };
1083
1162
  }
@@ -1178,6 +1257,16 @@ var RealtimeService = class {
1178
1257
  }
1179
1258
  socket = null;
1180
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();
1181
1270
  reconnectAttempts = 0;
1182
1271
  maxReconnectAttempts = 5;
1183
1272
  reconnectInterval = 3e3;
@@ -1206,6 +1295,12 @@ var RealtimeService = class {
1206
1295
  for (const table of this.subscribers.keys()) {
1207
1296
  this.sendSub("subscribe", table);
1208
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
+ }
1209
1304
  resolve();
1210
1305
  };
1211
1306
  socket.onmessage = (event) => {
@@ -1241,17 +1336,15 @@ var RealtimeService = class {
1241
1336
  /**
1242
1337
  * Subscribe to changes on a specific table
1243
1338
  */
1244
- async subscribe(table, action, callback) {
1339
+ async subscribe(table, action, callback, filter) {
1245
1340
  await this.connect();
1246
1341
  const isNewTable = !this.subscribers.has(table);
1247
1342
  if (isNewTable) {
1248
1343
  this.subscribers.set(table, /* @__PURE__ */ new Set());
1249
1344
  }
1250
- const sub = { action, callback };
1345
+ const sub = { action, callback, filter: filter && Object.keys(filter).length > 0 ? filter : void 0 };
1251
1346
  this.subscribers.get(table).add(sub);
1252
- if (isNewTable && table !== "*") {
1253
- this.sendSub("subscribe", table);
1254
- }
1347
+ if (table !== "*") this.syncServerFilter(table, isNewTable);
1255
1348
  return {
1256
1349
  unsubscribe: () => {
1257
1350
  const tableSubs = this.subscribers.get(table);
@@ -1259,39 +1352,194 @@ var RealtimeService = class {
1259
1352
  tableSubs.delete(sub);
1260
1353
  if (tableSubs.size === 0) {
1261
1354
  this.subscribers.delete(table);
1355
+ this.filters.delete(table);
1262
1356
  if (table !== "*") this.sendSub("unsubscribe", table);
1357
+ } else if (table !== "*") {
1358
+ this.syncServerFilter(table, false);
1263
1359
  }
1264
1360
  }
1265
1361
  }
1266
1362
  };
1267
1363
  }
1268
- /** Send a subscribe/unsubscribe control frame to the server. */
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). */
1269
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) {
1270
1408
  if (this.socket?.readyState === WebSocket.OPEN) {
1271
- this.socket.send(JSON.stringify({ event, table }));
1409
+ this.socket.send(JSON.stringify(frame));
1272
1410
  }
1273
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
+ }
1274
1459
  /**
1275
1460
  * Handle incoming CDC events from the server
1276
1461
  */
1277
1462
  handleEvent(payload) {
1278
- const tableSubs = this.subscribers.get(payload.table);
1279
- if (tableSubs) {
1280
- for (const sub of tableSubs) {
1281
- if (sub.action === "*" || sub.action.toLowerCase() === payload.action.toLowerCase()) {
1282
- sub.callback(payload);
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);
1283
1503
  }
1284
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;
1285
1509
  }
1286
- const wildcardSubs = this.subscribers.get("*");
1287
- if (wildcardSubs) {
1288
- for (const sub of wildcardSubs) {
1289
- if (sub.action === "*" || sub.action.toLowerCase() === payload.action.toLowerCase()) {
1290
- sub.callback(payload);
1291
- }
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;
1292
1533
  }
1534
+ default:
1535
+ return false;
1293
1536
  }
1294
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
+ }
1295
1543
  /**
1296
1544
  * Attempt to reconnect on connection loss
1297
1545
  */
@@ -1329,8 +1577,17 @@ function createRealtimeModule(client) {
1329
1577
  typeof WebSocket !== "undefined" ? WebSocket : void 0
1330
1578
  );
1331
1579
  return {
1332
- async subscribe(table, action, callback) {
1333
- 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);
1334
1591
  }
1335
1592
  };
1336
1593
  }
@@ -1353,6 +1610,9 @@ function createApiKeysModule(client) {
1353
1610
  async delete(keyId) {
1354
1611
  return del(`/api/api-keys/${keyId}`);
1355
1612
  },
1613
+ async setBrand(keyId, emailConfigId) {
1614
+ return post(`/api/api-keys/${keyId}/brand`, { email_config_id: emailConfigId });
1615
+ },
1356
1616
  async getInstanceToken() {
1357
1617
  return get("/api/api-keys/instance");
1358
1618
  },
@@ -1671,8 +1931,8 @@ var BaasClient = class extends HttpClient {
1671
1931
  async deleteStorageFile(fileId) {
1672
1932
  return this.storage.deleteFile(fileId);
1673
1933
  }
1674
- async createStorageBucket(name, isPublic) {
1675
- return this.storage.createBucket(name, isPublic);
1934
+ async createStorageBucket(name, isPublic, maxBytes) {
1935
+ return this.storage.createBucket(name, isPublic, maxBytes);
1676
1936
  }
1677
1937
  async listStorageBuckets() {
1678
1938
  return this.storage.listBuckets();
@@ -1703,6 +1963,9 @@ var BaasClient = class extends HttpClient {
1703
1963
  async deleteApiKey(keyId) {
1704
1964
  return this.apiKeys.delete(keyId);
1705
1965
  }
1966
+ async setApiKeyBrand(keyId, emailConfigId) {
1967
+ return this.apiKeys.setBrand(keyId, emailConfigId);
1968
+ }
1706
1969
  async getInstanceToken() {
1707
1970
  return this.apiKeys.getInstanceToken();
1708
1971
  }
@@ -1777,9 +2040,18 @@ var BaasClient = class extends HttpClient {
1777
2040
  async getEmailConfig() {
1778
2041
  return this.email.getConfig();
1779
2042
  }
2043
+ async listEmailConfigs() {
2044
+ return this.email.listConfigs();
2045
+ }
1780
2046
  async saveEmailConfig(config) {
1781
2047
  return this.email.saveConfig(config);
1782
2048
  }
2049
+ async setDefaultEmailConfig(id) {
2050
+ return this.email.setDefaultConfig(id);
2051
+ }
2052
+ async deleteEmailConfig(id) {
2053
+ return this.email.deleteConfig(id);
2054
+ }
1783
2055
  async createEmailTemplate(template) {
1784
2056
  return this.email.createTemplate(template);
1785
2057
  }
@@ -1928,9 +2200,12 @@ var BaasClient = class extends HttpClient {
1928
2200
  async purgeAuditLogs(olderThanDays) {
1929
2201
  return this.audit.purge(olderThanDays);
1930
2202
  }
2203
+ async clearAllAuditLogs() {
2204
+ return this.audit.clearAll();
2205
+ }
1931
2206
  // Realtime shortcuts
1932
- subscribe(table, action, callback) {
1933
- return this.realtime.subscribe(table, action, callback);
2207
+ subscribe(table, action, callback, filter) {
2208
+ return this.realtime.subscribe(table, action, callback, filter);
1934
2209
  }
1935
2210
  // Environment shortcuts
1936
2211
  async getEnvironmentStatus() {