@comergehq/studio 0.1.21 → 0.1.22

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
@@ -537,6 +537,95 @@ var BaseRepository = class {
537
537
  }
538
538
  };
539
539
 
540
+ // src/core/services/supabase/realtimeManager.ts
541
+ var INITIAL_BACKOFF_MS = 1e3;
542
+ var MAX_BACKOFF_MS = 3e4;
543
+ var realtimeLog = log.extend("realtime");
544
+ var entries = /* @__PURE__ */ new Map();
545
+ var subscriberIdCounter = 0;
546
+ function clearTimer(entry) {
547
+ if (!entry.timer) return;
548
+ clearTimeout(entry.timer);
549
+ entry.timer = null;
550
+ }
551
+ function buildChannel(entry) {
552
+ const supabase = getSupabaseClient();
553
+ const channel = supabase.channel(entry.key);
554
+ entry.subscribers.forEach((configure) => {
555
+ configure(channel);
556
+ });
557
+ return channel;
558
+ }
559
+ function scheduleResubscribe(entry, reason) {
560
+ if (entry.timer) return;
561
+ const delay = entry.backoffMs;
562
+ entry.backoffMs = Math.min(entry.backoffMs * 2, MAX_BACKOFF_MS);
563
+ realtimeLog.warn(`[realtime] channel ${entry.key} ${reason}; resubscribe in ${delay}ms`);
564
+ entry.timer = setTimeout(() => {
565
+ entry.timer = null;
566
+ if (!entries.has(entry.key)) return;
567
+ if (entry.subscribers.size === 0) return;
568
+ subscribeChannel(entry);
569
+ }, delay);
570
+ }
571
+ function handleStatus(entry, status) {
572
+ if (status === "SUBSCRIBED") {
573
+ entry.backoffMs = INITIAL_BACKOFF_MS;
574
+ clearTimer(entry);
575
+ return;
576
+ }
577
+ if (status === "CLOSED" || status === "TIMED_OUT" || status === "CHANNEL_ERROR") {
578
+ scheduleResubscribe(entry, status);
579
+ }
580
+ }
581
+ function subscribeChannel(entry) {
582
+ try {
583
+ const supabase = getSupabaseClient();
584
+ if (entry.channel) supabase.removeChannel(entry.channel);
585
+ const channel = buildChannel(entry);
586
+ entry.channel = channel;
587
+ channel.subscribe((status) => handleStatus(entry, status));
588
+ } catch (error) {
589
+ realtimeLog.warn("[realtime] subscribe failed", error);
590
+ scheduleResubscribe(entry, "SUBSCRIBE_FAILED");
591
+ }
592
+ }
593
+ function subscribeManagedChannel(key, configure) {
594
+ let entry = entries.get(key);
595
+ if (!entry) {
596
+ entry = {
597
+ key,
598
+ channel: null,
599
+ subscribers: /* @__PURE__ */ new Map(),
600
+ backoffMs: INITIAL_BACKOFF_MS,
601
+ timer: null
602
+ };
603
+ entries.set(key, entry);
604
+ }
605
+ const subscriberId = ++subscriberIdCounter;
606
+ entry.subscribers.set(subscriberId, configure);
607
+ if (!entry.channel) {
608
+ subscribeChannel(entry);
609
+ } else {
610
+ configure(entry.channel);
611
+ }
612
+ return () => {
613
+ const current = entries.get(key);
614
+ if (!current) return;
615
+ current.subscribers.delete(subscriberId);
616
+ if (current.subscribers.size === 0) {
617
+ clearTimer(current);
618
+ try {
619
+ if (current.channel) getSupabaseClient().removeChannel(current.channel);
620
+ } finally {
621
+ entries.delete(key);
622
+ }
623
+ return;
624
+ }
625
+ subscribeChannel(current);
626
+ };
627
+ }
628
+
540
629
  // src/data/apps/repository.ts
541
630
  function mapDbAppRow(row) {
542
631
  return {
@@ -618,35 +707,33 @@ var AppsRepositoryImpl = class extends BaseRepository {
618
707
  return this.subscribeToAppChannel(`apps:id:${appId}`, `id=eq.${appId}`, handlers);
619
708
  }
620
709
  subscribeToAppChannel(channelKey, filter, handlers) {
621
- const supabase = getSupabaseClient();
622
- const channel = supabase.channel(channelKey).on(
623
- "postgres_changes",
624
- { event: "INSERT", schema: "public", table: "app", filter },
625
- (payload) => {
626
- var _a;
627
- console.log("[subscribeToAppChannel] onInsert", payload);
628
- (_a = handlers.onInsert) == null ? void 0 : _a.call(handlers, mapDbAppRow(payload.new));
629
- }
630
- ).on(
631
- "postgres_changes",
632
- { event: "UPDATE", schema: "public", table: "app", filter },
633
- (payload) => {
634
- var _a;
635
- console.log("[subscribeToAppChannel] onUpdate", payload);
636
- (_a = handlers.onUpdate) == null ? void 0 : _a.call(handlers, mapDbAppRow(payload.new));
637
- }
638
- ).on(
639
- "postgres_changes",
640
- { event: "DELETE", schema: "public", table: "app", filter },
641
- (payload) => {
642
- var _a;
643
- console.log("[subscribeToAppChannel] onDelete", payload);
644
- (_a = handlers.onDelete) == null ? void 0 : _a.call(handlers, mapDbAppRow(payload.old));
645
- }
646
- ).subscribe();
647
- return () => {
648
- supabase.removeChannel(channel);
649
- };
710
+ return subscribeManagedChannel(channelKey, (channel) => {
711
+ channel.on(
712
+ "postgres_changes",
713
+ { event: "INSERT", schema: "public", table: "app", filter },
714
+ (payload) => {
715
+ var _a;
716
+ console.log("[subscribeToAppChannel] onInsert", payload);
717
+ (_a = handlers.onInsert) == null ? void 0 : _a.call(handlers, mapDbAppRow(payload.new));
718
+ }
719
+ ).on(
720
+ "postgres_changes",
721
+ { event: "UPDATE", schema: "public", table: "app", filter },
722
+ (payload) => {
723
+ var _a;
724
+ console.log("[subscribeToAppChannel] onUpdate", payload);
725
+ (_a = handlers.onUpdate) == null ? void 0 : _a.call(handlers, mapDbAppRow(payload.new));
726
+ }
727
+ ).on(
728
+ "postgres_changes",
729
+ { event: "DELETE", schema: "public", table: "app", filter },
730
+ (payload) => {
731
+ var _a;
732
+ console.log("[subscribeToAppChannel] onDelete", payload);
733
+ (_a = handlers.onDelete) == null ? void 0 : _a.call(handlers, mapDbAppRow(payload.old));
734
+ }
735
+ );
736
+ });
650
737
  }
651
738
  };
652
739
  var appsRepository = new AppsRepositoryImpl(appsRemoteDataSource);
@@ -777,35 +864,33 @@ var MessagesRepositoryImpl = class extends BaseRepository {
777
864
  return this.unwrapOrThrow(res);
778
865
  }
779
866
  subscribeThread(threadId, handlers) {
780
- const supabase = getSupabaseClient();
781
- const channel = supabase.channel(`messages:thread:${threadId}`).on(
782
- "postgres_changes",
783
- { event: "INSERT", schema: "public", table: "message", filter: `thread_id=eq.${threadId}` },
784
- (payload) => {
785
- var _a;
786
- const row = payload.new;
787
- (_a = handlers.onInsert) == null ? void 0 : _a.call(handlers, mapDbRowToMessage(row));
788
- }
789
- ).on(
790
- "postgres_changes",
791
- { event: "UPDATE", schema: "public", table: "message", filter: `thread_id=eq.${threadId}` },
792
- (payload) => {
793
- var _a;
794
- const row = payload.new;
795
- (_a = handlers.onUpdate) == null ? void 0 : _a.call(handlers, mapDbRowToMessage(row));
796
- }
797
- ).on(
798
- "postgres_changes",
799
- { event: "DELETE", schema: "public", table: "message", filter: `thread_id=eq.${threadId}` },
800
- (payload) => {
801
- var _a;
802
- const row = payload.old;
803
- (_a = handlers.onDelete) == null ? void 0 : _a.call(handlers, mapDbRowToMessage(row));
804
- }
805
- ).subscribe();
806
- return () => {
807
- supabase.removeChannel(channel);
808
- };
867
+ return subscribeManagedChannel(`messages:thread:${threadId}`, (channel) => {
868
+ channel.on(
869
+ "postgres_changes",
870
+ { event: "INSERT", schema: "public", table: "message", filter: `thread_id=eq.${threadId}` },
871
+ (payload) => {
872
+ var _a;
873
+ const row = payload.new;
874
+ (_a = handlers.onInsert) == null ? void 0 : _a.call(handlers, mapDbRowToMessage(row));
875
+ }
876
+ ).on(
877
+ "postgres_changes",
878
+ { event: "UPDATE", schema: "public", table: "message", filter: `thread_id=eq.${threadId}` },
879
+ (payload) => {
880
+ var _a;
881
+ const row = payload.new;
882
+ (_a = handlers.onUpdate) == null ? void 0 : _a.call(handlers, mapDbRowToMessage(row));
883
+ }
884
+ ).on(
885
+ "postgres_changes",
886
+ { event: "DELETE", schema: "public", table: "message", filter: `thread_id=eq.${threadId}` },
887
+ (payload) => {
888
+ var _a;
889
+ const row = payload.old;
890
+ (_a = handlers.onDelete) == null ? void 0 : _a.call(handlers, mapDbRowToMessage(row));
891
+ }
892
+ );
893
+ });
809
894
  }
810
895
  };
811
896
  var messagesRepository = new MessagesRepositoryImpl(messagesRemoteDataSource);
@@ -7357,42 +7442,40 @@ var EditQueueRepositoryImpl = class extends BaseRepository {
7357
7442
  return this.unwrapOrThrow(res);
7358
7443
  }
7359
7444
  subscribeEditQueue(appId, handlers) {
7360
- const supabase = getSupabaseClient();
7361
- const channel = supabase.channel(`edit-queue:app:${appId}`).on(
7362
- "postgres_changes",
7363
- { event: "INSERT", schema: "public", table: "app_job_queue", filter: `app_id=eq.${appId}` },
7364
- (payload) => {
7365
- var _a;
7366
- const row = payload.new;
7367
- if (row.kind !== "edit") return;
7368
- const item = mapQueueItem(row);
7369
- if (!ACTIVE_STATUSES.includes(item.status)) return;
7370
- (_a = handlers.onInsert) == null ? void 0 : _a.call(handlers, item);
7371
- }
7372
- ).on(
7373
- "postgres_changes",
7374
- { event: "UPDATE", schema: "public", table: "app_job_queue", filter: `app_id=eq.${appId}` },
7375
- (payload) => {
7376
- var _a, _b;
7377
- const row = payload.new;
7378
- if (row.kind !== "edit") return;
7379
- const item = mapQueueItem(row);
7380
- if (ACTIVE_STATUSES.includes(item.status)) (_a = handlers.onUpdate) == null ? void 0 : _a.call(handlers, item);
7381
- else (_b = handlers.onDelete) == null ? void 0 : _b.call(handlers, item);
7382
- }
7383
- ).on(
7384
- "postgres_changes",
7385
- { event: "DELETE", schema: "public", table: "app_job_queue", filter: `app_id=eq.${appId}` },
7386
- (payload) => {
7387
- var _a;
7388
- const row = payload.old;
7389
- if (row.kind !== "edit") return;
7390
- (_a = handlers.onDelete) == null ? void 0 : _a.call(handlers, mapQueueItem(row));
7391
- }
7392
- ).subscribe();
7393
- return () => {
7394
- supabase.removeChannel(channel);
7395
- };
7445
+ return subscribeManagedChannel(`edit-queue:app:${appId}`, (channel) => {
7446
+ channel.on(
7447
+ "postgres_changes",
7448
+ { event: "INSERT", schema: "public", table: "app_job_queue", filter: `app_id=eq.${appId}` },
7449
+ (payload) => {
7450
+ var _a;
7451
+ const row = payload.new;
7452
+ if (row.kind !== "edit") return;
7453
+ const item = mapQueueItem(row);
7454
+ if (!ACTIVE_STATUSES.includes(item.status)) return;
7455
+ (_a = handlers.onInsert) == null ? void 0 : _a.call(handlers, item);
7456
+ }
7457
+ ).on(
7458
+ "postgres_changes",
7459
+ { event: "UPDATE", schema: "public", table: "app_job_queue", filter: `app_id=eq.${appId}` },
7460
+ (payload) => {
7461
+ var _a, _b;
7462
+ const row = payload.new;
7463
+ if (row.kind !== "edit") return;
7464
+ const item = mapQueueItem(row);
7465
+ if (ACTIVE_STATUSES.includes(item.status)) (_a = handlers.onUpdate) == null ? void 0 : _a.call(handlers, item);
7466
+ else (_b = handlers.onDelete) == null ? void 0 : _b.call(handlers, item);
7467
+ }
7468
+ ).on(
7469
+ "postgres_changes",
7470
+ { event: "DELETE", schema: "public", table: "app_job_queue", filter: `app_id=eq.${appId}` },
7471
+ (payload) => {
7472
+ var _a;
7473
+ const row = payload.old;
7474
+ if (row.kind !== "edit") return;
7475
+ (_a = handlers.onDelete) == null ? void 0 : _a.call(handlers, mapQueueItem(row));
7476
+ }
7477
+ );
7478
+ });
7396
7479
  }
7397
7480
  };
7398
7481
  var editQueueRepository = new EditQueueRepositoryImpl(