@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.js CHANGED
@@ -27,6 +27,7 @@ __export(index_exports, {
27
27
  HttpClient: () => HttpClient,
28
28
  InsForgeClient: () => InsForgeClient,
29
29
  InsForgeError: () => InsForgeError,
30
+ Realtime: () => Realtime,
30
31
  Storage: () => Storage,
31
32
  StorageBucket: () => StorageBucket,
32
33
  TokenManager: () => TokenManager,
@@ -111,7 +112,6 @@ var HttpClient = class {
111
112
  method,
112
113
  headers: requestHeaders,
113
114
  body: processedBody,
114
- credentials: "include",
115
115
  ...fetchOptions
116
116
  });
117
117
  if (response.status === 204) {
@@ -176,29 +176,8 @@ var HttpClient = class {
176
176
  // src/lib/token-manager.ts
177
177
  var TOKEN_KEY = "insforge-auth-token";
178
178
  var USER_KEY = "insforge-auth-user";
179
- var AUTH_FLAG_COOKIE = "isAuthenticated";
180
- function hasAuthCookie() {
181
- if (typeof document === "undefined") return false;
182
- return document.cookie.split(";").some(
183
- (c) => c.trim().startsWith(`${AUTH_FLAG_COOKIE}=`)
184
- );
185
- }
186
- function setAuthCookie() {
187
- if (typeof document === "undefined") return;
188
- const maxAge = 7 * 24 * 60 * 60;
189
- document.cookie = `${AUTH_FLAG_COOKIE}=true; path=/; max-age=${maxAge}; SameSite=Lax`;
190
- }
191
- function clearAuthCookie() {
192
- if (typeof document === "undefined") return;
193
- document.cookie = `${AUTH_FLAG_COOKIE}=; path=/; max-age=0; SameSite=Lax`;
194
- }
195
179
  var TokenManager = class {
196
180
  constructor(storage) {
197
- // In-memory storage
198
- this.accessToken = null;
199
- this.user = null;
200
- // Mode: 'memory' (new backend) or 'storage' (legacy backend, default)
201
- this._mode = "storage";
202
181
  if (storage) {
203
182
  this.storage = storage;
204
183
  } else if (typeof window !== "undefined" && window.localStorage) {
@@ -216,206 +195,35 @@ var TokenManager = class {
216
195
  };
217
196
  }
218
197
  }
219
- /**
220
- * Get current mode
221
- */
222
- get mode() {
223
- return this._mode;
224
- }
225
- /**
226
- * Set mode to memory (new backend with cookies + memory)
227
- */
228
- setMemoryMode() {
229
- if (this._mode === "storage") {
230
- this.storage.removeItem(TOKEN_KEY);
231
- this.storage.removeItem(USER_KEY);
232
- }
233
- this._mode = "memory";
234
- }
235
- /**
236
- * Set mode to storage (legacy backend with localStorage)
237
- * Also loads existing session from localStorage
238
- */
239
- setStorageMode() {
240
- this._mode = "storage";
241
- this.loadFromStorage();
198
+ saveSession(session) {
199
+ this.storage.setItem(TOKEN_KEY, session.accessToken);
200
+ this.storage.setItem(USER_KEY, JSON.stringify(session.user));
242
201
  }
243
- /**
244
- * Load session from localStorage
245
- */
246
- loadFromStorage() {
202
+ getSession() {
247
203
  const token = this.storage.getItem(TOKEN_KEY);
248
204
  const userStr = this.storage.getItem(USER_KEY);
249
- if (token && userStr) {
250
- try {
251
- this.accessToken = token;
252
- this.user = JSON.parse(userStr);
253
- } catch {
254
- this.clearSession();
255
- }
205
+ if (!token || !userStr) {
206
+ return null;
256
207
  }
257
- }
258
- /**
259
- * Save session (memory always, localStorage only in storage mode)
260
- */
261
- saveSession(session) {
262
- this.accessToken = session.accessToken;
263
- this.user = session.user;
264
- if (this._mode === "storage") {
265
- this.storage.setItem(TOKEN_KEY, session.accessToken);
266
- this.storage.setItem(USER_KEY, JSON.stringify(session.user));
208
+ try {
209
+ const user = JSON.parse(userStr);
210
+ return { accessToken: token, user };
211
+ } catch {
212
+ this.clearSession();
213
+ return null;
267
214
  }
268
215
  }
269
- /**
270
- * Get current session
271
- */
272
- getSession() {
273
- if (!this.accessToken || !this.user) return null;
274
- return {
275
- accessToken: this.accessToken,
276
- user: this.user
277
- };
278
- }
279
- /**
280
- * Get access token
281
- */
282
216
  getAccessToken() {
283
- return this.accessToken;
284
- }
285
- /**
286
- * Set access token
287
- */
288
- setAccessToken(token) {
289
- this.accessToken = token;
290
- if (this._mode === "storage") {
291
- this.storage.setItem(TOKEN_KEY, token);
292
- }
293
- }
294
- /**
295
- * Get user
296
- */
297
- getUser() {
298
- return this.user;
299
- }
300
- /**
301
- * Set user
302
- */
303
- setUser(user) {
304
- this.user = user;
305
- if (this._mode === "storage") {
306
- this.storage.setItem(USER_KEY, JSON.stringify(user));
307
- }
217
+ const token = this.storage.getItem(TOKEN_KEY);
218
+ return typeof token === "string" ? token : null;
308
219
  }
309
- /**
310
- * Clear session (both memory and localStorage)
311
- */
312
220
  clearSession() {
313
- this.accessToken = null;
314
- this.user = null;
315
221
  this.storage.removeItem(TOKEN_KEY);
316
222
  this.storage.removeItem(USER_KEY);
317
223
  }
318
- /**
319
- * Check if there's a session in localStorage (for legacy detection)
320
- */
321
- hasStoredSession() {
322
- const token = this.storage.getItem(TOKEN_KEY);
323
- return !!token;
324
- }
325
- };
326
-
327
- // src/modules/database-postgrest.ts
328
- var import_postgrest_js = require("@supabase/postgrest-js");
329
- function createInsForgePostgrestFetch(httpClient, tokenManager) {
330
- return async (input, init) => {
331
- const url = typeof input === "string" ? input : input.toString();
332
- const urlObj = new URL(url);
333
- const tableName = urlObj.pathname.slice(1);
334
- const insforgeUrl = `${httpClient.baseUrl}/api/database/records/${tableName}${urlObj.search}`;
335
- const token = tokenManager.getAccessToken();
336
- const httpHeaders = httpClient.getHeaders();
337
- const authToken = token || httpHeaders["Authorization"]?.replace("Bearer ", "");
338
- const headers = new Headers(init?.headers);
339
- if (authToken && !headers.has("Authorization")) {
340
- headers.set("Authorization", `Bearer ${authToken}`);
341
- }
342
- const response = await fetch(insforgeUrl, {
343
- ...init,
344
- headers
345
- });
346
- return response;
347
- };
348
- }
349
- var Database = class {
350
- constructor(httpClient, tokenManager) {
351
- this.postgrest = new import_postgrest_js.PostgrestClient("http://dummy", {
352
- fetch: createInsForgePostgrestFetch(httpClient, tokenManager),
353
- headers: {}
354
- });
355
- }
356
- /**
357
- * Create a query builder for a table
358
- *
359
- * @example
360
- * // Basic query
361
- * const { data, error } = await client.database
362
- * .from('posts')
363
- * .select('*')
364
- * .eq('user_id', userId);
365
- *
366
- * // With count (Supabase style!)
367
- * const { data, error, count } = await client.database
368
- * .from('posts')
369
- * .select('*', { count: 'exact' })
370
- * .range(0, 9);
371
- *
372
- * // Just get count, no data
373
- * const { count } = await client.database
374
- * .from('posts')
375
- * .select('*', { count: 'exact', head: true });
376
- *
377
- * // Complex queries with OR
378
- * const { data } = await client.database
379
- * .from('posts')
380
- * .select('*, users!inner(*)')
381
- * .or('status.eq.active,status.eq.pending');
382
- *
383
- * // All features work:
384
- * - Nested selects
385
- * - Foreign key expansion
386
- * - OR/AND/NOT conditions
387
- * - Count with head
388
- * - Range pagination
389
- * - Upserts
390
- */
391
- from(table) {
392
- return this.postgrest.from(table);
393
- }
394
224
  };
395
225
 
396
226
  // src/modules/auth.ts
397
- function convertDbProfileToCamelCase(dbProfile) {
398
- const result = {
399
- id: dbProfile.id
400
- };
401
- Object.keys(dbProfile).forEach((key) => {
402
- result[key] = dbProfile[key];
403
- if (key.includes("_")) {
404
- const camelKey = key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
405
- result[camelKey] = dbProfile[key];
406
- }
407
- });
408
- return result;
409
- }
410
- function convertCamelCaseToDbProfile(profile) {
411
- const dbProfile = {};
412
- Object.keys(profile).forEach((key) => {
413
- if (profile[key] === void 0) return;
414
- const snakeKey = key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
415
- dbProfile[snakeKey] = profile[key];
416
- });
417
- return dbProfile;
418
- }
419
227
  function isHostedAuthEnvironment() {
420
228
  if (typeof window === "undefined") {
421
229
  return false;
@@ -433,74 +241,8 @@ var Auth = class {
433
241
  constructor(http, tokenManager) {
434
242
  this.http = http;
435
243
  this.tokenManager = tokenManager;
436
- this.database = new Database(http, tokenManager);
437
244
  this.detectAuthCallback();
438
245
  }
439
- /**
440
- * Restore session on app initialization
441
- *
442
- * @returns Object with isLoggedIn status
443
- *
444
- * @example
445
- * ```typescript
446
- * const client = new InsForgeClient({ baseUrl: '...' });
447
- * const { isLoggedIn } = await client.auth.restoreSession();
448
- *
449
- * if (isLoggedIn) {
450
- * const { data } = await client.auth.getCurrentUser();
451
- * }
452
- * ```
453
- */
454
- async restoreSession() {
455
- if (typeof window === "undefined") {
456
- return { isLoggedIn: false };
457
- }
458
- if (this.tokenManager.getAccessToken()) {
459
- return { isLoggedIn: true };
460
- }
461
- if (hasAuthCookie()) {
462
- try {
463
- const response = await this.http.post(
464
- "/api/auth/refresh"
465
- );
466
- if (response.accessToken) {
467
- this.tokenManager.setMemoryMode();
468
- this.tokenManager.setAccessToken(response.accessToken);
469
- this.http.setAuthToken(response.accessToken);
470
- if (response.user) {
471
- this.tokenManager.setUser(response.user);
472
- }
473
- return { isLoggedIn: true };
474
- }
475
- } catch (error) {
476
- if (error instanceof InsForgeError) {
477
- if (error.statusCode === 404) {
478
- this.tokenManager.setStorageMode();
479
- const token = this.tokenManager.getAccessToken();
480
- if (token) {
481
- this.http.setAuthToken(token);
482
- return { isLoggedIn: true };
483
- }
484
- return { isLoggedIn: false };
485
- }
486
- if (error.statusCode === 401 || error.statusCode === 403) {
487
- clearAuthCookie();
488
- return { isLoggedIn: false };
489
- }
490
- }
491
- return { isLoggedIn: false };
492
- }
493
- }
494
- if (this.tokenManager.hasStoredSession()) {
495
- this.tokenManager.setStorageMode();
496
- const token = this.tokenManager.getAccessToken();
497
- if (token) {
498
- this.http.setAuthToken(token);
499
- return { isLoggedIn: true };
500
- }
501
- }
502
- return { isLoggedIn: false };
503
- }
504
246
  /**
505
247
  * Automatically detect and handle OAuth callback parameters in the URL
506
248
  * This runs on initialization to seamlessly complete the OAuth flow
@@ -528,9 +270,8 @@ var Auth = class {
528
270
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
529
271
  }
530
272
  };
531
- this.http.setAuthToken(accessToken);
532
273
  this.tokenManager.saveSession(session);
533
- setAuthCookie();
274
+ this.http.setAuthToken(accessToken);
534
275
  const url = new URL(window.location.href);
535
276
  url.searchParams.delete("access_token");
536
277
  url.searchParams.delete("user_id");
@@ -551,13 +292,14 @@ var Auth = class {
551
292
  async signUp(request) {
552
293
  try {
553
294
  const response = await this.http.post("/api/auth/users", request);
554
- if (response.accessToken && response.user && !isHostedAuthEnvironment()) {
295
+ if (response.accessToken && response.user) {
555
296
  const session = {
556
297
  accessToken: response.accessToken,
557
298
  user: response.user
558
299
  };
559
- this.tokenManager.saveSession(session);
560
- setAuthCookie();
300
+ if (!isHostedAuthEnvironment()) {
301
+ this.tokenManager.saveSession(session);
302
+ }
561
303
  this.http.setAuthToken(response.accessToken);
562
304
  }
563
305
  return {
@@ -584,15 +326,21 @@ var Auth = class {
584
326
  async signInWithPassword(request) {
585
327
  try {
586
328
  const response = await this.http.post("/api/auth/sessions", request);
587
- if (response.accessToken && response.user && !isHostedAuthEnvironment()) {
588
- const session = {
589
- accessToken: response.accessToken,
590
- user: response.user
591
- };
329
+ const session = {
330
+ accessToken: response.accessToken || "",
331
+ user: response.user || {
332
+ id: "",
333
+ email: "",
334
+ name: "",
335
+ emailVerified: false,
336
+ createdAt: "",
337
+ updatedAt: ""
338
+ }
339
+ };
340
+ if (!isHostedAuthEnvironment()) {
592
341
  this.tokenManager.saveSession(session);
593
- setAuthCookie();
594
- this.http.setAuthToken(response.accessToken);
595
342
  }
343
+ this.http.setAuthToken(response.accessToken || "");
596
344
  return {
597
345
  data: response,
598
346
  error: null
@@ -650,13 +398,8 @@ var Auth = class {
650
398
  */
651
399
  async signOut() {
652
400
  try {
653
- try {
654
- await this.http.post("/api/auth/logout");
655
- } catch {
656
- }
657
401
  this.tokenManager.clearSession();
658
402
  this.http.setAuthToken(null);
659
- clearAuthCookie();
660
403
  return { error: null };
661
404
  } catch (error) {
662
405
  return {
@@ -717,14 +460,9 @@ var Auth = class {
717
460
  }
718
461
  this.http.setAuthToken(session.accessToken);
719
462
  const authResponse = await this.http.get("/api/auth/sessions/current");
720
- const { data: profile, error: profileError } = await this.database.from("users").select("*").eq("id", authResponse.user.id).single();
721
- if (profileError && profileError.code !== "PGRST116") {
722
- return { data: null, error: profileError };
723
- }
724
463
  return {
725
464
  data: {
726
- user: authResponse.user,
727
- profile: profile ? convertDbProfileToCamelCase(profile) : null
465
+ user: authResponse.user
728
466
  },
729
467
  error: null
730
468
  };
@@ -748,17 +486,28 @@ var Auth = class {
748
486
  }
749
487
  /**
750
488
  * Get any user's profile by ID
751
- * Returns profile information from the users table (dynamic fields)
489
+ * Returns profile information from the users table
752
490
  */
753
491
  async getProfile(userId) {
754
- const { data, error } = await this.database.from("users").select("*").eq("id", userId).single();
755
- if (error && error.code === "PGRST116") {
756
- return { data: null, error: null };
757
- }
758
- if (data) {
759
- return { data: convertDbProfileToCamelCase(data), error: null };
492
+ try {
493
+ const response = await this.http.get(`/api/auth/profiles/${userId}`);
494
+ return {
495
+ data: response,
496
+ error: null
497
+ };
498
+ } catch (error) {
499
+ if (error instanceof InsForgeError) {
500
+ return { data: null, error };
501
+ }
502
+ return {
503
+ data: null,
504
+ error: new InsForgeError(
505
+ "An unexpected error occurred while fetching user profile",
506
+ 500,
507
+ "UNEXPECTED_ERROR"
508
+ )
509
+ };
760
510
  }
761
- return { data: null, error };
762
511
  }
763
512
  /**
764
513
  * Get the current session (only session data, no API call)
@@ -789,42 +538,31 @@ var Auth = class {
789
538
  /**
790
539
  * Set/Update the current user's profile
791
540
  * Updates profile information in the users table (supports any dynamic fields)
541
+ * Requires authentication
792
542
  */
793
543
  async setProfile(profile) {
794
- const session = this.tokenManager.getSession();
795
- if (!session?.accessToken) {
544
+ try {
545
+ const response = await this.http.patch(
546
+ "/api/auth/profiles/current",
547
+ { profile }
548
+ );
549
+ return {
550
+ data: response,
551
+ error: null
552
+ };
553
+ } catch (error) {
554
+ if (error instanceof InsForgeError) {
555
+ return { data: null, error };
556
+ }
796
557
  return {
797
558
  data: null,
798
559
  error: new InsForgeError(
799
- "No authenticated user found",
800
- 401,
801
- "UNAUTHENTICATED"
560
+ "An unexpected error occurred while updating user profile",
561
+ 500,
562
+ "UNEXPECTED_ERROR"
802
563
  )
803
564
  };
804
565
  }
805
- if (!session.user?.id) {
806
- const { data: data2, error: error2 } = await this.getCurrentUser();
807
- if (error2) {
808
- return { data: null, error: error2 };
809
- }
810
- if (data2?.user) {
811
- session.user = {
812
- id: data2.user.id,
813
- email: data2.user.email,
814
- name: "",
815
- emailVerified: false,
816
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
817
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
818
- };
819
- this.tokenManager.saveSession(session);
820
- }
821
- }
822
- const dbProfile = convertCamelCaseToDbProfile(profile);
823
- const { data, error } = await this.database.from("users").update(dbProfile).eq("id", session.user.id).select().single();
824
- if (data) {
825
- return { data: convertDbProfileToCamelCase(data), error: null };
826
- }
827
- return { data: null, error };
828
566
  }
829
567
  /**
830
568
  * Send email verification (code or link based on config)
@@ -978,14 +716,13 @@ var Auth = class {
978
716
  "/api/auth/email/verify",
979
717
  request
980
718
  );
981
- if (response.accessToken && !isHostedAuthEnvironment()) {
719
+ if (response.accessToken) {
982
720
  const session = {
983
721
  accessToken: response.accessToken,
984
722
  user: response.user || {}
985
723
  };
986
724
  this.tokenManager.saveSession(session);
987
725
  this.http.setAuthToken(response.accessToken);
988
- setAuthCookie();
989
726
  }
990
727
  return {
991
728
  data: response,
@@ -1007,6 +744,75 @@ var Auth = class {
1007
744
  }
1008
745
  };
1009
746
 
747
+ // src/modules/database-postgrest.ts
748
+ var import_postgrest_js = require("@supabase/postgrest-js");
749
+ function createInsForgePostgrestFetch(httpClient, tokenManager) {
750
+ return async (input, init) => {
751
+ const url = typeof input === "string" ? input : input.toString();
752
+ const urlObj = new URL(url);
753
+ const tableName = urlObj.pathname.slice(1);
754
+ const insforgeUrl = `${httpClient.baseUrl}/api/database/records/${tableName}${urlObj.search}`;
755
+ const token = tokenManager.getAccessToken();
756
+ const httpHeaders = httpClient.getHeaders();
757
+ const authToken = token || httpHeaders["Authorization"]?.replace("Bearer ", "");
758
+ const headers = new Headers(init?.headers);
759
+ if (authToken && !headers.has("Authorization")) {
760
+ headers.set("Authorization", `Bearer ${authToken}`);
761
+ }
762
+ const response = await fetch(insforgeUrl, {
763
+ ...init,
764
+ headers
765
+ });
766
+ return response;
767
+ };
768
+ }
769
+ var Database = class {
770
+ constructor(httpClient, tokenManager) {
771
+ this.postgrest = new import_postgrest_js.PostgrestClient("http://dummy", {
772
+ fetch: createInsForgePostgrestFetch(httpClient, tokenManager),
773
+ headers: {}
774
+ });
775
+ }
776
+ /**
777
+ * Create a query builder for a table
778
+ *
779
+ * @example
780
+ * // Basic query
781
+ * const { data, error } = await client.database
782
+ * .from('posts')
783
+ * .select('*')
784
+ * .eq('user_id', userId);
785
+ *
786
+ * // With count (Supabase style!)
787
+ * const { data, error, count } = await client.database
788
+ * .from('posts')
789
+ * .select('*', { count: 'exact' })
790
+ * .range(0, 9);
791
+ *
792
+ * // Just get count, no data
793
+ * const { count } = await client.database
794
+ * .from('posts')
795
+ * .select('*', { count: 'exact', head: true });
796
+ *
797
+ * // Complex queries with OR
798
+ * const { data } = await client.database
799
+ * .from('posts')
800
+ * .select('*, users!inner(*)')
801
+ * .or('status.eq.active,status.eq.pending');
802
+ *
803
+ * // All features work:
804
+ * - Nested selects
805
+ * - Foreign key expansion
806
+ * - OR/AND/NOT conditions
807
+ * - Count with head
808
+ * - Range pagination
809
+ * - Upserts
810
+ */
811
+ from(table) {
812
+ return this.postgrest.from(table);
813
+ }
814
+ };
815
+
1010
816
  // src/modules/storage.ts
1011
817
  var StorageBucket = class {
1012
818
  constructor(bucketName, http) {
@@ -1528,6 +1334,239 @@ var Functions = class {
1528
1334
  }
1529
1335
  };
1530
1336
 
1337
+ // src/modules/realtime.ts
1338
+ var import_socket = require("socket.io-client");
1339
+ var CONNECT_TIMEOUT = 1e4;
1340
+ var Realtime = class {
1341
+ constructor(baseUrl, tokenManager) {
1342
+ this.socket = null;
1343
+ this.connectPromise = null;
1344
+ this.subscribedChannels = /* @__PURE__ */ new Set();
1345
+ this.eventListeners = /* @__PURE__ */ new Map();
1346
+ this.baseUrl = baseUrl;
1347
+ this.tokenManager = tokenManager;
1348
+ }
1349
+ notifyListeners(event, payload) {
1350
+ const listeners = this.eventListeners.get(event);
1351
+ if (!listeners) return;
1352
+ for (const cb of listeners) {
1353
+ try {
1354
+ cb(payload);
1355
+ } catch (err) {
1356
+ console.error(`Error in ${event} callback:`, err);
1357
+ }
1358
+ }
1359
+ }
1360
+ /**
1361
+ * Connect to the realtime server
1362
+ * @returns Promise that resolves when connected
1363
+ */
1364
+ connect() {
1365
+ if (this.socket?.connected) {
1366
+ return Promise.resolve();
1367
+ }
1368
+ if (this.connectPromise) {
1369
+ return this.connectPromise;
1370
+ }
1371
+ this.connectPromise = new Promise((resolve, reject) => {
1372
+ const session = this.tokenManager.getSession();
1373
+ const token = session?.accessToken;
1374
+ this.socket = (0, import_socket.io)(this.baseUrl, {
1375
+ transports: ["websocket"],
1376
+ auth: token ? { token } : void 0
1377
+ });
1378
+ let initialConnection = true;
1379
+ let timeoutId = null;
1380
+ const cleanup = () => {
1381
+ if (timeoutId) {
1382
+ clearTimeout(timeoutId);
1383
+ timeoutId = null;
1384
+ }
1385
+ };
1386
+ timeoutId = setTimeout(() => {
1387
+ if (initialConnection) {
1388
+ initialConnection = false;
1389
+ this.connectPromise = null;
1390
+ this.socket?.disconnect();
1391
+ this.socket = null;
1392
+ reject(new Error(`Connection timeout after ${CONNECT_TIMEOUT}ms`));
1393
+ }
1394
+ }, CONNECT_TIMEOUT);
1395
+ this.socket.on("connect", () => {
1396
+ cleanup();
1397
+ for (const channel of this.subscribedChannels) {
1398
+ this.socket.emit("realtime:subscribe", { channel });
1399
+ }
1400
+ this.notifyListeners("connect");
1401
+ if (initialConnection) {
1402
+ initialConnection = false;
1403
+ this.connectPromise = null;
1404
+ resolve();
1405
+ }
1406
+ });
1407
+ this.socket.on("connect_error", (error) => {
1408
+ cleanup();
1409
+ this.notifyListeners("connect_error", error);
1410
+ if (initialConnection) {
1411
+ initialConnection = false;
1412
+ this.connectPromise = null;
1413
+ reject(error);
1414
+ }
1415
+ });
1416
+ this.socket.on("disconnect", (reason) => {
1417
+ this.notifyListeners("disconnect", reason);
1418
+ });
1419
+ this.socket.on("realtime:error", (error) => {
1420
+ this.notifyListeners("error", error);
1421
+ });
1422
+ this.socket.onAny((event, message) => {
1423
+ if (event === "realtime:error") return;
1424
+ this.notifyListeners(event, message);
1425
+ });
1426
+ });
1427
+ return this.connectPromise;
1428
+ }
1429
+ /**
1430
+ * Disconnect from the realtime server
1431
+ */
1432
+ disconnect() {
1433
+ if (this.socket) {
1434
+ this.socket.disconnect();
1435
+ this.socket = null;
1436
+ }
1437
+ this.subscribedChannels.clear();
1438
+ }
1439
+ /**
1440
+ * Check if connected to the realtime server
1441
+ */
1442
+ get isConnected() {
1443
+ return this.socket?.connected ?? false;
1444
+ }
1445
+ /**
1446
+ * Get the current connection state
1447
+ */
1448
+ get connectionState() {
1449
+ if (!this.socket) return "disconnected";
1450
+ if (this.socket.connected) return "connected";
1451
+ return "connecting";
1452
+ }
1453
+ /**
1454
+ * Get the socket ID (if connected)
1455
+ */
1456
+ get socketId() {
1457
+ return this.socket?.id;
1458
+ }
1459
+ /**
1460
+ * Subscribe to a channel
1461
+ *
1462
+ * Automatically connects if not already connected.
1463
+ *
1464
+ * @param channel - Channel name (e.g., 'orders:123', 'broadcast')
1465
+ * @returns Promise with the subscription response
1466
+ */
1467
+ async subscribe(channel) {
1468
+ if (this.subscribedChannels.has(channel)) {
1469
+ return { ok: true, channel };
1470
+ }
1471
+ if (!this.socket?.connected) {
1472
+ try {
1473
+ await this.connect();
1474
+ } catch (error) {
1475
+ const message = error instanceof Error ? error.message : "Connection failed";
1476
+ return { ok: false, channel, error: { code: "CONNECTION_FAILED", message } };
1477
+ }
1478
+ }
1479
+ return new Promise((resolve) => {
1480
+ this.socket.emit("realtime:subscribe", { channel }, (response) => {
1481
+ if (response.ok) {
1482
+ this.subscribedChannels.add(channel);
1483
+ }
1484
+ resolve(response);
1485
+ });
1486
+ });
1487
+ }
1488
+ /**
1489
+ * Unsubscribe from a channel (fire-and-forget)
1490
+ *
1491
+ * @param channel - Channel name to unsubscribe from
1492
+ */
1493
+ unsubscribe(channel) {
1494
+ this.subscribedChannels.delete(channel);
1495
+ if (this.socket?.connected) {
1496
+ this.socket.emit("realtime:unsubscribe", { channel });
1497
+ }
1498
+ }
1499
+ /**
1500
+ * Publish a message to a channel
1501
+ *
1502
+ * @param channel - Channel name
1503
+ * @param event - Event name
1504
+ * @param payload - Message payload
1505
+ */
1506
+ async publish(channel, event, payload) {
1507
+ if (!this.socket?.connected) {
1508
+ throw new Error("Not connected to realtime server. Call connect() first.");
1509
+ }
1510
+ this.socket.emit("realtime:publish", { channel, event, payload });
1511
+ }
1512
+ /**
1513
+ * Listen for events
1514
+ *
1515
+ * Reserved event names:
1516
+ * - 'connect' - Fired when connected to the server
1517
+ * - 'connect_error' - Fired when connection fails (payload: Error)
1518
+ * - 'disconnect' - Fired when disconnected (payload: reason string)
1519
+ * - 'error' - Fired when a realtime error occurs (payload: RealtimeErrorPayload)
1520
+ *
1521
+ * All other events receive a `SocketMessage` payload with metadata.
1522
+ *
1523
+ * @param event - Event name to listen for
1524
+ * @param callback - Callback function when event is received
1525
+ */
1526
+ on(event, callback) {
1527
+ if (!this.eventListeners.has(event)) {
1528
+ this.eventListeners.set(event, /* @__PURE__ */ new Set());
1529
+ }
1530
+ this.eventListeners.get(event).add(callback);
1531
+ }
1532
+ /**
1533
+ * Remove a listener for a specific event
1534
+ *
1535
+ * @param event - Event name
1536
+ * @param callback - The callback function to remove
1537
+ */
1538
+ off(event, callback) {
1539
+ const listeners = this.eventListeners.get(event);
1540
+ if (listeners) {
1541
+ listeners.delete(callback);
1542
+ if (listeners.size === 0) {
1543
+ this.eventListeners.delete(event);
1544
+ }
1545
+ }
1546
+ }
1547
+ /**
1548
+ * Listen for an event only once, then automatically remove the listener
1549
+ *
1550
+ * @param event - Event name to listen for
1551
+ * @param callback - Callback function when event is received
1552
+ */
1553
+ once(event, callback) {
1554
+ const wrapper = (payload) => {
1555
+ this.off(event, wrapper);
1556
+ callback(payload);
1557
+ };
1558
+ this.on(event, wrapper);
1559
+ }
1560
+ /**
1561
+ * Get all currently subscribed channels
1562
+ *
1563
+ * @returns Array of channel names
1564
+ */
1565
+ getSubscribedChannels() {
1566
+ return Array.from(this.subscribedChannels);
1567
+ }
1568
+ };
1569
+
1531
1570
  // src/client.ts
1532
1571
  var InsForgeClient = class {
1533
1572
  constructor(config = {}) {
@@ -1545,11 +1584,15 @@ var InsForgeClient = class {
1545
1584
  if (existingSession?.accessToken) {
1546
1585
  this.http.setAuthToken(existingSession.accessToken);
1547
1586
  }
1548
- this.auth = new Auth(this.http, this.tokenManager);
1587
+ this.auth = new Auth(
1588
+ this.http,
1589
+ this.tokenManager
1590
+ );
1549
1591
  this.database = new Database(this.http, this.tokenManager);
1550
1592
  this.storage = new Storage(this.http);
1551
1593
  this.ai = new AI(this.http);
1552
1594
  this.functions = new Functions(this.http);
1595
+ this.realtime = new Realtime(this.http.baseUrl, this.tokenManager);
1553
1596
  }
1554
1597
  /**
1555
1598
  * Get the underlying HTTP client for custom requests
@@ -1587,6 +1630,7 @@ var index_default = InsForgeClient;
1587
1630
  HttpClient,
1588
1631
  InsForgeClient,
1589
1632
  InsForgeError,
1633
+ Realtime,
1590
1634
  Storage,
1591
1635
  StorageBucket,
1592
1636
  TokenManager,