@arcote.tech/arc 0.7.10 → 0.7.11

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
@@ -4597,6 +4597,7 @@ class StreamingQueryCache {
4597
4597
  views = [];
4598
4598
  activeStreams = new Map;
4599
4599
  pendingUnsubscribes = new Map;
4600
+ streamScopes = new Map;
4600
4601
  static UNSUBSCRIBE_DELAY_MS = 5000;
4601
4602
  registerViews(views) {
4602
4603
  this.views = views;
@@ -4655,6 +4656,7 @@ class StreamingQueryCache {
4655
4656
  if (current && current.refCount <= 0) {
4656
4657
  current.unsubscribe();
4657
4658
  this.activeStreams.delete(viewName);
4659
+ this.streamScopes.delete(viewName);
4658
4660
  }
4659
4661
  }, StreamingQueryCache.UNSUBSCRIBE_DELAY_MS);
4660
4662
  this.pendingUnsubscribes.set(viewName, timeout);
@@ -4662,6 +4664,8 @@ class StreamingQueryCache {
4662
4664
  }
4663
4665
  subscribeQuery(descriptor, eventWire, scope) {
4664
4666
  const key = descriptor.element;
4667
+ if (scope)
4668
+ this.streamScopes.set(key, scope);
4665
4669
  const { unsubscribe } = this.registerStream(key, () => {
4666
4670
  const subId = eventWire.subscribeQuery(descriptor, (data) => {
4667
4671
  this.setViewData(descriptor.element, data);
@@ -4670,6 +4674,28 @@ class StreamingQueryCache {
4670
4674
  });
4671
4675
  return unsubscribe;
4672
4676
  }
4677
+ invalidateScope(scope) {
4678
+ for (const [viewName, viewScope] of this.streamScopes) {
4679
+ if (viewScope !== scope)
4680
+ continue;
4681
+ const pending = this.pendingUnsubscribes.get(viewName);
4682
+ if (pending) {
4683
+ clearTimeout(pending);
4684
+ this.pendingUnsubscribes.delete(viewName);
4685
+ }
4686
+ const stream = this.activeStreams.get(viewName);
4687
+ if (stream) {
4688
+ try {
4689
+ stream.unsubscribe();
4690
+ } catch {}
4691
+ this.activeStreams.delete(viewName);
4692
+ }
4693
+ this.streamScopes.delete(viewName);
4694
+ const store = this.stores.get(viewName);
4695
+ if (store)
4696
+ store.clear();
4697
+ }
4698
+ }
4673
4699
  setViewData(viewName, data) {
4674
4700
  const store = this.stores.get(viewName);
4675
4701
  if (!store)
@@ -4717,6 +4743,11 @@ class StreamingQueryCache {
4717
4743
  stream.unsubscribe();
4718
4744
  }
4719
4745
  this.activeStreams.clear();
4746
+ this.streamScopes.clear();
4747
+ for (const timeout of this.pendingUnsubscribes.values()) {
4748
+ clearTimeout(timeout);
4749
+ }
4750
+ this.pendingUnsubscribes.clear();
4720
4751
  for (const store of this.stores.values()) {
4721
4752
  store.clear();
4722
4753
  }
@@ -38,6 +38,15 @@ export declare class StreamingQueryCache {
38
38
  private views;
39
39
  private activeStreams;
40
40
  private pendingUnsubscribes;
41
+ /**
42
+ * Tag each active stream with the scope name that subscribed it. Used by
43
+ * `invalidateScope()` to force-close streams when a scope's token changes
44
+ * (workspace switch / re-auth) — without this tag, `registerStream()` would
45
+ * reuse the stale WS subscription (refCount > 0, or within the
46
+ * UNSUBSCRIBE_DELAY_MS grace window) and the client would keep receiving
47
+ * data filtered by the previous token.
48
+ */
49
+ private streamScopes;
41
50
  private static UNSUBSCRIBE_DELAY_MS;
42
51
  /**
43
52
  * Register views that this cache will handle
@@ -83,6 +92,23 @@ export declare class StreamingQueryCache {
83
92
  method: string;
84
93
  args: any[];
85
94
  }, eventWire: EventWire, scope?: string): () => void;
95
+ /**
96
+ * Force-close every active stream tagged with `scope`. Called when a
97
+ * scope's token changes (workspace switch / re-auth) so the next
98
+ * `subscribeQuery()` creates a fresh WS subscription with the new token
99
+ * instead of reusing the stale one (which would keep pumping data filtered
100
+ * by the previous token until the page reload).
101
+ *
102
+ * Bypasses both `refCount` (other subscribers still mounted) and the
103
+ * UNSUBSCRIBE_DELAY_MS grace window — both became invalid the moment the
104
+ * token changed. React's `useQuery` re-subscribes immediately afterwards
105
+ * via the `subKey` change (token is in the key), getting a fresh stream.
106
+ *
107
+ * Bonus: each affected store is also cleared so any in-progress render
108
+ * that reads `store.find()` between `setToken` and the new WS data
109
+ * arriving gets `[]` rather than stale rows from the previous workspace.
110
+ */
111
+ invalidateScope(scope: string): void;
86
112
  /**
87
113
  * Set data for a view. Accepts array or single item (from queryMethod findOne).
88
114
  */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@arcote.tech/arc",
3
3
  "type": "module",
4
- "version": "0.7.10",
4
+ "version": "0.7.11",
5
5
  "private": false,
6
6
  "author": "Przemysław Krasiński [arcote.tech]",
7
7
  "description": "Arc framework core rewrite with improved event emission and type safety",