@afribase/afribase-js 0.2.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.js ADDED
@@ -0,0 +1,1979 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ AfribaseAuthClient: () => AfribaseAuthClient,
34
+ AfribaseClient: () => AfribaseClient,
35
+ AfribaseFunctionsClient: () => AfribaseFunctionsClient,
36
+ AfribaseQueryBuilder: () => AfribaseQueryBuilder,
37
+ AfribaseRealtimeClient: () => AfribaseRealtimeClient,
38
+ AfribaseRpcBuilder: () => AfribaseRpcBuilder,
39
+ AfribaseStorageClient: () => AfribaseStorageClient,
40
+ RealtimeChannel: () => RealtimeChannel,
41
+ StorageFileApi: () => StorageFileApi,
42
+ containedBy: () => containedBy,
43
+ contains: () => contains,
44
+ createClient: () => createClient,
45
+ csv: () => csv,
46
+ db: () => db,
47
+ del: () => del,
48
+ eq: () => eq,
49
+ explain: () => explain,
50
+ geojson: () => geojson,
51
+ gt: () => gt,
52
+ gte: () => gte,
53
+ ilike: () => ilike,
54
+ insert: () => insert,
55
+ is: () => is,
56
+ isIn: () => isIn,
57
+ like: () => like,
58
+ limit: () => limit,
59
+ lt: () => lt,
60
+ lte: () => lte,
61
+ match: () => match,
62
+ maybeSingle: () => maybeSingle,
63
+ neq: () => neq,
64
+ not: () => not,
65
+ or: () => or,
66
+ order: () => order,
67
+ range: () => range,
68
+ select: () => select,
69
+ single: () => single,
70
+ textSearch: () => textSearch,
71
+ update: () => update,
72
+ upsert: () => upsert
73
+ });
74
+ module.exports = __toCommonJS(index_exports);
75
+
76
+ // src/lib/fetch.ts
77
+ var import_cross_fetch = __toESM(require("cross-fetch"));
78
+ var _fetch = typeof globalThis !== "undefined" && globalThis.fetch ? globalThis.fetch : import_cross_fetch.default;
79
+ async function resolveFetch(customFetch) {
80
+ if (customFetch) return customFetch;
81
+ return _fetch;
82
+ }
83
+ async function fetchWithAuth(url, options) {
84
+ const fetchFn = await resolveFetch(options.customFetch);
85
+ const headers = {
86
+ "apikey": options.apiKey,
87
+ ...options.headers
88
+ };
89
+ if (options.accessToken) {
90
+ headers["Authorization"] = `Bearer ${options.accessToken}`;
91
+ } else {
92
+ headers["Authorization"] = `Bearer ${options.apiKey}`;
93
+ }
94
+ return fetchFn(url, {
95
+ method: options.method || "GET",
96
+ headers,
97
+ body: options.body
98
+ });
99
+ }
100
+ async function handleResponse(response) {
101
+ if (!response.ok) {
102
+ let errorBody;
103
+ try {
104
+ errorBody = await response.json();
105
+ } catch {
106
+ errorBody = { message: await response.text() };
107
+ }
108
+ return {
109
+ data: null,
110
+ error: {
111
+ message: errorBody.message || errorBody.msg || `Request failed with status ${response.status}`,
112
+ status: response.status,
113
+ ...errorBody
114
+ }
115
+ };
116
+ }
117
+ if (response.status === 204) {
118
+ return { data: null, error: null };
119
+ }
120
+ try {
121
+ const data = await response.json();
122
+ return { data, error: null };
123
+ } catch {
124
+ return { data: null, error: null };
125
+ }
126
+ }
127
+
128
+ // src/lib/AfribaseAuthClient.ts
129
+ var STORAGE_KEY = "afribase.auth.token";
130
+ var AfribaseAuthClient = class {
131
+ constructor(options) {
132
+ this.currentSession = null;
133
+ this.refreshTimer = null;
134
+ this.listeners = /* @__PURE__ */ new Map();
135
+ this.listenerIdCounter = 0;
136
+ this.url = options.url.replace(/\/$/, "");
137
+ this.apiKey = options.apiKey;
138
+ this.autoRefresh = options.autoRefreshToken ?? true;
139
+ this.persistSession = options.persistSession ?? true;
140
+ this.customFetch = options.customFetch;
141
+ }
142
+ // ─────────────────────────────────────────────────────────────────────
143
+ // Session Management
144
+ // ─────────────────────────────────────────────────────────────────────
145
+ /**
146
+ * Initialize the auth client — restores persisted session and emits INITIAL_SESSION.
147
+ * Call this once after creating the client.
148
+ */
149
+ async initialize() {
150
+ try {
151
+ const stored = this._getStoredSession();
152
+ if (stored) {
153
+ if (this._isSessionExpired(stored)) {
154
+ const { data, error } = await this._refreshSession(stored.refresh_token);
155
+ if (!error && data) {
156
+ this._setSession(data);
157
+ this._notifyListeners("INITIAL_SESSION", data);
158
+ return { data: { session: data }, error: null };
159
+ }
160
+ this._removeSession();
161
+ this._notifyListeners("INITIAL_SESSION", null);
162
+ return { data: { session: null }, error: null };
163
+ }
164
+ this._setSession(stored);
165
+ this._notifyListeners("INITIAL_SESSION", stored);
166
+ return { data: { session: stored }, error: null };
167
+ }
168
+ this._notifyListeners("INITIAL_SESSION", null);
169
+ return { data: { session: null }, error: null };
170
+ } catch (err) {
171
+ return { data: { session: null }, error: err };
172
+ }
173
+ }
174
+ /**
175
+ * Returns the current session if it exists.
176
+ */
177
+ async getSession() {
178
+ return { data: { session: this.currentSession }, error: null };
179
+ }
180
+ /**
181
+ * Returns the current user if authenticated.
182
+ */
183
+ async getUser() {
184
+ if (!this.currentSession?.access_token) {
185
+ return { data: { user: null }, error: { message: "Not authenticated" } };
186
+ }
187
+ const resp = await fetchWithAuth(`${this.url}/user`, {
188
+ apiKey: this.apiKey,
189
+ accessToken: this.currentSession.access_token,
190
+ customFetch: this.customFetch
191
+ });
192
+ const result = await handleResponse(resp);
193
+ if (result.data && this.currentSession) {
194
+ this.currentSession.user = result.data;
195
+ }
196
+ return { data: { user: result.data }, error: result.error };
197
+ }
198
+ // ─────────────────────────────────────────────────────────────────────
199
+ // Sign Up / Sign In
200
+ // ─────────────────────────────────────────────────────────────────────
201
+ /**
202
+ * Create a new user with email/phone and password.
203
+ */
204
+ async signUp(credentials) {
205
+ const body = { password: credentials.password };
206
+ if (credentials.email) body.email = credentials.email;
207
+ if (credentials.phone) body.phone = credentials.phone;
208
+ if (credentials.options?.data) body.data = credentials.options.data;
209
+ if (credentials.options?.captchaToken) body.gotcha = credentials.options.captchaToken;
210
+ const resp = await fetchWithAuth(`${this.url}/signup`, {
211
+ method: "POST",
212
+ headers: { "Content-Type": "application/json" },
213
+ body: JSON.stringify(body),
214
+ apiKey: this.apiKey,
215
+ customFetch: this.customFetch
216
+ });
217
+ const result = await handleResponse(resp);
218
+ if (result.error) return { data: { user: null, session: null }, error: result.error };
219
+ const data = result.data;
220
+ if (data.access_token) {
221
+ const session = this._makeSession(data);
222
+ this._setSession(session);
223
+ this._notifyListeners("SIGNED_IN", session);
224
+ return { data: { user: session.user, session }, error: null };
225
+ }
226
+ return { data: { user: data, session: null }, error: null };
227
+ }
228
+ /**
229
+ * Sign in with email/phone and password.
230
+ */
231
+ async signInWithPassword(credentials) {
232
+ const body = { password: credentials.password };
233
+ if (credentials.email) body.email = credentials.email;
234
+ if (credentials.phone) body.phone = credentials.phone;
235
+ if (credentials.options?.captchaToken) body.gotcha = credentials.options.captchaToken;
236
+ const resp = await fetchWithAuth(`${this.url}/token?grant_type=password`, {
237
+ method: "POST",
238
+ headers: { "Content-Type": "application/json" },
239
+ body: JSON.stringify(body),
240
+ apiKey: this.apiKey,
241
+ customFetch: this.customFetch
242
+ });
243
+ const result = await handleResponse(resp);
244
+ if (result.error) return { data: { user: null, session: null }, error: result.error };
245
+ const session = this._makeSession(result.data);
246
+ this._setSession(session);
247
+ this._notifyListeners("SIGNED_IN", session);
248
+ return { data: { user: session.user, session }, error: null };
249
+ }
250
+ /**
251
+ * Sign in with a third-party OAuth provider.
252
+ * Returns a URL to redirect the user to.
253
+ */
254
+ async signInWithOAuth(credentials) {
255
+ const params = new URLSearchParams();
256
+ if (credentials.options?.redirectTo) params.set("redirect_to", credentials.options.redirectTo);
257
+ if (credentials.options?.scopes) params.set("scopes", credentials.options.scopes);
258
+ if (credentials.options?.queryParams) {
259
+ for (const [k, v] of Object.entries(credentials.options.queryParams)) {
260
+ params.set(k, v);
261
+ }
262
+ }
263
+ const qs = params.toString();
264
+ const url = `${this.url}/authorize?provider=${credentials.provider}${qs ? "&" + qs : ""}`;
265
+ return { data: { provider: credentials.provider, url }, error: null };
266
+ }
267
+ /**
268
+ * Sign in with a one-time password (magic link or SMS OTP).
269
+ */
270
+ async signInWithOtp(credentials) {
271
+ const body = {};
272
+ if (credentials.email) body.email = credentials.email;
273
+ if (credentials.phone) body.phone = credentials.phone;
274
+ if (credentials.options?.data) body.data = credentials.options.data;
275
+ if (credentials.options?.captchaToken) body.gotcha = credentials.options.captchaToken;
276
+ if (credentials.options?.shouldCreateUser !== void 0) body.create_user = credentials.options.shouldCreateUser;
277
+ const resp = await fetchWithAuth(`${this.url}/otp`, {
278
+ method: "POST",
279
+ headers: { "Content-Type": "application/json" },
280
+ body: JSON.stringify(body),
281
+ apiKey: this.apiKey,
282
+ customFetch: this.customFetch
283
+ });
284
+ return handleResponse(resp);
285
+ }
286
+ /**
287
+ * Verify an OTP or magic link token.
288
+ */
289
+ async verifyOtp(params) {
290
+ const body = { token: params.token, type: params.type };
291
+ if (params.email) body.email = params.email;
292
+ if (params.phone) body.phone = params.phone;
293
+ const resp = await fetchWithAuth(`${this.url}/verify`, {
294
+ method: "POST",
295
+ headers: { "Content-Type": "application/json" },
296
+ body: JSON.stringify(body),
297
+ apiKey: this.apiKey,
298
+ customFetch: this.customFetch
299
+ });
300
+ const result = await handleResponse(resp);
301
+ if (result.error) return { data: { user: null, session: null }, error: result.error };
302
+ if (result.data?.access_token) {
303
+ const session = this._makeSession(result.data);
304
+ this._setSession(session);
305
+ this._notifyListeners("SIGNED_IN", session);
306
+ return { data: { user: session.user, session }, error: null };
307
+ }
308
+ return { data: result.data, error: null };
309
+ }
310
+ /**
311
+ * Sign in anonymously — creates an anonymous user.
312
+ */
313
+ async signInAnonymously(options) {
314
+ const body = {};
315
+ if (options?.data) body.data = options.data;
316
+ if (options?.captchaToken) body.gotcha = options.captchaToken;
317
+ const resp = await fetchWithAuth(`${this.url}/signup`, {
318
+ method: "POST",
319
+ headers: { "Content-Type": "application/json" },
320
+ body: JSON.stringify(body),
321
+ apiKey: this.apiKey,
322
+ customFetch: this.customFetch
323
+ });
324
+ const result = await handleResponse(resp);
325
+ if (result.error) return { data: { user: null, session: null }, error: result.error };
326
+ if (result.data?.access_token) {
327
+ const session = this._makeSession(result.data);
328
+ this._setSession(session);
329
+ this._notifyListeners("SIGNED_IN", session);
330
+ return { data: { user: session.user, session }, error: null };
331
+ }
332
+ return { data: result.data, error: null };
333
+ }
334
+ // ─────────────────────────────────────────────────────────────────────
335
+ // Password Recovery & User Updates
336
+ // ─────────────────────────────────────────────────────────────────────
337
+ /**
338
+ * Sends a password reset email.
339
+ */
340
+ async resetPasswordForEmail(email, options) {
341
+ const body = { email };
342
+ if (options?.redirectTo) body.redirect_to = options.redirectTo;
343
+ if (options?.captchaToken) body.gotcha = options.captchaToken;
344
+ const resp = await fetchWithAuth(`${this.url}/recover`, {
345
+ method: "POST",
346
+ headers: { "Content-Type": "application/json" },
347
+ body: JSON.stringify(body),
348
+ apiKey: this.apiKey,
349
+ customFetch: this.customFetch
350
+ });
351
+ return handleResponse(resp);
352
+ }
353
+ /**
354
+ * Update the current user (email, phone, password, metadata).
355
+ */
356
+ async updateUser(attributes) {
357
+ if (!this.currentSession?.access_token) {
358
+ return { data: { user: null }, error: { message: "Not authenticated" } };
359
+ }
360
+ const resp = await fetchWithAuth(`${this.url}/user`, {
361
+ method: "PUT",
362
+ headers: { "Content-Type": "application/json" },
363
+ body: JSON.stringify(attributes),
364
+ apiKey: this.apiKey,
365
+ accessToken: this.currentSession.access_token,
366
+ customFetch: this.customFetch
367
+ });
368
+ const result = await handleResponse(resp);
369
+ if (!result.error && result.data && this.currentSession) {
370
+ this.currentSession.user = result.data;
371
+ this._persistSession(this.currentSession);
372
+ this._notifyListeners("USER_UPDATED", this.currentSession);
373
+ }
374
+ return { data: { user: result.data }, error: result.error };
375
+ }
376
+ // ─────────────────────────────────────────────────────────────────────
377
+ // Sign Out
378
+ // ─────────────────────────────────────────────────────────────────────
379
+ /**
380
+ * Sign out the current user.
381
+ */
382
+ async signOut() {
383
+ if (this.currentSession?.access_token) {
384
+ try {
385
+ await fetchWithAuth(`${this.url}/logout`, {
386
+ method: "POST",
387
+ apiKey: this.apiKey,
388
+ accessToken: this.currentSession.access_token,
389
+ customFetch: this.customFetch
390
+ });
391
+ } catch {
392
+ }
393
+ }
394
+ this._removeSession();
395
+ this._notifyListeners("SIGNED_OUT", null);
396
+ return { error: null };
397
+ }
398
+ // ─────────────────────────────────────────────────────────────────────
399
+ // Auth State Change
400
+ // ─────────────────────────────────────────────────────────────────────
401
+ /**
402
+ * Listen for auth state changes.
403
+ * Returns a subscription object with an unsubscribe method.
404
+ */
405
+ onAuthStateChange(callback) {
406
+ const id = `auth-listener-${++this.listenerIdCounter}`;
407
+ this.listeners.set(id, callback);
408
+ setTimeout(() => {
409
+ callback("INITIAL_SESSION", this.currentSession);
410
+ }, 0);
411
+ return {
412
+ data: {
413
+ subscription: {
414
+ id,
415
+ unsubscribe: () => {
416
+ this.listeners.delete(id);
417
+ }
418
+ }
419
+ }
420
+ };
421
+ }
422
+ // ─────────────────────────────────────────────────────────────────────
423
+ // Token Refresh
424
+ // ─────────────────────────────────────────────────────────────────────
425
+ /**
426
+ * Manually refresh the session token.
427
+ */
428
+ async refreshSession() {
429
+ if (!this.currentSession?.refresh_token) {
430
+ return { data: { session: null }, error: { message: "No refresh token" } };
431
+ }
432
+ const { data, error } = await this._refreshSession(this.currentSession.refresh_token);
433
+ if (error) return { data: { session: null }, error };
434
+ if (data) {
435
+ this._setSession(data);
436
+ this._notifyListeners("TOKEN_REFRESHED", data);
437
+ }
438
+ return { data: { session: data }, error: null };
439
+ }
440
+ /**
441
+ * Set the session manually (useful for server-side auth).
442
+ */
443
+ async setSession(params) {
444
+ const resp = await fetchWithAuth(`${this.url}/user`, {
445
+ apiKey: this.apiKey,
446
+ accessToken: params.access_token,
447
+ customFetch: this.customFetch
448
+ });
449
+ const result = await handleResponse(resp);
450
+ if (result.error) return { data: { session: null }, error: result.error };
451
+ const session = {
452
+ access_token: params.access_token,
453
+ refresh_token: params.refresh_token,
454
+ expires_in: 3600,
455
+ expires_at: Math.floor(Date.now() / 1e3) + 3600,
456
+ token_type: "bearer",
457
+ user: result.data
458
+ };
459
+ this._setSession(session);
460
+ this._notifyListeners("SIGNED_IN", session);
461
+ return { data: { session }, error: null };
462
+ }
463
+ // ─────────────────────────────────────────────────────────────────────
464
+ // Helpers — Access Token for other clients
465
+ // ─────────────────────────────────────────────────────────────────────
466
+ /** @internal */
467
+ get accessToken() {
468
+ return this.currentSession?.access_token ?? null;
469
+ }
470
+ // ─────────────────────────────────────────────────────────────────────
471
+ // Private Helpers
472
+ // ─────────────────────────────────────────────────────────────────────
473
+ _makeSession(data) {
474
+ const expiresAt = data.expires_at ?? Math.floor(Date.now() / 1e3) + (data.expires_in || 3600);
475
+ return {
476
+ access_token: data.access_token,
477
+ refresh_token: data.refresh_token,
478
+ expires_in: data.expires_in || 3600,
479
+ expires_at: expiresAt,
480
+ token_type: data.token_type || "bearer",
481
+ user: data.user || data
482
+ };
483
+ }
484
+ _setSession(session) {
485
+ this.currentSession = session;
486
+ this._persistSession(session);
487
+ this._scheduleRefresh(session);
488
+ }
489
+ _removeSession() {
490
+ this.currentSession = null;
491
+ if (this.refreshTimer) {
492
+ clearTimeout(this.refreshTimer);
493
+ this.refreshTimer = null;
494
+ }
495
+ this._clearStoredSession();
496
+ }
497
+ async _refreshSession(refreshToken) {
498
+ const resp = await fetchWithAuth(`${this.url}/token?grant_type=refresh_token`, {
499
+ method: "POST",
500
+ headers: { "Content-Type": "application/json" },
501
+ body: JSON.stringify({ refresh_token: refreshToken }),
502
+ apiKey: this.apiKey,
503
+ customFetch: this.customFetch
504
+ });
505
+ const result = await handleResponse(resp);
506
+ if (result.error) return { data: null, error: result.error };
507
+ return { data: this._makeSession(result.data), error: null };
508
+ }
509
+ _scheduleRefresh(session) {
510
+ if (!this.autoRefresh) return;
511
+ if (this.refreshTimer) clearTimeout(this.refreshTimer);
512
+ const expiresAt = session.expires_at ?? Math.floor(Date.now() / 1e3) + session.expires_in;
513
+ const now = Math.floor(Date.now() / 1e3);
514
+ const refreshIn = Math.max((expiresAt - now - 60) * 1e3, 1e3);
515
+ this.refreshTimer = setTimeout(async () => {
516
+ if (this.currentSession?.refresh_token) {
517
+ const { data, error } = await this._refreshSession(this.currentSession.refresh_token);
518
+ if (!error && data) {
519
+ this._setSession(data);
520
+ this._notifyListeners("TOKEN_REFRESHED", data);
521
+ } else {
522
+ this._removeSession();
523
+ this._notifyListeners("SIGNED_OUT", null);
524
+ }
525
+ }
526
+ }, refreshIn);
527
+ }
528
+ _isSessionExpired(session) {
529
+ if (!session.expires_at) return false;
530
+ return Math.floor(Date.now() / 1e3) >= session.expires_at;
531
+ }
532
+ _notifyListeners(event, session) {
533
+ for (const cb of this.listeners.values()) {
534
+ try {
535
+ cb(event, session);
536
+ } catch {
537
+ }
538
+ }
539
+ }
540
+ // Storage helpers (localStorage in browser, no-op in Node)
541
+ _persistSession(session) {
542
+ if (!this.persistSession) return;
543
+ try {
544
+ if (typeof globalThis !== "undefined" && globalThis.localStorage) {
545
+ globalThis.localStorage.setItem(STORAGE_KEY, JSON.stringify(session));
546
+ }
547
+ } catch {
548
+ }
549
+ }
550
+ _getStoredSession() {
551
+ if (!this.persistSession) return null;
552
+ try {
553
+ if (typeof globalThis !== "undefined" && globalThis.localStorage) {
554
+ const raw = globalThis.localStorage.getItem(STORAGE_KEY);
555
+ return raw ? JSON.parse(raw) : null;
556
+ }
557
+ } catch {
558
+ }
559
+ return null;
560
+ }
561
+ _clearStoredSession() {
562
+ try {
563
+ if (typeof globalThis !== "undefined" && globalThis.localStorage) {
564
+ globalThis.localStorage.removeItem(STORAGE_KEY);
565
+ }
566
+ } catch {
567
+ }
568
+ }
569
+ };
570
+
571
+ // src/lib/AfribaseRealtimeClient.ts
572
+ function getWebSocketImpl() {
573
+ if (typeof WebSocket !== "undefined") return WebSocket;
574
+ try {
575
+ const ws = globalThis.require?.("ws") ?? new Function('return require("ws")')();
576
+ return ws;
577
+ } catch {
578
+ throw new Error(
579
+ 'WebSocket is not available. In Node.js, install the "ws" package: npm install ws'
580
+ );
581
+ }
582
+ }
583
+ var WS_OPEN = 1;
584
+ var RealtimeChannel = class {
585
+ constructor(topic, params, sendMessage, removeChannel) {
586
+ this.params = params;
587
+ this.sendMessage = sendMessage;
588
+ this.removeChannel = removeChannel;
589
+ this.bindings = [];
590
+ this.presenceState = {};
591
+ this._status = "CLOSED";
592
+ this.topic = topic;
593
+ this.presenceKey = params.config?.presence?.key || "";
594
+ }
595
+ on(type, filter, callback) {
596
+ this.bindings.push({ type, filter, callback });
597
+ return this;
598
+ }
599
+ /**
600
+ * Subscribe to the channel — starts receiving events.
601
+ */
602
+ subscribe(callback) {
603
+ const joinConfig = { topic: this.topic };
604
+ const pgBindings = this.bindings.filter((b) => b.type === "postgres_changes");
605
+ if (pgBindings.length > 0) {
606
+ joinConfig.postgres_changes = pgBindings.map((b) => ({
607
+ event: b.filter?.event || "*",
608
+ schema: b.filter?.schema || "public",
609
+ table: b.filter?.table || "*",
610
+ filter: b.filter?.filter
611
+ }));
612
+ }
613
+ if (this.params.config?.broadcast) {
614
+ joinConfig.broadcast = this.params.config.broadcast;
615
+ }
616
+ if (this.params.config?.presence) {
617
+ joinConfig.presence = this.params.config.presence;
618
+ }
619
+ this.sendMessage({
620
+ topic: this.topic,
621
+ event: "phx_join",
622
+ payload: joinConfig,
623
+ ref: null
624
+ });
625
+ this._status = "SUBSCRIBED";
626
+ if (callback) {
627
+ setTimeout(() => callback("SUBSCRIBED"), 0);
628
+ }
629
+ return this;
630
+ }
631
+ /**
632
+ * Send a broadcast message to all subscribers.
633
+ */
634
+ send(payload) {
635
+ this.sendMessage({
636
+ topic: this.topic,
637
+ event: payload.event,
638
+ payload: payload.payload,
639
+ ref: null
640
+ });
641
+ return this;
642
+ }
643
+ /**
644
+ * Track presence for the current user.
645
+ */
646
+ track(payload) {
647
+ this.sendMessage({
648
+ topic: this.topic,
649
+ event: "presence",
650
+ payload: { type: "track", ...payload, key: this.presenceKey },
651
+ ref: null
652
+ });
653
+ return this;
654
+ }
655
+ /**
656
+ * Untrack presence for the current user.
657
+ */
658
+ untrack() {
659
+ this.sendMessage({
660
+ topic: this.topic,
661
+ event: "presence",
662
+ payload: { type: "untrack", key: this.presenceKey },
663
+ ref: null
664
+ });
665
+ return this;
666
+ }
667
+ /**
668
+ * Get current presence state.
669
+ */
670
+ presenceState_() {
671
+ return { ...this.presenceState };
672
+ }
673
+ /**
674
+ * Unsubscribe from this channel.
675
+ */
676
+ unsubscribe() {
677
+ this.sendMessage({
678
+ topic: this.topic,
679
+ event: "phx_leave",
680
+ payload: {},
681
+ ref: null
682
+ });
683
+ this._status = "CLOSED";
684
+ this.removeChannel(this.topic);
685
+ }
686
+ get status() {
687
+ return this._status;
688
+ }
689
+ /** @internal — called by the RealtimeClient when a message arrives */
690
+ _handleMessage(msg) {
691
+ if (msg.payload?.type === "postgres_changes") {
692
+ const pgPayload = msg.payload;
693
+ for (const binding of this.bindings) {
694
+ if (binding.type !== "postgres_changes") continue;
695
+ const f = binding.filter;
696
+ if (f?.schema && f.schema !== pgPayload.schema) continue;
697
+ if (f?.table && f.table !== pgPayload.table) continue;
698
+ if (f?.event && f.event !== "*" && f.event !== pgPayload.eventType) continue;
699
+ binding.callback(pgPayload);
700
+ }
701
+ return;
702
+ }
703
+ if (msg.payload?.type === "presence_state" || msg.payload?.type === "presence_diff") {
704
+ if (msg.payload.type === "presence_state") {
705
+ this.presenceState = msg.payload.state || {};
706
+ } else if (msg.payload.type === "presence_diff") {
707
+ const { joins, leaves } = msg.payload;
708
+ if (joins) {
709
+ for (const [key, val] of Object.entries(joins)) {
710
+ this.presenceState[key] = val.metas;
711
+ }
712
+ }
713
+ if (leaves) {
714
+ for (const key of Object.keys(leaves)) {
715
+ delete this.presenceState[key];
716
+ }
717
+ }
718
+ }
719
+ for (const binding of this.bindings) {
720
+ if (binding.type !== "presence") continue;
721
+ if (binding.filter?.event === "sync" || binding.filter?.event === msg.payload.type) {
722
+ binding.callback({
723
+ type: msg.payload.type,
724
+ presenceState: this.presenceState,
725
+ joins: msg.payload.joins,
726
+ leaves: msg.payload.leaves
727
+ });
728
+ }
729
+ }
730
+ return;
731
+ }
732
+ for (const binding of this.bindings) {
733
+ if (binding.type !== "broadcast") continue;
734
+ if (binding.filter?.event === msg.event || binding.filter?.event === "*") {
735
+ binding.callback({ type: "broadcast", event: msg.event, payload: msg.payload });
736
+ }
737
+ }
738
+ }
739
+ };
740
+ var AfribaseRealtimeClient = class {
741
+ constructor(options) {
742
+ this.ws = null;
743
+ this.channels = /* @__PURE__ */ new Map();
744
+ this.heartbeatTimer = null;
745
+ this.reconnectTimer = null;
746
+ this.reconnectAttempts = 0;
747
+ this.maxReconnectAttempts = 10;
748
+ this.reconnectBaseDelay = 1e3;
749
+ this.connected = false;
750
+ this.url = options.url.replace(/^http/, "ws").replace(/\/$/, "") + "/realtime/v1/websocket";
751
+ this.apiKey = options.apiKey;
752
+ this.accessToken = options.accessToken ?? null;
753
+ }
754
+ /**
755
+ * Set the access token (called by AfribaseClient when auth state changes).
756
+ */
757
+ setAuth(token) {
758
+ this.accessToken = token;
759
+ if (this.ws && this.ws.readyState === WS_OPEN) {
760
+ this._send({
761
+ topic: "phoenix",
762
+ event: "access_token",
763
+ payload: { access_token: token },
764
+ ref: null
765
+ });
766
+ }
767
+ }
768
+ /**
769
+ * Create a channel for a given topic.
770
+ */
771
+ channel(topic, options = {}) {
772
+ const normalizedTopic = topic.startsWith("realtime:") ? topic : `realtime:${topic}`;
773
+ if (this.channels.has(normalizedTopic)) {
774
+ return this.channels.get(normalizedTopic);
775
+ }
776
+ const ch = new RealtimeChannel(
777
+ normalizedTopic,
778
+ options,
779
+ (msg) => this._send(msg),
780
+ (t) => this.channels.delete(t)
781
+ );
782
+ this.channels.set(normalizedTopic, ch);
783
+ if (!this.ws) {
784
+ this._connect();
785
+ }
786
+ return ch;
787
+ }
788
+ /**
789
+ * Remove all channels and disconnect.
790
+ */
791
+ removeAllChannels() {
792
+ for (const ch of this.channels.values()) {
793
+ ch.unsubscribe();
794
+ }
795
+ this.channels.clear();
796
+ this._disconnect();
797
+ }
798
+ /**
799
+ * Disconnect the WebSocket.
800
+ */
801
+ disconnect() {
802
+ this._disconnect();
803
+ }
804
+ // ─────────────────────────────────────────────────────────────────────
805
+ // Private
806
+ // ─────────────────────────────────────────────────────────────────────
807
+ _connect() {
808
+ if (this.ws) return;
809
+ try {
810
+ const WSImpl = getWebSocketImpl();
811
+ const params = new URLSearchParams({
812
+ apikey: this.apiKey,
813
+ vsn: "1.0.0"
814
+ });
815
+ if (this.accessToken) {
816
+ params.set("token", this.accessToken);
817
+ }
818
+ this.ws = new WSImpl(`${this.url}?${params.toString()}`);
819
+ } catch (err) {
820
+ this._scheduleReconnect();
821
+ return;
822
+ }
823
+ this.ws.onopen = () => {
824
+ this.connected = true;
825
+ this.reconnectAttempts = 0;
826
+ this._startHeartbeat();
827
+ for (const ch of this.channels.values()) {
828
+ if (ch.status === "CLOSED") {
829
+ ch.subscribe();
830
+ }
831
+ }
832
+ };
833
+ this.ws.onmessage = (ev) => {
834
+ try {
835
+ const msg = JSON.parse(typeof ev.data === "string" ? ev.data : ev.data.toString());
836
+ this._handleMessage(msg);
837
+ } catch {
838
+ }
839
+ };
840
+ this.ws.onclose = () => {
841
+ this.connected = false;
842
+ this._stopHeartbeat();
843
+ this.ws = null;
844
+ if (this.channels.size > 0) {
845
+ this._scheduleReconnect();
846
+ }
847
+ };
848
+ this.ws.onerror = () => {
849
+ };
850
+ }
851
+ _disconnect() {
852
+ if (this.reconnectTimer) {
853
+ clearTimeout(this.reconnectTimer);
854
+ this.reconnectTimer = null;
855
+ }
856
+ this._stopHeartbeat();
857
+ if (this.ws) {
858
+ this.ws.onclose = null;
859
+ this.ws.close();
860
+ this.ws = null;
861
+ }
862
+ this.connected = false;
863
+ }
864
+ _send(msg) {
865
+ if (this.ws && this.ws.readyState === WS_OPEN) {
866
+ this.ws.send(JSON.stringify(msg));
867
+ }
868
+ }
869
+ _handleMessage(msg) {
870
+ if (msg.topic === "phoenix" && msg.event === "phx_reply") return;
871
+ const ch = this.channels.get(msg.topic);
872
+ if (ch) {
873
+ ch._handleMessage(msg);
874
+ }
875
+ }
876
+ _startHeartbeat() {
877
+ this._stopHeartbeat();
878
+ this.heartbeatTimer = setInterval(() => {
879
+ this._send({ topic: "phoenix", event: "heartbeat", payload: {}, ref: null });
880
+ }, 3e4);
881
+ }
882
+ _stopHeartbeat() {
883
+ if (this.heartbeatTimer) {
884
+ clearInterval(this.heartbeatTimer);
885
+ this.heartbeatTimer = null;
886
+ }
887
+ }
888
+ _scheduleReconnect() {
889
+ if (this.reconnectTimer) return;
890
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) return;
891
+ const delay = Math.min(
892
+ this.reconnectBaseDelay * Math.pow(2, this.reconnectAttempts),
893
+ 3e4
894
+ );
895
+ this.reconnectAttempts++;
896
+ this.reconnectTimer = setTimeout(() => {
897
+ this.reconnectTimer = null;
898
+ this._connect();
899
+ }, delay);
900
+ }
901
+ };
902
+
903
+ // src/lib/AfribaseStorageClient.ts
904
+ var AfribaseStorageClient = class {
905
+ constructor(options) {
906
+ this.url = options.url.replace(/\/$/, "");
907
+ this.apiKey = options.apiKey;
908
+ this.getAccessToken = options.getAccessToken;
909
+ this.customFetch = options.customFetch;
910
+ }
911
+ /**
912
+ * Get a reference to a specific bucket for file operations.
913
+ */
914
+ from(bucketId) {
915
+ return new StorageFileApi(
916
+ this.url,
917
+ bucketId,
918
+ this.apiKey,
919
+ this.getAccessToken,
920
+ this.customFetch
921
+ );
922
+ }
923
+ // ─────────────────────────────────────────────────────────────────────
924
+ // Bucket Management
925
+ // ─────────────────────────────────────────────────────────────────────
926
+ /**
927
+ * List all buckets.
928
+ */
929
+ async listBuckets() {
930
+ const resp = await fetchWithAuth(`${this.url}/bucket`, {
931
+ apiKey: this.apiKey,
932
+ accessToken: this.getAccessToken(),
933
+ customFetch: this.customFetch
934
+ });
935
+ return handleResponse(resp);
936
+ }
937
+ /**
938
+ * Get a bucket by ID.
939
+ */
940
+ async getBucket(id) {
941
+ const resp = await fetchWithAuth(`${this.url}/bucket/${id}`, {
942
+ apiKey: this.apiKey,
943
+ accessToken: this.getAccessToken(),
944
+ customFetch: this.customFetch
945
+ });
946
+ return handleResponse(resp);
947
+ }
948
+ /**
949
+ * Create a new bucket.
950
+ */
951
+ async createBucket(id, options) {
952
+ const resp = await fetchWithAuth(`${this.url}/bucket`, {
953
+ method: "POST",
954
+ headers: { "Content-Type": "application/json" },
955
+ body: JSON.stringify({
956
+ id,
957
+ name: id,
958
+ public: options?.public ?? false,
959
+ file_size_limit: options?.fileSizeLimit,
960
+ allowed_mime_types: options?.allowedMimeTypes
961
+ }),
962
+ apiKey: this.apiKey,
963
+ accessToken: this.getAccessToken(),
964
+ customFetch: this.customFetch
965
+ });
966
+ return handleResponse(resp);
967
+ }
968
+ /**
969
+ * Update a bucket.
970
+ */
971
+ async updateBucket(id, options) {
972
+ const resp = await fetchWithAuth(`${this.url}/bucket/${id}`, {
973
+ method: "PUT",
974
+ headers: { "Content-Type": "application/json" },
975
+ body: JSON.stringify({
976
+ public: options.public,
977
+ file_size_limit: options.fileSizeLimit,
978
+ allowed_mime_types: options.allowedMimeTypes
979
+ }),
980
+ apiKey: this.apiKey,
981
+ accessToken: this.getAccessToken(),
982
+ customFetch: this.customFetch
983
+ });
984
+ return handleResponse(resp);
985
+ }
986
+ /**
987
+ * Delete a bucket (must be empty).
988
+ */
989
+ async deleteBucket(id) {
990
+ const resp = await fetchWithAuth(`${this.url}/bucket/${id}`, {
991
+ method: "DELETE",
992
+ apiKey: this.apiKey,
993
+ accessToken: this.getAccessToken(),
994
+ customFetch: this.customFetch
995
+ });
996
+ return handleResponse(resp);
997
+ }
998
+ /**
999
+ * Empty a bucket (remove all files).
1000
+ */
1001
+ async emptyBucket(id) {
1002
+ const resp = await fetchWithAuth(`${this.url}/bucket/${id}/empty`, {
1003
+ method: "POST",
1004
+ apiKey: this.apiKey,
1005
+ accessToken: this.getAccessToken(),
1006
+ customFetch: this.customFetch
1007
+ });
1008
+ return handleResponse(resp);
1009
+ }
1010
+ };
1011
+ var StorageFileApi = class {
1012
+ constructor(storageUrl, bucketId, apiKey, getAccessToken, customFetch) {
1013
+ this.storageUrl = storageUrl;
1014
+ this.bucketId = bucketId;
1015
+ this.apiKey = apiKey;
1016
+ this.getAccessToken = getAccessToken;
1017
+ this.customFetch = customFetch;
1018
+ }
1019
+ /**
1020
+ * Upload a file to the bucket.
1021
+ */
1022
+ async upload(path, fileBody, options) {
1023
+ const contentType = options?.contentType || "application/octet-stream";
1024
+ const headers = {
1025
+ "Content-Type": contentType
1026
+ };
1027
+ if (options?.cacheControl) headers["Cache-Control"] = options.cacheControl;
1028
+ if (options?.upsert) headers["x-upsert"] = "true";
1029
+ const resp = await fetchWithAuth(
1030
+ `${this.storageUrl}/object/${this.bucketId}/${path}`,
1031
+ {
1032
+ method: "POST",
1033
+ headers,
1034
+ body: fileBody,
1035
+ apiKey: this.apiKey,
1036
+ accessToken: this.getAccessToken(),
1037
+ customFetch: this.customFetch
1038
+ }
1039
+ );
1040
+ const result = await handleResponse(resp);
1041
+ if (result.error) return { data: null, error: result.error };
1042
+ return {
1043
+ data: {
1044
+ path,
1045
+ id: result.data?.id ?? result.data?.Key,
1046
+ fullPath: `${this.bucketId}/${path}`
1047
+ },
1048
+ error: null
1049
+ };
1050
+ }
1051
+ /**
1052
+ * Update (replace) an existing file.
1053
+ */
1054
+ async update(path, fileBody, options) {
1055
+ const contentType = options?.contentType || "application/octet-stream";
1056
+ const headers = {
1057
+ "Content-Type": contentType,
1058
+ "x-upsert": "true"
1059
+ };
1060
+ if (options?.cacheControl) headers["Cache-Control"] = options.cacheControl;
1061
+ const resp = await fetchWithAuth(
1062
+ `${this.storageUrl}/object/${this.bucketId}/${path}`,
1063
+ {
1064
+ method: "POST",
1065
+ headers,
1066
+ body: fileBody,
1067
+ apiKey: this.apiKey,
1068
+ accessToken: this.getAccessToken(),
1069
+ customFetch: this.customFetch
1070
+ }
1071
+ );
1072
+ const result = await handleResponse(resp);
1073
+ if (result.error) return { data: null, error: result.error };
1074
+ return {
1075
+ data: {
1076
+ path,
1077
+ id: result.data?.id ?? result.data?.Key,
1078
+ fullPath: `${this.bucketId}/${path}`
1079
+ },
1080
+ error: null
1081
+ };
1082
+ }
1083
+ /**
1084
+ * Download a file from the bucket.
1085
+ */
1086
+ async download(path, options) {
1087
+ const params = new URLSearchParams();
1088
+ if (options?.transform) {
1089
+ if (options.transform.width) params.set("width", String(options.transform.width));
1090
+ if (options.transform.height) params.set("height", String(options.transform.height));
1091
+ if (options.transform.resize) params.set("resize", options.transform.resize);
1092
+ if (options.transform.quality) params.set("quality", String(options.transform.quality));
1093
+ if (options.transform.format) params.set("format", options.transform.format);
1094
+ }
1095
+ const qs = params.toString();
1096
+ const url = `${this.storageUrl}/object/${this.bucketId}/${path}${qs ? "?" + qs : ""}`;
1097
+ const fetchFn = this.customFetch || (globalThis.fetch ?? (await import("cross-fetch")).default);
1098
+ const headers = {
1099
+ "apikey": this.apiKey
1100
+ };
1101
+ const token = this.getAccessToken();
1102
+ if (token) headers["Authorization"] = `Bearer ${token}`;
1103
+ else headers["Authorization"] = `Bearer ${this.apiKey}`;
1104
+ const resp = await fetchFn(url, { headers });
1105
+ if (!resp.ok) {
1106
+ let errorBody;
1107
+ try {
1108
+ errorBody = await resp.json();
1109
+ } catch {
1110
+ errorBody = { message: `Download failed with status ${resp.status}` };
1111
+ }
1112
+ return { data: null, error: errorBody };
1113
+ }
1114
+ const blob = await resp.blob();
1115
+ return { data: blob, error: null };
1116
+ }
1117
+ /**
1118
+ * List objects in the bucket.
1119
+ */
1120
+ async list(path, options) {
1121
+ const body = {
1122
+ prefix: path || ""
1123
+ };
1124
+ if (options?.limit) body.limit = options.limit;
1125
+ if (options?.offset) body.offset = options.offset;
1126
+ if (options?.sortBy) body.sortBy = options.sortBy;
1127
+ const resp = await fetchWithAuth(`${this.storageUrl}/object/list/${this.bucketId}`, {
1128
+ method: "POST",
1129
+ headers: { "Content-Type": "application/json" },
1130
+ body: JSON.stringify(body),
1131
+ apiKey: this.apiKey,
1132
+ accessToken: this.getAccessToken(),
1133
+ customFetch: this.customFetch
1134
+ });
1135
+ return handleResponse(resp);
1136
+ }
1137
+ /**
1138
+ * Move a file to a new path within the same bucket.
1139
+ */
1140
+ async move(fromPath, toPath) {
1141
+ const resp = await fetchWithAuth(`${this.storageUrl}/object/move`, {
1142
+ method: "POST",
1143
+ headers: { "Content-Type": "application/json" },
1144
+ body: JSON.stringify({
1145
+ bucketId: this.bucketId,
1146
+ sourceKey: fromPath,
1147
+ destinationKey: toPath
1148
+ }),
1149
+ apiKey: this.apiKey,
1150
+ accessToken: this.getAccessToken(),
1151
+ customFetch: this.customFetch
1152
+ });
1153
+ return handleResponse(resp);
1154
+ }
1155
+ /**
1156
+ * Copy a file to a new path within the same bucket.
1157
+ */
1158
+ async copy(fromPath, toPath) {
1159
+ const resp = await fetchWithAuth(`${this.storageUrl}/object/copy`, {
1160
+ method: "POST",
1161
+ headers: { "Content-Type": "application/json" },
1162
+ body: JSON.stringify({
1163
+ bucketId: this.bucketId,
1164
+ sourceKey: fromPath,
1165
+ destinationKey: toPath
1166
+ }),
1167
+ apiKey: this.apiKey,
1168
+ accessToken: this.getAccessToken(),
1169
+ customFetch: this.customFetch
1170
+ });
1171
+ return handleResponse(resp);
1172
+ }
1173
+ /**
1174
+ * Remove one or more files from the bucket.
1175
+ */
1176
+ async remove(paths) {
1177
+ const resp = await fetchWithAuth(`${this.storageUrl}/object/${this.bucketId}`, {
1178
+ method: "DELETE",
1179
+ headers: { "Content-Type": "application/json" },
1180
+ body: JSON.stringify({ prefixes: paths }),
1181
+ apiKey: this.apiKey,
1182
+ accessToken: this.getAccessToken(),
1183
+ customFetch: this.customFetch
1184
+ });
1185
+ return handleResponse(resp);
1186
+ }
1187
+ /**
1188
+ * Create a signed URL for temporary access to a private file.
1189
+ */
1190
+ async createSignedUrl(path, expiresIn, options) {
1191
+ const body = {
1192
+ expiresIn
1193
+ };
1194
+ if (options?.download) {
1195
+ body.download = typeof options.download === "string" ? options.download : true;
1196
+ }
1197
+ if (options?.transform) body.transform = options.transform;
1198
+ const resp = await fetchWithAuth(`${this.storageUrl}/object/sign/${this.bucketId}/${path}`, {
1199
+ method: "POST",
1200
+ headers: { "Content-Type": "application/json" },
1201
+ body: JSON.stringify(body),
1202
+ apiKey: this.apiKey,
1203
+ accessToken: this.getAccessToken(),
1204
+ customFetch: this.customFetch
1205
+ });
1206
+ const result = await handleResponse(resp);
1207
+ if (result.error) return { data: null, error: result.error };
1208
+ const signedUrl = result.data?.signedURL || result.data?.signedUrl;
1209
+ return {
1210
+ data: {
1211
+ signedUrl: signedUrl?.startsWith("http") ? signedUrl : `${this.storageUrl}${signedUrl}`,
1212
+ path,
1213
+ token: result.data?.token || ""
1214
+ },
1215
+ error: null
1216
+ };
1217
+ }
1218
+ /**
1219
+ * Create signed URLs for multiple files.
1220
+ */
1221
+ async createSignedUrls(paths, expiresIn) {
1222
+ const resp = await fetchWithAuth(`${this.storageUrl}/object/sign/${this.bucketId}`, {
1223
+ method: "POST",
1224
+ headers: { "Content-Type": "application/json" },
1225
+ body: JSON.stringify({ expiresIn, paths }),
1226
+ apiKey: this.apiKey,
1227
+ accessToken: this.getAccessToken(),
1228
+ customFetch: this.customFetch
1229
+ });
1230
+ const result = await handleResponse(resp);
1231
+ if (result.error) return { data: null, error: result.error };
1232
+ const urls = (result.data || []).map((item) => ({
1233
+ signedUrl: item.signedURL?.startsWith("http") ? item.signedURL : `${this.storageUrl}${item.signedURL}`,
1234
+ path: item.path,
1235
+ token: item.token || ""
1236
+ }));
1237
+ return { data: urls, error: null };
1238
+ }
1239
+ /**
1240
+ * Get the public URL for a file in a public bucket.
1241
+ */
1242
+ getPublicUrl(path, options) {
1243
+ const params = new URLSearchParams();
1244
+ if (options?.download) {
1245
+ params.set("download", typeof options.download === "string" ? options.download : "");
1246
+ }
1247
+ if (options?.transform) {
1248
+ if (options.transform.width) params.set("width", String(options.transform.width));
1249
+ if (options.transform.height) params.set("height", String(options.transform.height));
1250
+ if (options.transform.resize) params.set("resize", options.transform.resize);
1251
+ if (options.transform.quality) params.set("quality", String(options.transform.quality));
1252
+ if (options.transform.format) params.set("format", options.transform.format);
1253
+ }
1254
+ const qs = params.toString();
1255
+ const publicUrl = `${this.storageUrl}/object/public/${this.bucketId}/${path}${qs ? "?" + qs : ""}`;
1256
+ return { data: { publicUrl } };
1257
+ }
1258
+ };
1259
+
1260
+ // src/lib/AfribaseFunctionsClient.ts
1261
+ var AfribaseFunctionsClient = class {
1262
+ constructor(options) {
1263
+ this.url = options.url.replace(/\/$/, "");
1264
+ this.apiKey = options.apiKey;
1265
+ this.getAccessToken = options.getAccessToken;
1266
+ this.customFetch = options.customFetch;
1267
+ }
1268
+ /**
1269
+ * Invoke an edge function by name.
1270
+ *
1271
+ * @example
1272
+ * const { data, error } = await client.functions.invoke('hello-world', {
1273
+ * body: { name: 'Afribase' },
1274
+ * });
1275
+ */
1276
+ async invoke(functionName, options) {
1277
+ const method = options?.method || "POST";
1278
+ const headers = {
1279
+ ...options?.headers
1280
+ };
1281
+ let body = void 0;
1282
+ if (options?.body !== void 0) {
1283
+ if (typeof options.body === "string" || options.body instanceof Blob || options.body instanceof ArrayBuffer || options.body instanceof FormData) {
1284
+ body = options.body;
1285
+ } else {
1286
+ headers["Content-Type"] = headers["Content-Type"] || "application/json";
1287
+ body = JSON.stringify(options.body);
1288
+ }
1289
+ }
1290
+ if (options?.region) {
1291
+ headers["x-region"] = options.region;
1292
+ }
1293
+ const resp = await fetchWithAuth(`${this.url}/${functionName}`, {
1294
+ method,
1295
+ headers,
1296
+ body,
1297
+ apiKey: this.apiKey,
1298
+ accessToken: this.getAccessToken(),
1299
+ customFetch: this.customFetch
1300
+ });
1301
+ const contentType = resp.headers.get("content-type") || "";
1302
+ if (contentType.includes("application/json")) {
1303
+ return handleResponse(resp);
1304
+ }
1305
+ if (!resp.ok) {
1306
+ const text2 = await resp.text();
1307
+ return {
1308
+ data: null,
1309
+ error: { message: text2, status: resp.status }
1310
+ };
1311
+ }
1312
+ const text = await resp.text();
1313
+ return { data: text, error: null };
1314
+ }
1315
+ };
1316
+
1317
+ // src/lib/AfribaseQueryBuilder.ts
1318
+ var AfribaseQueryBuilder = class {
1319
+ constructor(url, options) {
1320
+ this._method = null;
1321
+ this._body = void 0;
1322
+ this._isSingle = false;
1323
+ this._isMaybeSingle = false;
1324
+ this._isHead = false;
1325
+ this._countType = null;
1326
+ this._url = url;
1327
+ this._apiKey = options.apiKey;
1328
+ this._accessToken = options.accessToken;
1329
+ this._customFetch = options.customFetch;
1330
+ this._query = new URLSearchParams();
1331
+ this._headers = {
1332
+ "Content-Type": "application/json",
1333
+ "Prefer": "return=representation"
1334
+ };
1335
+ }
1336
+ // ─────────────────────────────────────────────────────────────────────
1337
+ // CRUD Operations
1338
+ // ─────────────────────────────────────────────────────────────────────
1339
+ /**
1340
+ * Perform a SELECT query.
1341
+ */
1342
+ select(columns = "*", options) {
1343
+ this._method = "GET";
1344
+ this._query.set("select", columns);
1345
+ if (options?.count) {
1346
+ this._countType = options.count;
1347
+ this._headers["Prefer"] = `count=${options.count}`;
1348
+ }
1349
+ if (options?.head) {
1350
+ this._isHead = true;
1351
+ this._method = "HEAD";
1352
+ }
1353
+ return this;
1354
+ }
1355
+ /**
1356
+ * Perform an INSERT.
1357
+ */
1358
+ insert(values, options) {
1359
+ this._method = "POST";
1360
+ this._body = values;
1361
+ const prefer = ["return=representation"];
1362
+ if (options?.count) {
1363
+ this._countType = options.count;
1364
+ prefer.push(`count=${options.count}`);
1365
+ }
1366
+ if (options?.defaultToNull === false) prefer.push("missing=default");
1367
+ this._headers["Prefer"] = prefer.join(",");
1368
+ return this;
1369
+ }
1370
+ /**
1371
+ * Perform an UPSERT (insert or update on conflict).
1372
+ */
1373
+ upsert(values, options) {
1374
+ this._method = "POST";
1375
+ this._body = values;
1376
+ const prefer = ["return=representation", "resolution=merge-duplicates"];
1377
+ if (options?.ignoreDuplicates) {
1378
+ prefer[1] = "resolution=ignore-duplicates";
1379
+ }
1380
+ if (options?.count) {
1381
+ this._countType = options.count;
1382
+ prefer.push(`count=${options.count}`);
1383
+ }
1384
+ if (options?.defaultToNull === false) prefer.push("missing=default");
1385
+ if (options?.onConflict) this._query.set("on_conflict", options.onConflict);
1386
+ this._headers["Prefer"] = prefer.join(",");
1387
+ return this;
1388
+ }
1389
+ /**
1390
+ * Perform an UPDATE (must be combined with filters).
1391
+ */
1392
+ update(values, options) {
1393
+ this._method = "PATCH";
1394
+ this._body = values;
1395
+ const prefer = ["return=representation"];
1396
+ if (options?.count) {
1397
+ this._countType = options.count;
1398
+ prefer.push(`count=${options.count}`);
1399
+ }
1400
+ this._headers["Prefer"] = prefer.join(",");
1401
+ return this;
1402
+ }
1403
+ /**
1404
+ * Perform a DELETE (must be combined with filters).
1405
+ */
1406
+ delete(options) {
1407
+ this._method = "DELETE";
1408
+ const prefer = ["return=representation"];
1409
+ if (options?.count) {
1410
+ this._countType = options.count;
1411
+ prefer.push(`count=${options.count}`);
1412
+ }
1413
+ this._headers["Prefer"] = prefer.join(",");
1414
+ return this;
1415
+ }
1416
+ // ─────────────────────────────────────────────────────────────────────
1417
+ // Filters
1418
+ // ─────────────────────────────────────────────────────────────────────
1419
+ eq(column, value) {
1420
+ this._query.append(column, `eq.${value}`);
1421
+ return this;
1422
+ }
1423
+ neq(column, value) {
1424
+ this._query.append(column, `neq.${value}`);
1425
+ return this;
1426
+ }
1427
+ gt(column, value) {
1428
+ this._query.append(column, `gt.${value}`);
1429
+ return this;
1430
+ }
1431
+ gte(column, value) {
1432
+ this._query.append(column, `gte.${value}`);
1433
+ return this;
1434
+ }
1435
+ lt(column, value) {
1436
+ this._query.append(column, `lt.${value}`);
1437
+ return this;
1438
+ }
1439
+ lte(column, value) {
1440
+ this._query.append(column, `lte.${value}`);
1441
+ return this;
1442
+ }
1443
+ like(column, pattern) {
1444
+ this._query.append(column, `like.${pattern}`);
1445
+ return this;
1446
+ }
1447
+ ilike(column, pattern) {
1448
+ this._query.append(column, `ilike.${pattern}`);
1449
+ return this;
1450
+ }
1451
+ is(column, value) {
1452
+ this._query.append(column, `is.${value}`);
1453
+ return this;
1454
+ }
1455
+ in(column, values) {
1456
+ const cleanedValues = values.map(
1457
+ (v) => typeof v === "string" ? `"${v}"` : v
1458
+ );
1459
+ this._query.append(column, `in.(${cleanedValues.join(",")})`);
1460
+ return this;
1461
+ }
1462
+ contains(column, value) {
1463
+ if (Array.isArray(value)) {
1464
+ this._query.append(column, `cs.{${value.join(",")}}`);
1465
+ } else if (typeof value === "object") {
1466
+ this._query.append(column, `cs.${JSON.stringify(value)}`);
1467
+ } else {
1468
+ this._query.append(column, `cs.${value}`);
1469
+ }
1470
+ return this;
1471
+ }
1472
+ containedBy(column, value) {
1473
+ if (Array.isArray(value)) {
1474
+ this._query.append(column, `cd.{${value.join(",")}}`);
1475
+ } else if (typeof value === "object") {
1476
+ this._query.append(column, `cd.${JSON.stringify(value)}`);
1477
+ } else {
1478
+ this._query.append(column, `cd.${value}`);
1479
+ }
1480
+ return this;
1481
+ }
1482
+ /**
1483
+ * Full text search using to_tsquery.
1484
+ */
1485
+ textSearch(column, query, options) {
1486
+ let op = "fts";
1487
+ if (options?.type === "plain") op = "plfts";
1488
+ else if (options?.type === "phrase") op = "phfts";
1489
+ else if (options?.type === "websearch") op = "wfts";
1490
+ const configStr = options?.config ? `(${options.config})` : "";
1491
+ this._query.append(column, `${op}${configStr}.${query}`);
1492
+ return this;
1493
+ }
1494
+ /**
1495
+ * Negate a filter.
1496
+ */
1497
+ not(column, operator, value) {
1498
+ this._query.append(column, `not.${operator}.${value}`);
1499
+ return this;
1500
+ }
1501
+ /**
1502
+ * Combine filters with OR.
1503
+ */
1504
+ or(filters, options) {
1505
+ const key = options?.foreignTable ? `${options.foreignTable}.or` : "or";
1506
+ this._query.append(key, `(${filters})`);
1507
+ return this;
1508
+ }
1509
+ /**
1510
+ * Generic filter — any PostgREST operator.
1511
+ */
1512
+ filter(column, operator, value) {
1513
+ this._query.append(column, `${operator}.${value}`);
1514
+ return this;
1515
+ }
1516
+ /**
1517
+ * Match multiple column values (shorthand for multiple .eq).
1518
+ */
1519
+ match(query) {
1520
+ for (const [key, value] of Object.entries(query)) {
1521
+ this._query.append(key, `eq.${value}`);
1522
+ }
1523
+ return this;
1524
+ }
1525
+ // ─────────────────────────────────────────────────────────────────────
1526
+ // Modifiers
1527
+ // ─────────────────────────────────────────────────────────────────────
1528
+ /**
1529
+ * Order results.
1530
+ */
1531
+ order(column, options) {
1532
+ const direction = options?.ascending === false ? "desc" : "asc";
1533
+ const nulls = options?.nullsFirst ? ".nullsfirst" : ".nullslast";
1534
+ const key = options?.foreignTable ? `${options.foreignTable}.order` : "order";
1535
+ this._query.append(key, `${column}.${direction}${nulls}`);
1536
+ return this;
1537
+ }
1538
+ /**
1539
+ * Limit the number of results.
1540
+ */
1541
+ limit(count, options) {
1542
+ const key = options?.foreignTable ? `${options.foreignTable}.limit` : "limit";
1543
+ this._query.set(key, String(count));
1544
+ return this;
1545
+ }
1546
+ /**
1547
+ * Paginate with offset.
1548
+ */
1549
+ range(from, to, options) {
1550
+ const key = options?.foreignTable ? `${options.foreignTable}.offset` : "offset";
1551
+ this._query.set(key, String(from));
1552
+ const limitKey = options?.foreignTable ? `${options.foreignTable}.limit` : "limit";
1553
+ this._query.set(limitKey, String(to - from + 1));
1554
+ this._headers["Range"] = `${from}-${to}`;
1555
+ this._headers["Range-Unit"] = "items";
1556
+ return this;
1557
+ }
1558
+ /**
1559
+ * Return a single row (throws if 0 or 2+ rows).
1560
+ */
1561
+ single() {
1562
+ this._isSingle = true;
1563
+ this._headers["Accept"] = "application/vnd.pgrst.object+json";
1564
+ return this;
1565
+ }
1566
+ /**
1567
+ * Return at most one row (returns null if 0 rows).
1568
+ */
1569
+ maybeSingle() {
1570
+ this._isMaybeSingle = true;
1571
+ this._headers["Accept"] = "application/vnd.pgrst.object+json";
1572
+ return this;
1573
+ }
1574
+ /**
1575
+ * Return as CSV.
1576
+ */
1577
+ csv() {
1578
+ this._headers["Accept"] = "text/csv";
1579
+ return this;
1580
+ }
1581
+ /**
1582
+ * Return a GeoJSON response.
1583
+ */
1584
+ geojson() {
1585
+ this._headers["Accept"] = "application/geo+json";
1586
+ return this;
1587
+ }
1588
+ /**
1589
+ * Limit the fields returned (PostgREST explain).
1590
+ */
1591
+ explain(options) {
1592
+ const parts = ["application/vnd.pgrst.plan"];
1593
+ if (options?.analyze) parts.push("analyze");
1594
+ if (options?.verbose) parts.push("verbose");
1595
+ if (options?.settings) parts.push("settings");
1596
+ if (options?.buffers) parts.push("buffers");
1597
+ if (options?.format) parts.push(options.format);
1598
+ this._headers["Accept"] = parts.join("+");
1599
+ return this;
1600
+ }
1601
+ // ─────────────────────────────────────────────────────────────────────
1602
+ // Execute
1603
+ // ─────────────────────────────────────────────────────────────────────
1604
+ /**
1605
+ * Execute the query and return the results.
1606
+ */
1607
+ async then(onfulfilled, onrejected) {
1608
+ let res;
1609
+ try {
1610
+ res = await this._execute();
1611
+ } catch (err) {
1612
+ if (onrejected) return Promise.resolve(onrejected(err));
1613
+ throw err;
1614
+ }
1615
+ if (onfulfilled) return Promise.resolve(onfulfilled(res));
1616
+ return res;
1617
+ }
1618
+ async _execute() {
1619
+ const method = this._method || "GET";
1620
+ const queryString = this._query.toString();
1621
+ const requestUrl = queryString ? `${this._url}?${queryString}` : this._url;
1622
+ const resp = await fetchWithAuth(requestUrl, {
1623
+ method,
1624
+ headers: this._headers,
1625
+ body: this._body ? JSON.stringify(this._body) : void 0,
1626
+ apiKey: this._apiKey,
1627
+ accessToken: this._accessToken,
1628
+ customFetch: this._customFetch
1629
+ });
1630
+ let count = null;
1631
+ if (this._countType) {
1632
+ const contentRange = resp.headers.get("content-range");
1633
+ if (contentRange) {
1634
+ const match2 = contentRange.match(/\/(\d+)/);
1635
+ if (match2) count = parseInt(match2[1], 10);
1636
+ }
1637
+ }
1638
+ if (!resp.ok) {
1639
+ let errorBody;
1640
+ try {
1641
+ errorBody = await resp.json();
1642
+ } catch {
1643
+ errorBody = { message: await resp.text() };
1644
+ }
1645
+ return {
1646
+ data: null,
1647
+ error: {
1648
+ message: errorBody.message || `Request failed with status ${resp.status}`,
1649
+ details: errorBody.details || "",
1650
+ hint: errorBody.hint || "",
1651
+ code: errorBody.code || String(resp.status)
1652
+ },
1653
+ count,
1654
+ status: resp.status,
1655
+ statusText: resp.statusText
1656
+ };
1657
+ }
1658
+ if (this._isHead || method === "HEAD") {
1659
+ return { data: null, error: null, count, status: resp.status, statusText: resp.statusText };
1660
+ }
1661
+ if (resp.status === 204) {
1662
+ return { data: null, error: null, count, status: resp.status, statusText: resp.statusText };
1663
+ }
1664
+ let data;
1665
+ const contentType = resp.headers.get("content-type") || "";
1666
+ if (contentType.includes("text/csv") || contentType.includes("geo+json") || contentType.includes("pgrst.plan")) {
1667
+ data = await resp.text();
1668
+ } else {
1669
+ data = await resp.json();
1670
+ }
1671
+ if (this._isMaybeSingle && (resp.status === 406 || data === null)) {
1672
+ return { data: null, error: null, count, status: 200, statusText: "OK" };
1673
+ }
1674
+ return { data, error: null, count, status: resp.status, statusText: resp.statusText };
1675
+ }
1676
+ };
1677
+ var AfribaseRpcBuilder = class {
1678
+ constructor(url, fnName, params, options) {
1679
+ this._isSingle = false;
1680
+ this._countType = null;
1681
+ this._url = `${url}/rpc/${fnName}`;
1682
+ this._apiKey = options.apiKey;
1683
+ this._accessToken = options.accessToken;
1684
+ this._customFetch = options.customFetch;
1685
+ this._params = params;
1686
+ this._query = new URLSearchParams();
1687
+ this._headers = {
1688
+ "Content-Type": "application/json",
1689
+ "Prefer": "return=representation"
1690
+ };
1691
+ this._method = options.get ? "GET" : "POST";
1692
+ if (options.count) {
1693
+ this._countType = options.count;
1694
+ this._headers["Prefer"] = `count=${options.count}`;
1695
+ }
1696
+ if (this._method === "GET" && params) {
1697
+ for (const [key, value] of Object.entries(params)) {
1698
+ this._query.set(key, String(value));
1699
+ }
1700
+ }
1701
+ }
1702
+ single() {
1703
+ this._isSingle = true;
1704
+ this._headers["Accept"] = "application/vnd.pgrst.object+json";
1705
+ return this;
1706
+ }
1707
+ maybeSingle() {
1708
+ this._headers["Accept"] = "application/vnd.pgrst.object+json";
1709
+ return this;
1710
+ }
1711
+ select(columns = "*") {
1712
+ this._query.set("select", columns);
1713
+ return this;
1714
+ }
1715
+ order(column, options) {
1716
+ const direction = options?.ascending === false ? "desc" : "asc";
1717
+ this._query.append("order", `${column}.${direction}`);
1718
+ return this;
1719
+ }
1720
+ limit(count) {
1721
+ this._query.set("limit", String(count));
1722
+ return this;
1723
+ }
1724
+ range(from, to) {
1725
+ this._query.set("offset", String(from));
1726
+ this._query.set("limit", String(to - from + 1));
1727
+ this._headers["Range"] = `${from}-${to}`;
1728
+ this._headers["Range-Unit"] = "items";
1729
+ return this;
1730
+ }
1731
+ eq(column, value) {
1732
+ this._query.append(column, `eq.${value}`);
1733
+ return this;
1734
+ }
1735
+ filter(column, operator, value) {
1736
+ this._query.append(column, `${operator}.${value}`);
1737
+ return this;
1738
+ }
1739
+ async then(onfulfilled, onrejected) {
1740
+ let res;
1741
+ try {
1742
+ res = await this._execute();
1743
+ } catch (err) {
1744
+ if (onrejected) return Promise.resolve(onrejected(err));
1745
+ throw err;
1746
+ }
1747
+ if (onfulfilled) return Promise.resolve(onfulfilled(res));
1748
+ return res;
1749
+ }
1750
+ async _execute() {
1751
+ const queryString = this._query.toString();
1752
+ const requestUrl = queryString ? `${this._url}?${queryString}` : this._url;
1753
+ const resp = await fetchWithAuth(requestUrl, {
1754
+ method: this._method,
1755
+ headers: this._headers,
1756
+ body: this._method === "POST" ? JSON.stringify(this._params) : void 0,
1757
+ apiKey: this._apiKey,
1758
+ accessToken: this._accessToken,
1759
+ customFetch: this._customFetch
1760
+ });
1761
+ let count = null;
1762
+ if (this._countType) {
1763
+ const contentRange = resp.headers.get("content-range");
1764
+ if (contentRange) {
1765
+ const match2 = contentRange.match(/\/(\d+)/);
1766
+ if (match2) count = parseInt(match2[1], 10);
1767
+ }
1768
+ }
1769
+ if (!resp.ok) {
1770
+ let errorBody;
1771
+ try {
1772
+ errorBody = await resp.json();
1773
+ } catch {
1774
+ errorBody = { message: await resp.text() };
1775
+ }
1776
+ return {
1777
+ data: null,
1778
+ error: {
1779
+ message: errorBody.message || `RPC failed with status ${resp.status}`,
1780
+ details: errorBody.details || "",
1781
+ hint: errorBody.hint || "",
1782
+ code: errorBody.code || String(resp.status)
1783
+ },
1784
+ count,
1785
+ status: resp.status,
1786
+ statusText: resp.statusText
1787
+ };
1788
+ }
1789
+ if (resp.status === 204) {
1790
+ return { data: null, error: null, count, status: 204, statusText: "No Content" };
1791
+ }
1792
+ const data = await resp.json();
1793
+ return { data, error: null, count, status: resp.status, statusText: resp.statusText };
1794
+ }
1795
+ };
1796
+
1797
+ // src/lib/modular.ts
1798
+ async function db(builder, ...ops) {
1799
+ let current = builder;
1800
+ for (const op of ops) {
1801
+ current = op(current);
1802
+ }
1803
+ return current;
1804
+ }
1805
+ var select = (columns = "*", options) => (b) => b.select(columns, options);
1806
+ var insert = (values, options) => (b) => b.insert(values, options);
1807
+ var update = (values, options) => (b) => b.update(values, options);
1808
+ var upsert = (values, options) => (b) => b.upsert(values, options);
1809
+ var del = (options) => (b) => b.delete(options);
1810
+ var eq = (column, value) => (b) => b.eq(column, value);
1811
+ var neq = (column, value) => (b) => b.neq(column, value);
1812
+ var gt = (column, value) => (b) => b.gt(column, value);
1813
+ var gte = (column, value) => (b) => b.gte(column, value);
1814
+ var lt = (column, value) => (b) => b.lt(column, value);
1815
+ var lte = (column, value) => (b) => b.lte(column, value);
1816
+ var like = (column, pattern) => (b) => b.like(column, pattern);
1817
+ var ilike = (column, pattern) => (b) => b.ilike(column, pattern);
1818
+ var is = (column, value) => (b) => b.is(column, value);
1819
+ var isIn = (column, values) => (b) => b.in(column, values);
1820
+ var contains = (column, value) => (b) => b.contains(column, value);
1821
+ var containedBy = (column, value) => (b) => b.containedBy(column, value);
1822
+ var textSearch = (column, query, options) => (b) => b.textSearch(column, query, options);
1823
+ var not = (column, operator, value) => (b) => b.not(column, operator, value);
1824
+ var or = (filters, options) => (b) => b.or(filters, options);
1825
+ var match = (query) => (b) => b.match(query);
1826
+ var order = (column, options) => (b) => b.order(column, options);
1827
+ var limit = (count, options) => (b) => b.limit(count, options);
1828
+ var range = (from, to, options) => (b) => b.range(from, to, options);
1829
+ var single = () => (b) => b.single();
1830
+ var maybeSingle = () => (b) => b.maybeSingle();
1831
+ var csv = () => (b) => b.csv();
1832
+ var geojson = () => (b) => b.geojson();
1833
+ var explain = (options) => (b) => b.explain(options);
1834
+
1835
+ // src/index.ts
1836
+ var AfribaseClient = class {
1837
+ constructor(baseUrl, anonKey, options) {
1838
+ this.baseUrl = baseUrl.replace(/\/$/, "");
1839
+ this.anonKey = anonKey;
1840
+ this.customFetch = options?.fetch;
1841
+ const authUrl = options?.authUrl || `${this.baseUrl}/auth/v1`;
1842
+ this.restUrl = options?.restUrl || `${this.baseUrl}/rest/v1`;
1843
+ const realtimeUrl = options?.realtimeUrl || this.baseUrl;
1844
+ const storageUrl = options?.storageUrl || `${this.baseUrl}/storage/v1`;
1845
+ const functionsUrl = options?.functionsUrl || `${this.baseUrl}/functions/v1`;
1846
+ this.auth = new AfribaseAuthClient({
1847
+ url: authUrl,
1848
+ apiKey: anonKey,
1849
+ autoRefreshToken: options?.autoRefreshToken ?? true,
1850
+ persistSession: options?.persistSession ?? true,
1851
+ customFetch: this.customFetch
1852
+ });
1853
+ this.realtimeClient = new AfribaseRealtimeClient({
1854
+ url: realtimeUrl,
1855
+ apiKey: anonKey,
1856
+ accessToken: this.auth.accessToken
1857
+ });
1858
+ this.storage = new AfribaseStorageClient({
1859
+ url: storageUrl,
1860
+ apiKey: anonKey,
1861
+ getAccessToken: () => this.auth.accessToken,
1862
+ customFetch: this.customFetch
1863
+ });
1864
+ this.functions = new AfribaseFunctionsClient({
1865
+ url: functionsUrl,
1866
+ apiKey: anonKey,
1867
+ getAccessToken: () => this.auth.accessToken,
1868
+ customFetch: this.customFetch
1869
+ });
1870
+ this.auth.onAuthStateChange((_event, session) => {
1871
+ this.realtimeClient.setAuth(session?.access_token ?? null);
1872
+ });
1873
+ this.auth.initialize();
1874
+ }
1875
+ // ─────────────────────────────────────────────────────────────────────
1876
+ // Database (PostgREST)
1877
+ // ─────────────────────────────────────────────────────────────────────
1878
+ /**
1879
+ * Query a table via PostgREST.
1880
+ *
1881
+ * @example
1882
+ * const { data } = await afribase.from('users').select('*').eq('id', 1);
1883
+ * const { data } = await afribase.from('posts').select('*, comments(*)').order('created_at', { ascending: false }).limit(10);
1884
+ */
1885
+ from(table) {
1886
+ return new AfribaseQueryBuilder(`${this.restUrl}/${table}`, {
1887
+ apiKey: this.anonKey,
1888
+ accessToken: this.auth.accessToken,
1889
+ customFetch: this.customFetch
1890
+ });
1891
+ }
1892
+ /**
1893
+ * Call a Postgres function (RPC).
1894
+ *
1895
+ * @example
1896
+ * const { data } = await afribase.rpc('get_top_users', { limit_count: 10 });
1897
+ */
1898
+ rpc(fn, params = {}, options) {
1899
+ return new AfribaseRpcBuilder(this.restUrl, fn, params, {
1900
+ apiKey: this.anonKey,
1901
+ accessToken: this.auth.accessToken,
1902
+ customFetch: this.customFetch,
1903
+ head: options?.head,
1904
+ count: options?.count,
1905
+ get: options?.get
1906
+ });
1907
+ }
1908
+ // ─────────────────────────────────────────────────────────────────────
1909
+ // Realtime
1910
+ // ─────────────────────────────────────────────────────────────────────
1911
+ /**
1912
+ * Create a realtime channel.
1913
+ *
1914
+ * @example
1915
+ * const channel = afribase.channel('room:lobby');
1916
+ * channel.on('broadcast', { event: 'message' }, (payload) => console.log(payload)).subscribe();
1917
+ */
1918
+ channel(name, options) {
1919
+ return this.realtimeClient.channel(name, options);
1920
+ }
1921
+ /**
1922
+ * Remove all realtime channels and disconnect.
1923
+ */
1924
+ removeAllChannels() {
1925
+ this.realtimeClient.removeAllChannels();
1926
+ }
1927
+ /**
1928
+ * Get the underlying realtime client.
1929
+ */
1930
+ get realtime() {
1931
+ return this.realtimeClient;
1932
+ }
1933
+ };
1934
+ var createClient = (baseUrl, anonKey, options) => {
1935
+ return new AfribaseClient(baseUrl, anonKey, options);
1936
+ };
1937
+ // Annotate the CommonJS export names for ESM import in node:
1938
+ 0 && (module.exports = {
1939
+ AfribaseAuthClient,
1940
+ AfribaseClient,
1941
+ AfribaseFunctionsClient,
1942
+ AfribaseQueryBuilder,
1943
+ AfribaseRealtimeClient,
1944
+ AfribaseRpcBuilder,
1945
+ AfribaseStorageClient,
1946
+ RealtimeChannel,
1947
+ StorageFileApi,
1948
+ containedBy,
1949
+ contains,
1950
+ createClient,
1951
+ csv,
1952
+ db,
1953
+ del,
1954
+ eq,
1955
+ explain,
1956
+ geojson,
1957
+ gt,
1958
+ gte,
1959
+ ilike,
1960
+ insert,
1961
+ is,
1962
+ isIn,
1963
+ like,
1964
+ limit,
1965
+ lt,
1966
+ lte,
1967
+ match,
1968
+ maybeSingle,
1969
+ neq,
1970
+ not,
1971
+ or,
1972
+ order,
1973
+ range,
1974
+ select,
1975
+ single,
1976
+ textSearch,
1977
+ update,
1978
+ upsert
1979
+ });