@giaeulate/baas-sdk 1.2.0 → 1.3.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/dist/index.d.ts CHANGED
@@ -45,13 +45,27 @@ type RealtimeCallback<T = any> = (payload: RealtimePayload<T>) => void;
45
45
  * Base class providing HTTP request infrastructure for all SDK modules
46
46
  */
47
47
 
48
+ /** Privilege class of the API key. anon = RLS applies (safe for browsers);
49
+ * service_role = RLS bypass / admin (server-side only). Both share the
50
+ * `baas_sk_` prefix, so the type cannot be inferred — it must be declared. */
51
+ type KeyType = 'anon' | 'service_role';
52
+ interface ClientOptions {
53
+ /** Defaults to 'service_role' for backward-compat. Set 'anon' in any client
54
+ * bundle (browser / mobile app) — see warnIfUnsafeKey. */
55
+ keyType?: KeyType;
56
+ }
48
57
  declare class HttpClient {
49
58
  url: string;
50
59
  apiKey: string;
60
+ keyType: KeyType;
51
61
  token: string | null;
52
62
  private environment;
53
63
  private _onForceLogout;
54
- constructor(url?: string, apiKey?: string);
64
+ constructor(url?: string, apiKey?: string, options?: ClientOptions);
65
+ /** SECURITY: a service_role key bypasses RLS and grants admin over every
66
+ * tenant. If it ends up in a browser bundle, anyone can extract it. Warn
67
+ * loudly when a non-anon key is constructed in a browser context. */
68
+ private warnIfUnsafeKey;
55
69
  /**
56
70
  * Set the auth token manually (e.g. restoring from SecureStore in React Native).
57
71
  * Persists to both the in-memory property and localStorage so getDynamicToken() works.
@@ -133,7 +147,23 @@ declare class QueryBuilder {
133
147
  is(column: string, value: any): this;
134
148
  in(column: string, values: any[]): this;
135
149
  not(column: string, operator: string, value: any): this;
150
+ /** Array/jsonb/range contains (@>). Arrays become a `{a,b}` literal. */
151
+ contains(column: string, values: any): this;
152
+ /** Array/jsonb/range contained-by (<@). */
153
+ containedBy(column: string, values: any): this;
154
+ /** Array/range overlap (&&). */
155
+ overlaps(column: string, values: any): this;
156
+ /** Full-text search (@@). type: fts|plfts|phfts|wfts (tsquery flavour). */
157
+ textSearch(column: string, query: string, type?: 'fts' | 'plfts' | 'phfts' | 'wfts'): this;
158
+ private arrayLiteral;
159
+ /** OR group: `or=(c1,c2)`. Members are dotted `col.op.value` strings. */
136
160
  or(filters: string): this;
161
+ /** AND group: `and=(c1,c2)` — e.g. a range `qty.gt.5,qty.lt.20`. */
162
+ and(filters: string): this;
163
+ /** Negated AND group: `not.and=(...)`. */
164
+ notAnd(filters: string): this;
165
+ /** Negated OR group: `not.or=(...)`. */
166
+ notOr(filters: string): this;
137
167
  order(column: string, { ascending }?: {
138
168
  ascending?: boolean | undefined;
139
169
  }): this;
@@ -177,6 +207,26 @@ declare class QueryBuilder {
177
207
  } | {
178
208
  success: boolean;
179
209
  }>;
210
+ /** Insert-or-update on conflict. [onConflict] is the conflict-target column(s). */
211
+ upsert(data: any, onConflict: string): Promise<{
212
+ data: null;
213
+ error: any;
214
+ status: number;
215
+ } | {
216
+ data: any;
217
+ error: null;
218
+ status: number;
219
+ }>;
220
+ /**
221
+ * Count rows matching the current filters (PostgREST parity). Sends
222
+ * `Prefer: count=exact` and parses the total from the `Content-Range`
223
+ * response header. Returns `{ count, error, status }`.
224
+ */
225
+ count(): Promise<{
226
+ count: number;
227
+ error: string | null;
228
+ status: number;
229
+ }>;
180
230
  private getHeaders;
181
231
  private handleResponse;
182
232
  }
@@ -205,6 +255,10 @@ interface AuthModule {
205
255
  validateResetToken(token: string): Promise<any>;
206
256
  verifyEmail(token: string): Promise<any>;
207
257
  requestEmailVerification(): Promise<any>;
258
+ signInAnonymous(): Promise<any>;
259
+ requestOtp(email: string, createUser?: boolean): Promise<any>;
260
+ verifyOtp(type: 'email_otp' | 'magic_link', email: string, token: string): Promise<any>;
261
+ upgradeAnonymous(email: string, password: string): Promise<any>;
208
262
  getAuthProviders(): Promise<any>;
209
263
  getAuthProvider(provider: string): Promise<any>;
210
264
  configureAuthProvider(provider: string, clientID: string, clientSecret: string, enabled: boolean): Promise<any>;
@@ -310,12 +364,21 @@ interface StorageModule {
310
364
  upload(file: File, bucketId?: string): Promise<StorageFile>;
311
365
  /** List all files across buckets. */
312
366
  listFiles(): Promise<StorageFile[]>;
313
- /** Get file metadata + a 15-min presigned download URL. */
314
- getFile(fileId: string): Promise<StorageFileWithUrl>;
367
+ /** Get file metadata + a presigned download URL. expiresIn (seconds) controls
368
+ * the URL lifetime (default 15m, server cap 7d). */
369
+ getFile(fileId: string, expiresIn?: number): Promise<StorageFileWithUrl>;
370
+ /** Create a time-limited signed download URL (Supabase createSignedUrl parity). */
371
+ createSignedUrl(fileId: string, expiresIn?: number): Promise<{
372
+ signedUrl: string;
373
+ }>;
315
374
  /** Delete a file by ID. */
316
375
  deleteFile(fileId: string): Promise<void>;
317
376
  /** Create a new bucket. */
318
377
  createBucket(name: string, isPublic?: boolean): Promise<StorageBucket>;
378
+ /** Update mutable bucket fields (is_public). */
379
+ updateBucket(bucketId: string, opts: {
380
+ isPublic?: boolean;
381
+ }): Promise<StorageBucket>;
319
382
  /** List all buckets. */
320
383
  listBuckets(): Promise<StorageBucket[]>;
321
384
  /** Get bucket info by ID. */
@@ -324,6 +387,10 @@ interface StorageModule {
324
387
  deleteBucket(bucketId: string): Promise<void>;
325
388
  /** List files inside a specific bucket. */
326
389
  listBucketFiles(bucketId: string): Promise<StorageFile[]>;
390
+ /** Download a file's bytes via the backend download route (streams + enforces
391
+ * the bucket's public/RLS rules; the Bearer is sent so private files work).
392
+ * Returns a Blob. */
393
+ download(fileId: string): Promise<Blob>;
327
394
  /** @deprecated Use upload(file, bucketId?) instead. */
328
395
  upload(table: string, file: File): Promise<any>;
329
396
  }
@@ -562,6 +629,9 @@ interface AuditLogsOptions {
562
629
  interface AuditModule {
563
630
  list(options?: AuditLogsOptions): Promise<any>;
564
631
  getActions(): Promise<any>;
632
+ exportLogs(format: 'csv' | 'json', options?: AuditLogsOptions): Promise<any>;
633
+ purgePreview(olderThanDays: number): Promise<any>;
634
+ purge(olderThanDays: number): Promise<any>;
565
635
  }
566
636
 
567
637
  /**
@@ -712,6 +782,69 @@ interface IPWhitelistModule {
712
782
  delete(id: string): Promise<any>;
713
783
  }
714
784
 
785
+ /**
786
+ * Cache Management Module
787
+ */
788
+
789
+ type CacheStrategy = 'no_cache' | 'ttl_only' | 'cdc_invalidation' | 'write_through';
790
+ interface CacheStatus {
791
+ connected: boolean;
792
+ backend: string;
793
+ version: string;
794
+ ping_latency_ms: number;
795
+ circuit_state: string;
796
+ cdc_lag_seconds: number;
797
+ }
798
+ interface CacheStats {
799
+ memory_used: string;
800
+ memory_used_bytes: number;
801
+ db_size: number;
802
+ uptime_seconds: number;
803
+ ops_per_sec: number;
804
+ namespaces_total: number;
805
+ }
806
+ interface CachePolicy {
807
+ namespace: string;
808
+ strategy: CacheStrategy;
809
+ ttl_seconds: number;
810
+ use_versioning: boolean;
811
+ is_default: boolean;
812
+ }
813
+ interface CacheKey {
814
+ key: string;
815
+ ttl_seconds: number;
816
+ }
817
+ interface CacheKeysResponse {
818
+ keys: CacheKey[];
819
+ next_cursor: number;
820
+ has_more: boolean;
821
+ }
822
+ interface CacheKeyInspect {
823
+ key: string;
824
+ exists: boolean;
825
+ ttl_seconds: number;
826
+ size_bytes: number;
827
+ value_preview: string;
828
+ value_truncated: boolean;
829
+ }
830
+ interface CacheModule {
831
+ getStatus(): Promise<CacheStatus>;
832
+ getStats(): Promise<CacheStats>;
833
+ listPolicies(): Promise<{
834
+ policies: CachePolicy[];
835
+ }>;
836
+ updatePolicy(namespace: string, body: {
837
+ strategy: CacheStrategy;
838
+ ttl_seconds: number;
839
+ use_versioning: boolean;
840
+ }): Promise<any>;
841
+ removePolicy(namespace: string): Promise<any>;
842
+ invalidateNamespace(namespace: string): Promise<any>;
843
+ invalidateAll(): Promise<any>;
844
+ listKeys(prefix: string, cursor?: number, count?: number): Promise<CacheKeysResponse>;
845
+ inspectKey(key: string): Promise<CacheKeyInspect>;
846
+ }
847
+
715
848
  /**
716
849
  * Main BaaS Client - Composes all feature modules
717
850
  *
@@ -753,11 +886,20 @@ declare class BaasClient extends HttpClient {
753
886
  readonly corsOrigins: CorsOriginsModule;
754
887
  readonly policies: PoliciesModule;
755
888
  readonly ipWhitelist: IPWhitelistModule;
756
- constructor(url?: string, apiKey?: string);
889
+ readonly cache: CacheModule;
890
+ constructor(url?: string, apiKey?: string, options?: ClientOptions);
757
891
  /**
758
892
  * Create a query builder for fluent data queries
759
893
  */
760
894
  from(table: string): QueryBuilder;
895
+ /**
896
+ * Invoke a PL/pgSQL function (PostgREST `.rpc()` parity). Runs under the
897
+ * caller's RLS context; args bind by name and are $N-safe. Pass [params] for
898
+ * POST (named args in the body) or set `opts.get = true` for the GET variant.
899
+ */
900
+ rpc(fn: string, params?: Record<string, any>, opts?: {
901
+ get?: boolean;
902
+ }): Promise<any>;
761
903
  login(email: string, password: string): Promise<any>;
762
904
  verifyMFA(mfaToken: string, code: string): Promise<any>;
763
905
  getProfile(): Promise<any>;
@@ -873,6 +1015,9 @@ declare class BaasClient extends HttpClient {
873
1015
  testWebhook(id: string): Promise<any>;
874
1016
  listAuditLogs(options?: any): Promise<any>;
875
1017
  getAuditActions(): Promise<any>;
1018
+ exportAuditLogs(format: 'csv' | 'json', options?: any): Promise<any>;
1019
+ previewPurgeAuditLogs(olderThanDays: number): Promise<any>;
1020
+ purgeAuditLogs(olderThanDays: number): Promise<any>;
876
1021
  subscribe<T = any>(table: string, action: RealtimeAction, callback: RealtimeCallback<T>): Promise<Subscription>;
877
1022
  getEnvironmentStatus(): Promise<EnvironmentStatus>;
878
1023
  initTestEnvironment(): Promise<any>;
@@ -889,4 +1034,4 @@ declare class BaasClient extends HttpClient {
889
1034
  deleteIPWhitelistEntry(id: string): Promise<any>;
890
1035
  }
891
1036
 
892
- export { type ApiKeysModule, type ApiResponse, type ApplicationLogsOptions, type AuditLogsOptions, type AuditModule, type AuthModule, BaasClient, type BackupsModule, type BranchesModule, type CorsOriginsModule, type CreatePolicyInput, type DatabaseModule, type EmailConfig, type EmailModule, type EmailTemplate, type EnvVarsModule, type EnvironmentsModule, type FunctionsModule, type GraphQLModule, HttpClient, type HttpMethod, type IPWhitelistEntry, type IPWhitelistModule, type JobInput, type JobUpdateInput, type JobsModule, type LogDrainInput, type LogDrainUpdateInput, type LogDrainsModule, type MetricsModule, type MigrationsModule, type PaginationOptions, type PoliciesModule, type Policy, QueryBuilder, type QueryFilter, type RealtimeModule, type RequestLogsOptions, type RequestOptions, type SearchModule, type SearchOptions, type SendEmailInput, type StorageBucket, type StorageFile, type StorageFileWithUrl, type StorageModule, type Subscription, type TimeseriesOptions, type UsersModule, type WebhookInput, type WebhookUpdateInput, type WebhooksModule };
1037
+ export { type ApiKeysModule, type ApiResponse, type ApplicationLogsOptions, type AuditLogsOptions, type AuditModule, type AuthModule, BaasClient, type BackupsModule, type BranchesModule, type CacheKey, type CacheKeyInspect, type CacheKeysResponse, type CacheModule, type CachePolicy, type CacheStats, type CacheStatus, type CacheStrategy, type CorsOriginsModule, type CreatePolicyInput, type DatabaseModule, type EmailConfig, type EmailModule, type EmailTemplate, type EnvVarsModule, type EnvironmentsModule, type FunctionsModule, type GraphQLModule, HttpClient, type HttpMethod, type IPWhitelistEntry, type IPWhitelistModule, type JobInput, type JobUpdateInput, type JobsModule, type LogDrainInput, type LogDrainUpdateInput, type LogDrainsModule, type MetricsModule, type MigrationsModule, type PaginationOptions, type PoliciesModule, type Policy, QueryBuilder, type QueryFilter, type RealtimeModule, type RequestLogsOptions, type RequestOptions, type SearchModule, type SearchOptions, type SendEmailInput, type StorageBucket, type StorageFile, type StorageFileWithUrl, type StorageModule, type Subscription, type TimeseriesOptions, type UsersModule, type WebhookInput, type WebhookUpdateInput, type WebhooksModule };
package/dist/index.js CHANGED
@@ -2,14 +2,28 @@
2
2
  var HttpClient = class {
3
3
  url;
4
4
  apiKey = "";
5
+ keyType;
5
6
  token = null;
6
7
  environment = "prod";
7
8
  _onForceLogout = null;
8
- constructor(url, apiKey) {
9
+ constructor(url, apiKey, options) {
9
10
  let baseUrl = url || (typeof window !== "undefined" ? window.location.origin : "http://localhost:8080");
10
11
  this.url = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
11
12
  if (apiKey) this.apiKey = apiKey;
12
- this.token = this.cleanValue(localStorage.getItem("baas_token"));
13
+ this.keyType = options?.keyType ?? "service_role";
14
+ this.warnIfUnsafeKey();
15
+ this.token = typeof localStorage !== "undefined" ? this.cleanValue(localStorage.getItem("baas_token")) : null;
16
+ }
17
+ /** SECURITY: a service_role key bypasses RLS and grants admin over every
18
+ * tenant. If it ends up in a browser bundle, anyone can extract it. Warn
19
+ * loudly when a non-anon key is constructed in a browser context. */
20
+ warnIfUnsafeKey() {
21
+ const inBrowser = typeof window !== "undefined" && typeof document !== "undefined";
22
+ if (inBrowser && this.apiKey && this.keyType !== "anon") {
23
+ console.warn(
24
+ '[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" }.'
25
+ );
26
+ }
13
27
  }
14
28
  /**
15
29
  * Set the auth token manually (e.g. restoring from SecureStore in React Native).
@@ -86,7 +100,14 @@ var HttpClient = class {
86
100
  if (res.status === 403 && data?.error === "ip_not_allowed") {
87
101
  this.handleIPBlocked(data.ip);
88
102
  }
89
- return { error: data?.error || `Request failed: ${res.status}`, ...data };
103
+ const rateLimited = res.status === 429;
104
+ const retryHint = res.headers.get("retry-after") || res.headers.get("x-ratelimit-reset");
105
+ return {
106
+ error: data?.error || `Request failed: ${res.status}`,
107
+ status: res.status,
108
+ ...rateLimited ? { rateLimited: true, retryAfter: retryHint ? parseInt(retryHint, 10) : void 0 } : {},
109
+ ...data
110
+ };
90
111
  }
91
112
  return data;
92
113
  }
@@ -203,10 +224,49 @@ var QueryBuilder = class {
203
224
  this.queryParams.set(column, `not.${operator}.${value}`);
204
225
  return this;
205
226
  }
227
+ /** Array/jsonb/range contains (@>). Arrays become a `{a,b}` literal. */
228
+ contains(column, values) {
229
+ this.queryParams.set(column, `cs.${this.arrayLiteral(values)}`);
230
+ return this;
231
+ }
232
+ /** Array/jsonb/range contained-by (<@). */
233
+ containedBy(column, values) {
234
+ this.queryParams.set(column, `cd.${this.arrayLiteral(values)}`);
235
+ return this;
236
+ }
237
+ /** Array/range overlap (&&). */
238
+ overlaps(column, values) {
239
+ this.queryParams.set(column, `ov.${this.arrayLiteral(values)}`);
240
+ return this;
241
+ }
242
+ /** Full-text search (@@). type: fts|plfts|phfts|wfts (tsquery flavour). */
243
+ textSearch(column, query, type = "fts") {
244
+ this.queryParams.set(column, `${type}.${query}`);
245
+ return this;
246
+ }
247
+ arrayLiteral(values) {
248
+ return Array.isArray(values) ? `{${values.join(",")}}` : `${values}`;
249
+ }
250
+ /** OR group: `or=(c1,c2)`. Members are dotted `col.op.value` strings. */
206
251
  or(filters) {
207
252
  this.queryParams.set("or", `(${filters})`);
208
253
  return this;
209
254
  }
255
+ /** AND group: `and=(c1,c2)` — e.g. a range `qty.gt.5,qty.lt.20`. */
256
+ and(filters) {
257
+ this.queryParams.set("and", `(${filters})`);
258
+ return this;
259
+ }
260
+ /** Negated AND group: `not.and=(...)`. */
261
+ notAnd(filters) {
262
+ this.queryParams.set("not.and", `(${filters})`);
263
+ return this;
264
+ }
265
+ /** Negated OR group: `not.or=(...)`. */
266
+ notOr(filters) {
267
+ this.queryParams.set("not.or", `(${filters})`);
268
+ return this;
269
+ }
210
270
  order(column, { ascending = true } = {}) {
211
271
  this.queryParams.set("order", `${column}.${ascending ? "asc" : "desc"}`);
212
272
  return this;
@@ -250,6 +310,34 @@ var QueryBuilder = class {
250
310
  if (res.status === 204) return { success: true };
251
311
  return await this.handleResponse(res);
252
312
  }
313
+ /** Insert-or-update on conflict. [onConflict] is the conflict-target column(s). */
314
+ async upsert(data, onConflict) {
315
+ const res = await fetch(`${this.url}/api/v1/data/${this.table}/upsert`, {
316
+ method: "POST",
317
+ headers: this.getHeaders(),
318
+ body: JSON.stringify({ ...data, on_conflict: onConflict })
319
+ });
320
+ return await this.handleResponse(res);
321
+ }
322
+ /**
323
+ * Count rows matching the current filters (PostgREST parity). Sends
324
+ * `Prefer: count=exact` and parses the total from the `Content-Range`
325
+ * response header. Returns `{ count, error, status }`.
326
+ */
327
+ async count() {
328
+ const res = await fetch(`${this.url}/api/v1/data/${this.table}?${this.queryParams.toString()}`, {
329
+ headers: { ...this.getHeaders(), Prefer: "count=exact", "Cache-Control": "no-cache" },
330
+ cache: "no-store"
331
+ });
332
+ if (!res.ok) {
333
+ if (res.status === 401) this.client.forceLogout();
334
+ const body = await res.json().catch(() => null);
335
+ return { count: 0, error: body?.error || "Request failed", status: res.status };
336
+ }
337
+ const cr = res.headers.get("content-range");
338
+ const total = cr && cr.includes("/") ? parseInt(cr.split("/").pop() || "", 10) : NaN;
339
+ return { count: Number.isNaN(total) ? 0 : total, error: null, status: res.status };
340
+ }
253
341
  getHeaders() {
254
342
  return this.client.getHeaders();
255
343
  }
@@ -305,6 +393,43 @@ function createAuthModule(client) {
305
393
  localStorage.removeItem("baas_token");
306
394
  }
307
395
  },
396
+ // Anonymous sign-in (Supabase parity). Response carries access_token (+ a
397
+ // back-compat `token`); store whichever is present.
398
+ async signInAnonymous() {
399
+ const data = await post("/api/auth/anonymous");
400
+ const tok = data?.access_token || data?.token;
401
+ if (tok) {
402
+ client.token = tok;
403
+ localStorage.setItem("baas_token", tok);
404
+ }
405
+ return data;
406
+ },
407
+ // Passwordless: request a magic link + 6-digit email OTP. Always resolves on
408
+ // a well-formed request (no account enumeration).
409
+ async requestOtp(email, createUser = true) {
410
+ return post("/api/auth/otp", { email, create_user: createUser });
411
+ },
412
+ // Verify a passwordless OTP/magic-link and complete sign-in.
413
+ async verifyOtp(type, email, token) {
414
+ const data = await post("/api/auth/verify", { type, email, token });
415
+ const tok = data?.access_token || data?.token;
416
+ if (tok) {
417
+ client.token = tok;
418
+ localStorage.setItem("baas_token", tok);
419
+ }
420
+ return data;
421
+ },
422
+ // Upgrade the current anonymous guest to a real account (preserves user id).
423
+ // Requires the anonymous Bearer to be set (call after signInAnonymous).
424
+ async upgradeAnonymous(email, password) {
425
+ const data = await post("/api/auth/upgrade", { email, password });
426
+ const tok = data?.access_token || data?.token;
427
+ if (tok) {
428
+ client.token = tok;
429
+ localStorage.setItem("baas_token", tok);
430
+ }
431
+ return data;
432
+ },
308
433
  async verifyMFA(mfaToken, code) {
309
434
  const data = await post("/api/mfa/finalize", { mfa_token: mfaToken, code });
310
435
  if (data.token) {
@@ -502,6 +627,7 @@ function createDatabaseModule(client) {
502
627
  function createStorageModule(client) {
503
628
  const get = (endpoint) => client.get(endpoint);
504
629
  const post = (endpoint, body) => client.post(endpoint, body);
630
+ const put = (endpoint, body) => client.put(endpoint, body);
505
631
  const del = (endpoint) => client.delete(endpoint);
506
632
  async function uploadFile(fileOrTable, fileOrBucketId) {
507
633
  let file;
@@ -536,8 +662,15 @@ function createStorageModule(client) {
536
662
  async listFiles() {
537
663
  return get("/api/storage/files");
538
664
  },
539
- async getFile(fileId) {
540
- return get(`/api/storage/files/${fileId}`);
665
+ async getFile(fileId, expiresIn) {
666
+ const q = expiresIn && expiresIn > 0 ? `?expiresIn=${expiresIn}` : "";
667
+ return get(`/api/storage/files/${fileId}${q}`);
668
+ },
669
+ async createSignedUrl(fileId, expiresIn) {
670
+ const res = await get(
671
+ `/api/storage/files/${fileId}${expiresIn && expiresIn > 0 ? `?expiresIn=${expiresIn}` : ""}`
672
+ );
673
+ return { signedUrl: res?.url };
541
674
  },
542
675
  async deleteFile(fileId) {
543
676
  return del(`/api/storage/files/${fileId}`);
@@ -545,6 +678,9 @@ function createStorageModule(client) {
545
678
  async createBucket(name, isPublic = false) {
546
679
  return post("/api/storage/buckets", { name, is_public: isPublic });
547
680
  },
681
+ async updateBucket(bucketId, opts) {
682
+ return put(`/api/storage/buckets/${bucketId}`, { is_public: opts.isPublic });
683
+ },
548
684
  async listBuckets() {
549
685
  return get("/api/storage/buckets");
550
686
  },
@@ -556,6 +692,17 @@ function createStorageModule(client) {
556
692
  },
557
693
  async listBucketFiles(bucketId) {
558
694
  return get(`/api/storage/buckets/${bucketId}/files`);
695
+ },
696
+ async download(fileId) {
697
+ const res = await fetch(`${client.url}/api/storage/files/${fileId}/download`, {
698
+ headers: client.getHeaders("")
699
+ });
700
+ if (!res.ok) {
701
+ if (res.status === 401) client.forceLogout();
702
+ const err = await res.json().catch(() => ({ error: `Download failed: ${res.status}` }));
703
+ throw new Error(err.error ?? `Download failed: ${res.status}`);
704
+ }
705
+ return res.blob();
559
706
  }
560
707
  };
561
708
  }
@@ -656,7 +803,9 @@ function createFunctionsModule(client) {
656
803
  return del(`/api/functions/${id}`);
657
804
  },
658
805
  async update(id, code, options) {
659
- return put(`/api/functions/${id}`, { code, ...options });
806
+ const current = await get(`/api/functions/${id}`);
807
+ const name = current?.name ?? current?.data?.name;
808
+ return post("/api/functions", { name, code, ...options });
660
809
  },
661
810
  async executeCode(code, options) {
662
811
  return post("/api/functions/execute", { code, ...options });
@@ -802,7 +951,9 @@ function createGraphQLModule(client) {
802
951
  return post("/api/graphql", { query, variables });
803
952
  },
804
953
  async getSchema() {
805
- return get("/api/graphql/schema");
954
+ return post("/api/graphql", {
955
+ query: "{ __schema { queryType { name } mutationType { name } types { name kind } } }"
956
+ });
806
957
  },
807
958
  getPlaygroundUrl() {
808
959
  return `${client.url}/api/graphql/playground`;
@@ -848,6 +999,7 @@ function createMetricsModule(client) {
848
999
  // src/modules/audit.ts
849
1000
  function createAuditModule(client) {
850
1001
  const get = (endpoint) => client.get(endpoint);
1002
+ const del = (endpoint) => client.delete(endpoint);
851
1003
  return {
852
1004
  async list(options) {
853
1005
  const params = new URLSearchParams();
@@ -864,6 +1016,40 @@ function createAuditModule(client) {
864
1016
  },
865
1017
  async getActions() {
866
1018
  return get("/api/audit/actions");
1019
+ },
1020
+ async exportLogs(format, options) {
1021
+ const params = new URLSearchParams();
1022
+ params.set("format", format);
1023
+ if (options?.action) params.set("action", options.action);
1024
+ if (options?.method) params.set("method", options.method);
1025
+ if (options?.path) params.set("path", options.path);
1026
+ if (options?.status_code) params.set("status_code", options.status_code.toString());
1027
+ if (options?.start_date) params.set("start_date", options.start_date);
1028
+ if (options?.end_date) params.set("end_date", options.end_date);
1029
+ const res = await fetch(`${client.url}/api/audit/export?${params.toString()}`, {
1030
+ headers: client.getHeaders(),
1031
+ credentials: "include"
1032
+ });
1033
+ if (!res.ok) {
1034
+ const data = await res.json();
1035
+ return { error: data.error || `Export failed: ${res.status}` };
1036
+ }
1037
+ const blob = await res.blob();
1038
+ const url = window.URL.createObjectURL(blob);
1039
+ const a = document.createElement("a");
1040
+ a.href = url;
1041
+ a.download = `audit_logs_${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.${format}`;
1042
+ document.body.appendChild(a);
1043
+ a.click();
1044
+ window.URL.revokeObjectURL(url);
1045
+ document.body.removeChild(a);
1046
+ return { success: true };
1047
+ },
1048
+ async purgePreview(olderThanDays) {
1049
+ return get(`/api/audit/purge/preview?older_than_days=${olderThanDays}`);
1050
+ },
1051
+ async purge(olderThanDays) {
1052
+ return del(`/api/audit/purge?older_than_days=${olderThanDays}`);
867
1053
  }
868
1054
  };
869
1055
  }
@@ -989,6 +1175,9 @@ var RealtimeService = class {
989
1175
  socket.onopen = () => {
990
1176
  console.log("BaaS Realtime: Connected");
991
1177
  this.reconnectAttempts = 0;
1178
+ for (const table of this.subscribers.keys()) {
1179
+ this.sendSub("subscribe", table);
1180
+ }
992
1181
  resolve();
993
1182
  };
994
1183
  socket.onmessage = (event) => {
@@ -1026,11 +1215,15 @@ var RealtimeService = class {
1026
1215
  */
1027
1216
  async subscribe(table, action, callback) {
1028
1217
  await this.connect();
1029
- if (!this.subscribers.has(table)) {
1218
+ const isNewTable = !this.subscribers.has(table);
1219
+ if (isNewTable) {
1030
1220
  this.subscribers.set(table, /* @__PURE__ */ new Set());
1031
1221
  }
1032
1222
  const sub = { action, callback };
1033
1223
  this.subscribers.get(table).add(sub);
1224
+ if (isNewTable && table !== "*") {
1225
+ this.sendSub("subscribe", table);
1226
+ }
1034
1227
  return {
1035
1228
  unsubscribe: () => {
1036
1229
  const tableSubs = this.subscribers.get(table);
@@ -1038,11 +1231,18 @@ var RealtimeService = class {
1038
1231
  tableSubs.delete(sub);
1039
1232
  if (tableSubs.size === 0) {
1040
1233
  this.subscribers.delete(table);
1234
+ if (table !== "*") this.sendSub("unsubscribe", table);
1041
1235
  }
1042
1236
  }
1043
1237
  }
1044
1238
  };
1045
1239
  }
1240
+ /** Send a subscribe/unsubscribe control frame to the server. */
1241
+ sendSub(event, table) {
1242
+ if (this.socket?.readyState === WebSocket.OPEN) {
1243
+ this.socket.send(JSON.stringify({ event, table }));
1244
+ }
1245
+ }
1046
1246
  /**
1047
1247
  * Handle incoming CDC events from the server
1048
1248
  */
@@ -1214,6 +1414,44 @@ function createIPWhitelistModule(client) {
1214
1414
  };
1215
1415
  }
1216
1416
 
1417
+ // src/modules/cache.ts
1418
+ function createCacheModule(client) {
1419
+ const get = (endpoint) => client.get(endpoint);
1420
+ const post = (endpoint, body) => client.post(endpoint, body);
1421
+ const patch = (endpoint, body) => client.patch(endpoint, body);
1422
+ const del = (endpoint) => client.delete(endpoint);
1423
+ return {
1424
+ async getStatus() {
1425
+ return get("/api/cache/status");
1426
+ },
1427
+ async getStats() {
1428
+ return get("/api/cache/stats");
1429
+ },
1430
+ async listPolicies() {
1431
+ return get("/api/cache/policies");
1432
+ },
1433
+ async updatePolicy(namespace, body) {
1434
+ return patch(`/api/cache/policies/${encodeURIComponent(namespace)}`, body);
1435
+ },
1436
+ async removePolicy(namespace) {
1437
+ return del(`/api/cache/policies/${encodeURIComponent(namespace)}`);
1438
+ },
1439
+ async invalidateNamespace(namespace) {
1440
+ return post(`/api/cache/invalidate/${encodeURIComponent(namespace)}`);
1441
+ },
1442
+ async invalidateAll() {
1443
+ return post("/api/cache/invalidate-all");
1444
+ },
1445
+ async listKeys(prefix, cursor = 0, count = 100) {
1446
+ const params = new URLSearchParams({ prefix, cursor: String(cursor), count: String(count) });
1447
+ return get(`/api/cache/keys?${params}`);
1448
+ },
1449
+ async inspectKey(key) {
1450
+ return get(`/api/cache/keys/inspect?key=${encodeURIComponent(key)}`);
1451
+ }
1452
+ };
1453
+ }
1454
+
1217
1455
  // src/client.ts
1218
1456
  var BaasClient = class extends HttpClient {
1219
1457
  // Feature modules
@@ -1240,8 +1478,9 @@ var BaasClient = class extends HttpClient {
1240
1478
  corsOrigins;
1241
1479
  policies;
1242
1480
  ipWhitelist;
1243
- constructor(url, apiKey) {
1244
- super(url, apiKey);
1481
+ cache;
1482
+ constructor(url, apiKey, options) {
1483
+ super(url, apiKey, options);
1245
1484
  this.auth = createAuthModule(this);
1246
1485
  this.users = createUsersModule(this);
1247
1486
  this.database = createDatabaseModule(this);
@@ -1265,6 +1504,7 @@ var BaasClient = class extends HttpClient {
1265
1504
  this.corsOrigins = createCorsOriginsModule(this);
1266
1505
  this.policies = createPoliciesModule(this);
1267
1506
  this.ipWhitelist = createIPWhitelistModule(this);
1507
+ this.cache = createCacheModule(this);
1268
1508
  }
1269
1509
  /**
1270
1510
  * Create a query builder for fluent data queries
@@ -1272,6 +1512,18 @@ var BaasClient = class extends HttpClient {
1272
1512
  from(table) {
1273
1513
  return new QueryBuilder(table, this.url, this);
1274
1514
  }
1515
+ /**
1516
+ * Invoke a PL/pgSQL function (PostgREST `.rpc()` parity). Runs under the
1517
+ * caller's RLS context; args bind by name and are $N-safe. Pass [params] for
1518
+ * POST (named args in the body) or set `opts.get = true` for the GET variant.
1519
+ */
1520
+ async rpc(fn, params, opts) {
1521
+ if (opts?.get) {
1522
+ const qs = params ? "?" + new URLSearchParams(Object.entries(params).map(([k, v]) => [k, String(v)])).toString() : "";
1523
+ return this.get(`/api/v1/rpc/${fn}${qs}`);
1524
+ }
1525
+ return this.post(`/api/v1/rpc/${fn}`, params ?? {});
1526
+ }
1275
1527
  // ============================================
1276
1528
  // BACKWARD COMPATIBILITY METHODS
1277
1529
  // These delegate to the appropriate modules
@@ -1639,6 +1891,15 @@ var BaasClient = class extends HttpClient {
1639
1891
  async getAuditActions() {
1640
1892
  return this.audit.getActions();
1641
1893
  }
1894
+ async exportAuditLogs(format, options) {
1895
+ return this.audit.exportLogs(format, options);
1896
+ }
1897
+ async previewPurgeAuditLogs(olderThanDays) {
1898
+ return this.audit.purgePreview(olderThanDays);
1899
+ }
1900
+ async purgeAuditLogs(olderThanDays) {
1901
+ return this.audit.purge(olderThanDays);
1902
+ }
1642
1903
  // Realtime shortcuts
1643
1904
  subscribe(table, action, callback) {
1644
1905
  return this.realtime.subscribe(table, action, callback);
@@ -1692,3 +1953,4 @@ export {
1692
1953
  HttpClient,
1693
1954
  QueryBuilder
1694
1955
  };
1956
+ //# sourceMappingURL=index.js.map