@insforge/sdk 1.0.1-refresh.8 → 1.0.1-schema.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.mjs CHANGED
@@ -74,7 +74,6 @@ var HttpClient = class {
74
74
  method,
75
75
  headers: requestHeaders,
76
76
  body: processedBody,
77
- credentials: "include",
78
77
  ...fetchOptions
79
78
  });
80
79
  if (response.status === 204) {
@@ -139,29 +138,8 @@ var HttpClient = class {
139
138
  // src/lib/token-manager.ts
140
139
  var TOKEN_KEY = "insforge-auth-token";
141
140
  var USER_KEY = "insforge-auth-user";
142
- var AUTH_FLAG_COOKIE = "isAuthenticated";
143
- function hasAuthCookie() {
144
- if (typeof document === "undefined") return false;
145
- return document.cookie.split(";").some(
146
- (c) => c.trim().startsWith(`${AUTH_FLAG_COOKIE}=`)
147
- );
148
- }
149
- function setAuthCookie() {
150
- if (typeof document === "undefined") return;
151
- const maxAge = 7 * 24 * 60 * 60;
152
- document.cookie = `${AUTH_FLAG_COOKIE}=true; path=/; max-age=${maxAge}; SameSite=Lax`;
153
- }
154
- function clearAuthCookie() {
155
- if (typeof document === "undefined") return;
156
- document.cookie = `${AUTH_FLAG_COOKIE}=; path=/; max-age=0; SameSite=Lax`;
157
- }
158
141
  var TokenManager = class {
159
142
  constructor(storage) {
160
- // In-memory storage
161
- this.accessToken = null;
162
- this.user = null;
163
- // Mode: 'memory' (new backend) or 'storage' (legacy backend, default)
164
- this._mode = "storage";
165
143
  if (storage) {
166
144
  this.storage = storage;
167
145
  } else if (typeof window !== "undefined" && window.localStorage) {
@@ -179,206 +157,35 @@ var TokenManager = class {
179
157
  };
180
158
  }
181
159
  }
182
- /**
183
- * Get current mode
184
- */
185
- get mode() {
186
- return this._mode;
187
- }
188
- /**
189
- * Set mode to memory (new backend with cookies + memory)
190
- */
191
- setMemoryMode() {
192
- if (this._mode === "storage") {
193
- this.storage.removeItem(TOKEN_KEY);
194
- this.storage.removeItem(USER_KEY);
195
- }
196
- this._mode = "memory";
197
- }
198
- /**
199
- * Set mode to storage (legacy backend with localStorage)
200
- * Also loads existing session from localStorage
201
- */
202
- setStorageMode() {
203
- this._mode = "storage";
204
- this.loadFromStorage();
160
+ saveSession(session) {
161
+ this.storage.setItem(TOKEN_KEY, session.accessToken);
162
+ this.storage.setItem(USER_KEY, JSON.stringify(session.user));
205
163
  }
206
- /**
207
- * Load session from localStorage
208
- */
209
- loadFromStorage() {
164
+ getSession() {
210
165
  const token = this.storage.getItem(TOKEN_KEY);
211
166
  const userStr = this.storage.getItem(USER_KEY);
212
- if (token && userStr) {
213
- try {
214
- this.accessToken = token;
215
- this.user = JSON.parse(userStr);
216
- } catch {
217
- this.clearSession();
218
- }
167
+ if (!token || !userStr) {
168
+ return null;
219
169
  }
220
- }
221
- /**
222
- * Save session (memory always, localStorage only in storage mode)
223
- */
224
- saveSession(session) {
225
- this.accessToken = session.accessToken;
226
- this.user = session.user;
227
- if (this._mode === "storage") {
228
- this.storage.setItem(TOKEN_KEY, session.accessToken);
229
- this.storage.setItem(USER_KEY, JSON.stringify(session.user));
170
+ try {
171
+ const user = JSON.parse(userStr);
172
+ return { accessToken: token, user };
173
+ } catch {
174
+ this.clearSession();
175
+ return null;
230
176
  }
231
177
  }
232
- /**
233
- * Get current session
234
- */
235
- getSession() {
236
- if (!this.accessToken || !this.user) return null;
237
- return {
238
- accessToken: this.accessToken,
239
- user: this.user
240
- };
241
- }
242
- /**
243
- * Get access token
244
- */
245
178
  getAccessToken() {
246
- return this.accessToken;
247
- }
248
- /**
249
- * Set access token
250
- */
251
- setAccessToken(token) {
252
- this.accessToken = token;
253
- if (this._mode === "storage") {
254
- this.storage.setItem(TOKEN_KEY, token);
255
- }
256
- }
257
- /**
258
- * Get user
259
- */
260
- getUser() {
261
- return this.user;
262
- }
263
- /**
264
- * Set user
265
- */
266
- setUser(user) {
267
- this.user = user;
268
- if (this._mode === "storage") {
269
- this.storage.setItem(USER_KEY, JSON.stringify(user));
270
- }
179
+ const token = this.storage.getItem(TOKEN_KEY);
180
+ return typeof token === "string" ? token : null;
271
181
  }
272
- /**
273
- * Clear session (both memory and localStorage)
274
- */
275
182
  clearSession() {
276
- this.accessToken = null;
277
- this.user = null;
278
183
  this.storage.removeItem(TOKEN_KEY);
279
184
  this.storage.removeItem(USER_KEY);
280
185
  }
281
- /**
282
- * Check if there's a session in localStorage (for legacy detection)
283
- */
284
- hasStoredSession() {
285
- const token = this.storage.getItem(TOKEN_KEY);
286
- return !!token;
287
- }
288
- };
289
-
290
- // src/modules/database-postgrest.ts
291
- import { PostgrestClient } from "@supabase/postgrest-js";
292
- function createInsForgePostgrestFetch(httpClient, tokenManager) {
293
- return async (input, init) => {
294
- const url = typeof input === "string" ? input : input.toString();
295
- const urlObj = new URL(url);
296
- const tableName = urlObj.pathname.slice(1);
297
- const insforgeUrl = `${httpClient.baseUrl}/api/database/records/${tableName}${urlObj.search}`;
298
- const token = tokenManager.getAccessToken();
299
- const httpHeaders = httpClient.getHeaders();
300
- const authToken = token || httpHeaders["Authorization"]?.replace("Bearer ", "");
301
- const headers = new Headers(init?.headers);
302
- if (authToken && !headers.has("Authorization")) {
303
- headers.set("Authorization", `Bearer ${authToken}`);
304
- }
305
- const response = await fetch(insforgeUrl, {
306
- ...init,
307
- headers
308
- });
309
- return response;
310
- };
311
- }
312
- var Database = class {
313
- constructor(httpClient, tokenManager) {
314
- this.postgrest = new PostgrestClient("http://dummy", {
315
- fetch: createInsForgePostgrestFetch(httpClient, tokenManager),
316
- headers: {}
317
- });
318
- }
319
- /**
320
- * Create a query builder for a table
321
- *
322
- * @example
323
- * // Basic query
324
- * const { data, error } = await client.database
325
- * .from('posts')
326
- * .select('*')
327
- * .eq('user_id', userId);
328
- *
329
- * // With count (Supabase style!)
330
- * const { data, error, count } = await client.database
331
- * .from('posts')
332
- * .select('*', { count: 'exact' })
333
- * .range(0, 9);
334
- *
335
- * // Just get count, no data
336
- * const { count } = await client.database
337
- * .from('posts')
338
- * .select('*', { count: 'exact', head: true });
339
- *
340
- * // Complex queries with OR
341
- * const { data } = await client.database
342
- * .from('posts')
343
- * .select('*, users!inner(*)')
344
- * .or('status.eq.active,status.eq.pending');
345
- *
346
- * // All features work:
347
- * - Nested selects
348
- * - Foreign key expansion
349
- * - OR/AND/NOT conditions
350
- * - Count with head
351
- * - Range pagination
352
- * - Upserts
353
- */
354
- from(table) {
355
- return this.postgrest.from(table);
356
- }
357
186
  };
358
187
 
359
188
  // src/modules/auth.ts
360
- function convertDbProfileToCamelCase(dbProfile) {
361
- const result = {
362
- id: dbProfile.id
363
- };
364
- Object.keys(dbProfile).forEach((key) => {
365
- result[key] = dbProfile[key];
366
- if (key.includes("_")) {
367
- const camelKey = key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
368
- result[camelKey] = dbProfile[key];
369
- }
370
- });
371
- return result;
372
- }
373
- function convertCamelCaseToDbProfile(profile) {
374
- const dbProfile = {};
375
- Object.keys(profile).forEach((key) => {
376
- if (profile[key] === void 0) return;
377
- const snakeKey = key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
378
- dbProfile[snakeKey] = profile[key];
379
- });
380
- return dbProfile;
381
- }
382
189
  function isHostedAuthEnvironment() {
383
190
  if (typeof window === "undefined") {
384
191
  return false;
@@ -396,74 +203,8 @@ var Auth = class {
396
203
  constructor(http, tokenManager) {
397
204
  this.http = http;
398
205
  this.tokenManager = tokenManager;
399
- this.database = new Database(http, tokenManager);
400
206
  this.detectAuthCallback();
401
207
  }
402
- /**
403
- * Restore session on app initialization
404
- *
405
- * @returns Object with isLoggedIn status
406
- *
407
- * @example
408
- * ```typescript
409
- * const client = new InsForgeClient({ baseUrl: '...' });
410
- * const { isLoggedIn } = await client.auth.restoreSession();
411
- *
412
- * if (isLoggedIn) {
413
- * const { data } = await client.auth.getCurrentUser();
414
- * }
415
- * ```
416
- */
417
- async restoreSession() {
418
- if (typeof window === "undefined") {
419
- return { isLoggedIn: false };
420
- }
421
- if (this.tokenManager.getAccessToken()) {
422
- return { isLoggedIn: true };
423
- }
424
- if (hasAuthCookie()) {
425
- try {
426
- const response = await this.http.post(
427
- "/api/auth/refresh"
428
- );
429
- if (response.accessToken) {
430
- this.tokenManager.setMemoryMode();
431
- this.tokenManager.setAccessToken(response.accessToken);
432
- this.http.setAuthToken(response.accessToken);
433
- if (response.user) {
434
- this.tokenManager.setUser(response.user);
435
- }
436
- return { isLoggedIn: true };
437
- }
438
- } catch (error) {
439
- if (error instanceof InsForgeError) {
440
- if (error.statusCode === 404) {
441
- this.tokenManager.setStorageMode();
442
- const token = this.tokenManager.getAccessToken();
443
- if (token) {
444
- this.http.setAuthToken(token);
445
- return { isLoggedIn: true };
446
- }
447
- return { isLoggedIn: false };
448
- }
449
- if (error.statusCode === 401 || error.statusCode === 403) {
450
- clearAuthCookie();
451
- return { isLoggedIn: false };
452
- }
453
- }
454
- return { isLoggedIn: false };
455
- }
456
- }
457
- if (this.tokenManager.hasStoredSession()) {
458
- this.tokenManager.setStorageMode();
459
- const token = this.tokenManager.getAccessToken();
460
- if (token) {
461
- this.http.setAuthToken(token);
462
- return { isLoggedIn: true };
463
- }
464
- }
465
- return { isLoggedIn: false };
466
- }
467
208
  /**
468
209
  * Automatically detect and handle OAuth callback parameters in the URL
469
210
  * This runs on initialization to seamlessly complete the OAuth flow
@@ -491,9 +232,8 @@ var Auth = class {
491
232
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
492
233
  }
493
234
  };
494
- this.http.setAuthToken(accessToken);
495
235
  this.tokenManager.saveSession(session);
496
- setAuthCookie();
236
+ this.http.setAuthToken(accessToken);
497
237
  const url = new URL(window.location.href);
498
238
  url.searchParams.delete("access_token");
499
239
  url.searchParams.delete("user_id");
@@ -514,13 +254,14 @@ var Auth = class {
514
254
  async signUp(request) {
515
255
  try {
516
256
  const response = await this.http.post("/api/auth/users", request);
517
- if (response.accessToken && response.user && !isHostedAuthEnvironment()) {
257
+ if (response.accessToken && response.user) {
518
258
  const session = {
519
259
  accessToken: response.accessToken,
520
260
  user: response.user
521
261
  };
522
- this.tokenManager.saveSession(session);
523
- setAuthCookie();
262
+ if (!isHostedAuthEnvironment()) {
263
+ this.tokenManager.saveSession(session);
264
+ }
524
265
  this.http.setAuthToken(response.accessToken);
525
266
  }
526
267
  return {
@@ -547,15 +288,21 @@ var Auth = class {
547
288
  async signInWithPassword(request) {
548
289
  try {
549
290
  const response = await this.http.post("/api/auth/sessions", request);
550
- if (response.accessToken && response.user && !isHostedAuthEnvironment()) {
551
- const session = {
552
- accessToken: response.accessToken,
553
- user: response.user
554
- };
291
+ const session = {
292
+ accessToken: response.accessToken || "",
293
+ user: response.user || {
294
+ id: "",
295
+ email: "",
296
+ name: "",
297
+ emailVerified: false,
298
+ createdAt: "",
299
+ updatedAt: ""
300
+ }
301
+ };
302
+ if (!isHostedAuthEnvironment()) {
555
303
  this.tokenManager.saveSession(session);
556
- setAuthCookie();
557
- this.http.setAuthToken(response.accessToken);
558
304
  }
305
+ this.http.setAuthToken(response.accessToken || "");
559
306
  return {
560
307
  data: response,
561
308
  error: null
@@ -613,13 +360,8 @@ var Auth = class {
613
360
  */
614
361
  async signOut() {
615
362
  try {
616
- try {
617
- await this.http.post("/api/auth/logout");
618
- } catch {
619
- }
620
363
  this.tokenManager.clearSession();
621
364
  this.http.setAuthToken(null);
622
- clearAuthCookie();
623
365
  return { error: null };
624
366
  } catch (error) {
625
367
  return {
@@ -680,14 +422,9 @@ var Auth = class {
680
422
  }
681
423
  this.http.setAuthToken(session.accessToken);
682
424
  const authResponse = await this.http.get("/api/auth/sessions/current");
683
- const { data: profile, error: profileError } = await this.database.from("users").select("*").eq("id", authResponse.user.id).single();
684
- if (profileError && profileError.code !== "PGRST116") {
685
- return { data: null, error: profileError };
686
- }
687
425
  return {
688
426
  data: {
689
- user: authResponse.user,
690
- profile: profile ? convertDbProfileToCamelCase(profile) : null
427
+ user: authResponse.user
691
428
  },
692
429
  error: null
693
430
  };
@@ -711,17 +448,28 @@ var Auth = class {
711
448
  }
712
449
  /**
713
450
  * Get any user's profile by ID
714
- * Returns profile information from the users table (dynamic fields)
451
+ * Returns profile information from the users table
715
452
  */
716
453
  async getProfile(userId) {
717
- const { data, error } = await this.database.from("users").select("*").eq("id", userId).single();
718
- if (error && error.code === "PGRST116") {
719
- return { data: null, error: null };
720
- }
721
- if (data) {
722
- return { data: convertDbProfileToCamelCase(data), error: null };
454
+ try {
455
+ const response = await this.http.get(`/api/auth/profiles/${userId}`);
456
+ return {
457
+ data: response,
458
+ error: null
459
+ };
460
+ } catch (error) {
461
+ if (error instanceof InsForgeError) {
462
+ return { data: null, error };
463
+ }
464
+ return {
465
+ data: null,
466
+ error: new InsForgeError(
467
+ "An unexpected error occurred while fetching user profile",
468
+ 500,
469
+ "UNEXPECTED_ERROR"
470
+ )
471
+ };
723
472
  }
724
- return { data: null, error };
725
473
  }
726
474
  /**
727
475
  * Get the current session (only session data, no API call)
@@ -752,42 +500,31 @@ var Auth = class {
752
500
  /**
753
501
  * Set/Update the current user's profile
754
502
  * Updates profile information in the users table (supports any dynamic fields)
503
+ * Requires authentication
755
504
  */
756
505
  async setProfile(profile) {
757
- const session = this.tokenManager.getSession();
758
- if (!session?.accessToken) {
506
+ try {
507
+ const response = await this.http.patch(
508
+ "/api/auth/profiles/current",
509
+ { profile }
510
+ );
511
+ return {
512
+ data: response,
513
+ error: null
514
+ };
515
+ } catch (error) {
516
+ if (error instanceof InsForgeError) {
517
+ return { data: null, error };
518
+ }
759
519
  return {
760
520
  data: null,
761
521
  error: new InsForgeError(
762
- "No authenticated user found",
763
- 401,
764
- "UNAUTHENTICATED"
522
+ "An unexpected error occurred while updating user profile",
523
+ 500,
524
+ "UNEXPECTED_ERROR"
765
525
  )
766
526
  };
767
527
  }
768
- if (!session.user?.id) {
769
- const { data: data2, error: error2 } = await this.getCurrentUser();
770
- if (error2) {
771
- return { data: null, error: error2 };
772
- }
773
- if (data2?.user) {
774
- session.user = {
775
- id: data2.user.id,
776
- email: data2.user.email,
777
- name: "",
778
- emailVerified: false,
779
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
780
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
781
- };
782
- this.tokenManager.saveSession(session);
783
- }
784
- }
785
- const dbProfile = convertCamelCaseToDbProfile(profile);
786
- const { data, error } = await this.database.from("users").update(dbProfile).eq("id", session.user.id).select().single();
787
- if (data) {
788
- return { data: convertDbProfileToCamelCase(data), error: null };
789
- }
790
- return { data: null, error };
791
528
  }
792
529
  /**
793
530
  * Send email verification (code or link based on config)
@@ -941,14 +678,13 @@ var Auth = class {
941
678
  "/api/auth/email/verify",
942
679
  request
943
680
  );
944
- if (response.accessToken && !isHostedAuthEnvironment()) {
681
+ if (response.accessToken) {
945
682
  const session = {
946
683
  accessToken: response.accessToken,
947
684
  user: response.user || {}
948
685
  };
949
686
  this.tokenManager.saveSession(session);
950
687
  this.http.setAuthToken(response.accessToken);
951
- setAuthCookie();
952
688
  }
953
689
  return {
954
690
  data: response,
@@ -970,6 +706,75 @@ var Auth = class {
970
706
  }
971
707
  };
972
708
 
709
+ // src/modules/database-postgrest.ts
710
+ import { PostgrestClient } from "@supabase/postgrest-js";
711
+ function createInsForgePostgrestFetch(httpClient, tokenManager) {
712
+ return async (input, init) => {
713
+ const url = typeof input === "string" ? input : input.toString();
714
+ const urlObj = new URL(url);
715
+ const tableName = urlObj.pathname.slice(1);
716
+ const insforgeUrl = `${httpClient.baseUrl}/api/database/records/${tableName}${urlObj.search}`;
717
+ const token = tokenManager.getAccessToken();
718
+ const httpHeaders = httpClient.getHeaders();
719
+ const authToken = token || httpHeaders["Authorization"]?.replace("Bearer ", "");
720
+ const headers = new Headers(init?.headers);
721
+ if (authToken && !headers.has("Authorization")) {
722
+ headers.set("Authorization", `Bearer ${authToken}`);
723
+ }
724
+ const response = await fetch(insforgeUrl, {
725
+ ...init,
726
+ headers
727
+ });
728
+ return response;
729
+ };
730
+ }
731
+ var Database = class {
732
+ constructor(httpClient, tokenManager) {
733
+ this.postgrest = new PostgrestClient("http://dummy", {
734
+ fetch: createInsForgePostgrestFetch(httpClient, tokenManager),
735
+ headers: {}
736
+ });
737
+ }
738
+ /**
739
+ * Create a query builder for a table
740
+ *
741
+ * @example
742
+ * // Basic query
743
+ * const { data, error } = await client.database
744
+ * .from('posts')
745
+ * .select('*')
746
+ * .eq('user_id', userId);
747
+ *
748
+ * // With count (Supabase style!)
749
+ * const { data, error, count } = await client.database
750
+ * .from('posts')
751
+ * .select('*', { count: 'exact' })
752
+ * .range(0, 9);
753
+ *
754
+ * // Just get count, no data
755
+ * const { count } = await client.database
756
+ * .from('posts')
757
+ * .select('*', { count: 'exact', head: true });
758
+ *
759
+ * // Complex queries with OR
760
+ * const { data } = await client.database
761
+ * .from('posts')
762
+ * .select('*, users!inner(*)')
763
+ * .or('status.eq.active,status.eq.pending');
764
+ *
765
+ * // All features work:
766
+ * - Nested selects
767
+ * - Foreign key expansion
768
+ * - OR/AND/NOT conditions
769
+ * - Count with head
770
+ * - Range pagination
771
+ * - Upserts
772
+ */
773
+ from(table) {
774
+ return this.postgrest.from(table);
775
+ }
776
+ };
777
+
973
778
  // src/modules/storage.ts
974
779
  var StorageBucket = class {
975
780
  constructor(bucketName, http) {
@@ -1491,6 +1296,239 @@ var Functions = class {
1491
1296
  }
1492
1297
  };
1493
1298
 
1299
+ // src/modules/realtime.ts
1300
+ import { io } from "socket.io-client";
1301
+ var CONNECT_TIMEOUT = 1e4;
1302
+ var Realtime = class {
1303
+ constructor(baseUrl, tokenManager) {
1304
+ this.socket = null;
1305
+ this.connectPromise = null;
1306
+ this.subscribedChannels = /* @__PURE__ */ new Set();
1307
+ this.eventListeners = /* @__PURE__ */ new Map();
1308
+ this.baseUrl = baseUrl;
1309
+ this.tokenManager = tokenManager;
1310
+ }
1311
+ notifyListeners(event, payload) {
1312
+ const listeners = this.eventListeners.get(event);
1313
+ if (!listeners) return;
1314
+ for (const cb of listeners) {
1315
+ try {
1316
+ cb(payload);
1317
+ } catch (err) {
1318
+ console.error(`Error in ${event} callback:`, err);
1319
+ }
1320
+ }
1321
+ }
1322
+ /**
1323
+ * Connect to the realtime server
1324
+ * @returns Promise that resolves when connected
1325
+ */
1326
+ connect() {
1327
+ if (this.socket?.connected) {
1328
+ return Promise.resolve();
1329
+ }
1330
+ if (this.connectPromise) {
1331
+ return this.connectPromise;
1332
+ }
1333
+ this.connectPromise = new Promise((resolve, reject) => {
1334
+ const session = this.tokenManager.getSession();
1335
+ const token = session?.accessToken;
1336
+ this.socket = io(this.baseUrl, {
1337
+ transports: ["websocket"],
1338
+ auth: token ? { token } : void 0
1339
+ });
1340
+ let initialConnection = true;
1341
+ let timeoutId = null;
1342
+ const cleanup = () => {
1343
+ if (timeoutId) {
1344
+ clearTimeout(timeoutId);
1345
+ timeoutId = null;
1346
+ }
1347
+ };
1348
+ timeoutId = setTimeout(() => {
1349
+ if (initialConnection) {
1350
+ initialConnection = false;
1351
+ this.connectPromise = null;
1352
+ this.socket?.disconnect();
1353
+ this.socket = null;
1354
+ reject(new Error(`Connection timeout after ${CONNECT_TIMEOUT}ms`));
1355
+ }
1356
+ }, CONNECT_TIMEOUT);
1357
+ this.socket.on("connect", () => {
1358
+ cleanup();
1359
+ for (const channel of this.subscribedChannels) {
1360
+ this.socket.emit("realtime:subscribe", { channel });
1361
+ }
1362
+ this.notifyListeners("connect");
1363
+ if (initialConnection) {
1364
+ initialConnection = false;
1365
+ this.connectPromise = null;
1366
+ resolve();
1367
+ }
1368
+ });
1369
+ this.socket.on("connect_error", (error) => {
1370
+ cleanup();
1371
+ this.notifyListeners("connect_error", error);
1372
+ if (initialConnection) {
1373
+ initialConnection = false;
1374
+ this.connectPromise = null;
1375
+ reject(error);
1376
+ }
1377
+ });
1378
+ this.socket.on("disconnect", (reason) => {
1379
+ this.notifyListeners("disconnect", reason);
1380
+ });
1381
+ this.socket.on("realtime:error", (error) => {
1382
+ this.notifyListeners("error", error);
1383
+ });
1384
+ this.socket.onAny((event, message) => {
1385
+ if (event === "realtime:error") return;
1386
+ this.notifyListeners(event, message);
1387
+ });
1388
+ });
1389
+ return this.connectPromise;
1390
+ }
1391
+ /**
1392
+ * Disconnect from the realtime server
1393
+ */
1394
+ disconnect() {
1395
+ if (this.socket) {
1396
+ this.socket.disconnect();
1397
+ this.socket = null;
1398
+ }
1399
+ this.subscribedChannels.clear();
1400
+ }
1401
+ /**
1402
+ * Check if connected to the realtime server
1403
+ */
1404
+ get isConnected() {
1405
+ return this.socket?.connected ?? false;
1406
+ }
1407
+ /**
1408
+ * Get the current connection state
1409
+ */
1410
+ get connectionState() {
1411
+ if (!this.socket) return "disconnected";
1412
+ if (this.socket.connected) return "connected";
1413
+ return "connecting";
1414
+ }
1415
+ /**
1416
+ * Get the socket ID (if connected)
1417
+ */
1418
+ get socketId() {
1419
+ return this.socket?.id;
1420
+ }
1421
+ /**
1422
+ * Subscribe to a channel
1423
+ *
1424
+ * Automatically connects if not already connected.
1425
+ *
1426
+ * @param channel - Channel name (e.g., 'orders:123', 'broadcast')
1427
+ * @returns Promise with the subscription response
1428
+ */
1429
+ async subscribe(channel) {
1430
+ if (this.subscribedChannels.has(channel)) {
1431
+ return { ok: true, channel };
1432
+ }
1433
+ if (!this.socket?.connected) {
1434
+ try {
1435
+ await this.connect();
1436
+ } catch (error) {
1437
+ const message = error instanceof Error ? error.message : "Connection failed";
1438
+ return { ok: false, channel, error: { code: "CONNECTION_FAILED", message } };
1439
+ }
1440
+ }
1441
+ return new Promise((resolve) => {
1442
+ this.socket.emit("realtime:subscribe", { channel }, (response) => {
1443
+ if (response.ok) {
1444
+ this.subscribedChannels.add(channel);
1445
+ }
1446
+ resolve(response);
1447
+ });
1448
+ });
1449
+ }
1450
+ /**
1451
+ * Unsubscribe from a channel (fire-and-forget)
1452
+ *
1453
+ * @param channel - Channel name to unsubscribe from
1454
+ */
1455
+ unsubscribe(channel) {
1456
+ this.subscribedChannels.delete(channel);
1457
+ if (this.socket?.connected) {
1458
+ this.socket.emit("realtime:unsubscribe", { channel });
1459
+ }
1460
+ }
1461
+ /**
1462
+ * Publish a message to a channel
1463
+ *
1464
+ * @param channel - Channel name
1465
+ * @param event - Event name
1466
+ * @param payload - Message payload
1467
+ */
1468
+ async publish(channel, event, payload) {
1469
+ if (!this.socket?.connected) {
1470
+ throw new Error("Not connected to realtime server. Call connect() first.");
1471
+ }
1472
+ this.socket.emit("realtime:publish", { channel, event, payload });
1473
+ }
1474
+ /**
1475
+ * Listen for events
1476
+ *
1477
+ * Reserved event names:
1478
+ * - 'connect' - Fired when connected to the server
1479
+ * - 'connect_error' - Fired when connection fails (payload: Error)
1480
+ * - 'disconnect' - Fired when disconnected (payload: reason string)
1481
+ * - 'error' - Fired when a realtime error occurs (payload: RealtimeErrorPayload)
1482
+ *
1483
+ * All other events receive a `SocketMessage` payload with metadata.
1484
+ *
1485
+ * @param event - Event name to listen for
1486
+ * @param callback - Callback function when event is received
1487
+ */
1488
+ on(event, callback) {
1489
+ if (!this.eventListeners.has(event)) {
1490
+ this.eventListeners.set(event, /* @__PURE__ */ new Set());
1491
+ }
1492
+ this.eventListeners.get(event).add(callback);
1493
+ }
1494
+ /**
1495
+ * Remove a listener for a specific event
1496
+ *
1497
+ * @param event - Event name
1498
+ * @param callback - The callback function to remove
1499
+ */
1500
+ off(event, callback) {
1501
+ const listeners = this.eventListeners.get(event);
1502
+ if (listeners) {
1503
+ listeners.delete(callback);
1504
+ if (listeners.size === 0) {
1505
+ this.eventListeners.delete(event);
1506
+ }
1507
+ }
1508
+ }
1509
+ /**
1510
+ * Listen for an event only once, then automatically remove the listener
1511
+ *
1512
+ * @param event - Event name to listen for
1513
+ * @param callback - Callback function when event is received
1514
+ */
1515
+ once(event, callback) {
1516
+ const wrapper = (payload) => {
1517
+ this.off(event, wrapper);
1518
+ callback(payload);
1519
+ };
1520
+ this.on(event, wrapper);
1521
+ }
1522
+ /**
1523
+ * Get all currently subscribed channels
1524
+ *
1525
+ * @returns Array of channel names
1526
+ */
1527
+ getSubscribedChannels() {
1528
+ return Array.from(this.subscribedChannels);
1529
+ }
1530
+ };
1531
+
1494
1532
  // src/client.ts
1495
1533
  var InsForgeClient = class {
1496
1534
  constructor(config = {}) {
@@ -1508,11 +1546,15 @@ var InsForgeClient = class {
1508
1546
  if (existingSession?.accessToken) {
1509
1547
  this.http.setAuthToken(existingSession.accessToken);
1510
1548
  }
1511
- this.auth = new Auth(this.http, this.tokenManager);
1549
+ this.auth = new Auth(
1550
+ this.http,
1551
+ this.tokenManager
1552
+ );
1512
1553
  this.database = new Database(this.http, this.tokenManager);
1513
1554
  this.storage = new Storage(this.http);
1514
1555
  this.ai = new AI(this.http);
1515
1556
  this.functions = new Functions(this.http);
1557
+ this.realtime = new Realtime(this.http.baseUrl, this.tokenManager);
1516
1558
  }
1517
1559
  /**
1518
1560
  * Get the underlying HTTP client for custom requests
@@ -1549,6 +1591,7 @@ export {
1549
1591
  HttpClient,
1550
1592
  InsForgeClient,
1551
1593
  InsForgeError,
1594
+ Realtime,
1552
1595
  Storage,
1553
1596
  StorageBucket,
1554
1597
  TokenManager,