@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/CHANGELOG.md +26 -0
- package/LICENSE +21 -0
- package/README.md +54 -0
- package/dist/cli.js +416 -0
- package/dist/index.cjs +272 -10
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +150 -5
- package/dist/index.d.ts +150 -5
- package/dist/index.js +272 -10
- package/dist/index.js.map +1 -0
- package/package.json +43 -18
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
|
|
314
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1244
|
-
|
|
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
|