@prometheus-ags/entity-graph-core 3.0.0-alpha.0 → 3.0.0-rc.1

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
@@ -1,4 +1,4 @@
1
- import { create } from 'zustand';
1
+ import { createStore } from 'zustand/vanilla';
2
2
  import { subscribeWithSelector } from 'zustand/middleware';
3
3
  import { immer } from 'zustand/middleware/immer';
4
4
 
@@ -483,183 +483,187 @@ function readCachedEntitySnapshot(type, id, base, patch, metadata) {
483
483
  snapshotCache.set(key, { base, patch, metadata, snapshot });
484
484
  return snapshot;
485
485
  }
486
- var useGraphStore = create()(
487
- subscribeWithSelector(
488
- immer((set, get) => ({
489
- entities: {},
490
- patches: {},
491
- entityStates: {},
492
- syncMetadata: {},
493
- lists: {},
494
- upsertEntity: (type, id, data) => set((s) => {
495
- if (!s.entities[type]) s.entities[type] = {};
496
- const key = ek(type, id);
497
- const strategy = getMergeStrategy(type);
498
- const ctx = { type, id, origin: s.syncMetadata[key]?.origin ?? "server", updatedAt: Date.now() };
499
- s.entities[type][id] = strategy(s.entities[type][id], data, ctx);
500
- if (!s.syncMetadata[key]) s.syncMetadata[key] = defaultSyncMetadata();
501
- }),
502
- upsertEntities: (type, entries) => set((s) => {
503
- if (!s.entities[type]) s.entities[type] = {};
504
- const strategy = getMergeStrategy(type);
505
- for (const { id, data } of entries) {
486
+ function createGraphStore() {
487
+ return createStore()(
488
+ subscribeWithSelector(
489
+ immer((set, get) => ({
490
+ entities: {},
491
+ patches: {},
492
+ entityStates: {},
493
+ syncMetadata: {},
494
+ lists: {},
495
+ upsertEntity: (type, id, data) => set((s) => {
496
+ if (!s.entities[type]) s.entities[type] = {};
506
497
  const key = ek(type, id);
498
+ const strategy = getMergeStrategy(type);
507
499
  const ctx = { type, id, origin: s.syncMetadata[key]?.origin ?? "server", updatedAt: Date.now() };
508
500
  s.entities[type][id] = strategy(s.entities[type][id], data, ctx);
509
501
  if (!s.syncMetadata[key]) s.syncMetadata[key] = defaultSyncMetadata();
510
- }
511
- }),
512
- replaceEntity: (type, id, data) => set((s) => {
513
- if (!s.entities[type]) s.entities[type] = {};
514
- s.entities[type][id] = data;
515
- const key = ek(type, id);
516
- if (!s.syncMetadata[key]) s.syncMetadata[key] = defaultSyncMetadata();
517
- }),
518
- removeEntity: (type, id) => set((s) => {
519
- delete s.entities[type]?.[id];
520
- delete s.patches[type]?.[id];
521
- delete s.entityStates[ek(type, id)];
522
- delete s.syncMetadata[ek(type, id)];
523
- }),
524
- patchEntity: (type, id, patch) => set((s) => {
525
- if (!s.patches[type]) s.patches[type] = {};
526
- s.patches[type][id] = { ...s.patches[type][id] ?? {}, ...patch };
527
- }),
528
- unpatchEntity: (type, id, keys) => set((s) => {
529
- const p = s.patches[type]?.[id];
530
- if (!p) return;
531
- for (const k of keys) delete p[k];
532
- }),
533
- clearPatch: (type, id) => set((s) => {
534
- delete s.patches[type]?.[id];
535
- }),
536
- setEntityFetching: (type, id, fetching) => set((s) => {
537
- const k = ek(type, id);
538
- if (!s.entityStates[k]) s.entityStates[k] = defaultEntityState();
539
- s.entityStates[k].isFetching = fetching;
540
- }),
541
- setEntityError: (type, id, error) => set((s) => {
542
- const k = ek(type, id);
543
- if (!s.entityStates[k]) s.entityStates[k] = defaultEntityState();
544
- s.entityStates[k].error = error;
545
- s.entityStates[k].isFetching = false;
546
- }),
547
- setEntityFetched: (type, id) => set((s) => {
548
- const k = ek(type, id);
549
- if (!s.entityStates[k]) s.entityStates[k] = defaultEntityState();
550
- s.entityStates[k].lastFetched = Date.now();
551
- s.entityStates[k].isFetching = false;
552
- s.entityStates[k].error = null;
553
- s.entityStates[k].stale = false;
554
- s.syncMetadata[k] = { ...s.syncMetadata[k] ?? defaultSyncMetadata(), synced: true, origin: "server", updatedAt: Date.now() };
555
- }),
556
- setEntityStale: (type, id, stale) => set((s) => {
557
- const k = ek(type, id);
558
- if (!s.entityStates[k]) s.entityStates[k] = defaultEntityState();
559
- s.entityStates[k].stale = stale;
560
- }),
561
- setEntitySyncMetadata: (type, id, metadata) => set((s) => {
562
- const k = ek(type, id);
563
- s.syncMetadata[k] = { ...s.syncMetadata[k] ?? defaultSyncMetadata(), ...metadata };
564
- }),
565
- clearEntitySyncMetadata: (type, id) => set((s) => {
566
- delete s.syncMetadata[ek(type, id)];
567
- }),
568
- setListResult: (key, ids, meta) => set((s) => {
569
- const ex = s.lists[key] ?? defaultListState();
570
- s.lists[key] = { ...ex, ...meta, ids, isFetching: false, isFetchingMore: false, error: null, lastError: null, stale: false, lastFetched: Date.now() };
571
- }),
572
- appendListResult: (key, ids, meta) => set((s) => {
573
- const ex = s.lists[key] ?? defaultListState();
574
- s.lists[key] = { ...ex, ...meta, ids: Array.from(/* @__PURE__ */ new Set([...ex.ids, ...ids])), isFetching: false, isFetchingMore: false, error: null, lastError: null, stale: false, lastFetched: Date.now() };
575
- }),
576
- prependListResult: (key, ids, meta) => set((s) => {
577
- const ex = s.lists[key] ?? defaultListState();
578
- s.lists[key] = { ...ex, ...meta ?? {}, ids: Array.from(/* @__PURE__ */ new Set([...ids, ...ex.ids])), isFetching: false, isFetchingMore: false, error: null, lastError: null, stale: false, lastFetched: Date.now() };
579
- }),
580
- removeIdFromAllLists: (_type, id) => set((s) => {
581
- for (const key of Object.keys(s.lists)) {
582
- const list = s.lists[key];
583
- const idx = list.ids.indexOf(id);
584
- if (idx !== -1) {
585
- list.ids.splice(idx, 1);
586
- if (list.total !== null) list.total -= 1;
502
+ }),
503
+ upsertEntities: (type, entries) => set((s) => {
504
+ if (!s.entities[type]) s.entities[type] = {};
505
+ const strategy = getMergeStrategy(type);
506
+ for (const { id, data } of entries) {
507
+ const key = ek(type, id);
508
+ const ctx = { type, id, origin: s.syncMetadata[key]?.origin ?? "server", updatedAt: Date.now() };
509
+ s.entities[type][id] = strategy(s.entities[type][id], data, ctx);
510
+ if (!s.syncMetadata[key]) s.syncMetadata[key] = defaultSyncMetadata();
587
511
  }
588
- }
589
- }),
590
- insertIdInList: (key, id, position) => set((s) => {
591
- if (!s.lists[key]) s.lists[key] = defaultListState();
592
- const ids = s.lists[key].ids;
593
- const ex = ids.indexOf(id);
594
- if (ex !== -1) ids.splice(ex, 1);
595
- if (position === "start") ids.unshift(id);
596
- else if (position === "end") ids.push(id);
597
- else ids.splice(position, 0, id);
598
- }),
599
- setListFetching: (key, fetching) => set((s) => {
600
- if (!s.lists[key]) s.lists[key] = defaultListState();
601
- s.lists[key].isFetching = fetching;
602
- }),
603
- setListFetchingMore: (key, fetchingMore) => set((s) => {
604
- if (!s.lists[key]) s.lists[key] = defaultListState();
605
- s.lists[key].isFetchingMore = fetchingMore;
606
- }),
607
- setListError: (key, error, typed) => set((s) => {
608
- if (!s.lists[key]) s.lists[key] = defaultListState();
609
- s.lists[key].error = error;
610
- s.lists[key].lastError = typed ?? (error === null ? null : s.lists[key].lastError);
611
- s.lists[key].isFetching = false;
612
- s.lists[key].isFetchingMore = false;
613
- s.lists[key].lastFetched = Date.now();
614
- s.lists[key].stale = false;
615
- }),
616
- setListStale: (key, stale) => set((s) => {
617
- if (!s.lists[key]) s.lists[key] = defaultListState();
618
- s.lists[key].stale = stale;
619
- }),
620
- invalidateEntity: (type, id) => set((s) => {
621
- if (id) {
512
+ }),
513
+ replaceEntity: (type, id, data) => set((s) => {
514
+ if (!s.entities[type]) s.entities[type] = {};
515
+ s.entities[type][id] = data;
516
+ const key = ek(type, id);
517
+ if (!s.syncMetadata[key]) s.syncMetadata[key] = defaultSyncMetadata();
518
+ }),
519
+ removeEntity: (type, id) => set((s) => {
520
+ delete s.entities[type]?.[id];
521
+ delete s.patches[type]?.[id];
522
+ delete s.entityStates[ek(type, id)];
523
+ delete s.syncMetadata[ek(type, id)];
524
+ }),
525
+ patchEntity: (type, id, patch) => set((s) => {
526
+ if (!s.patches[type]) s.patches[type] = {};
527
+ s.patches[type][id] = { ...s.patches[type][id] ?? {}, ...patch };
528
+ }),
529
+ unpatchEntity: (type, id, keys) => set((s) => {
530
+ const p = s.patches[type]?.[id];
531
+ if (!p) return;
532
+ for (const k of keys) delete p[k];
533
+ }),
534
+ clearPatch: (type, id) => set((s) => {
535
+ delete s.patches[type]?.[id];
536
+ }),
537
+ setEntityFetching: (type, id, fetching) => set((s) => {
622
538
  const k = ek(type, id);
623
- if (s.entityStates[k]) s.entityStates[k].stale = true;
624
- } else {
625
- for (const k of Object.keys(s.entityStates)) if (k.startsWith(`${type}:`)) s.entityStates[k].stale = true;
626
- }
627
- }),
628
- invalidateLists: (matcher) => set((s) => {
629
- const pred = typeof matcher === "string" ? (k) => k.startsWith(matcher) : matcher;
630
- for (const key of Object.keys(s.lists)) if (pred(key)) s.lists[key].stale = true;
631
- }),
632
- invalidateType: (type) => {
633
- get().invalidateEntity(type);
634
- get().invalidateLists(type);
635
- },
636
- readEntity: (type, id) => {
637
- const s = get();
638
- const base = s.entities[type]?.[id];
639
- if (!base) return null;
640
- const patch = s.patches[type]?.[id];
641
- return patch ? { ...base, ...patch } : base;
642
- },
643
- readEntitySnapshot: (type, id) => {
644
- const s = get();
645
- const base = s.entities[type]?.[id];
646
- if (!base) {
647
- snapshotCache.delete(ek(type, id));
648
- return null;
539
+ if (!s.entityStates[k]) s.entityStates[k] = defaultEntityState();
540
+ s.entityStates[k].isFetching = fetching;
541
+ }),
542
+ setEntityError: (type, id, error) => set((s) => {
543
+ const k = ek(type, id);
544
+ if (!s.entityStates[k]) s.entityStates[k] = defaultEntityState();
545
+ s.entityStates[k].error = error;
546
+ s.entityStates[k].isFetching = false;
547
+ }),
548
+ setEntityFetched: (type, id) => set((s) => {
549
+ const k = ek(type, id);
550
+ if (!s.entityStates[k]) s.entityStates[k] = defaultEntityState();
551
+ s.entityStates[k].lastFetched = Date.now();
552
+ s.entityStates[k].isFetching = false;
553
+ s.entityStates[k].error = null;
554
+ s.entityStates[k].stale = false;
555
+ s.syncMetadata[k] = { ...s.syncMetadata[k] ?? defaultSyncMetadata(), synced: true, origin: "server", updatedAt: Date.now() };
556
+ }),
557
+ setEntityStale: (type, id, stale) => set((s) => {
558
+ const k = ek(type, id);
559
+ if (!s.entityStates[k]) s.entityStates[k] = defaultEntityState();
560
+ s.entityStates[k].stale = stale;
561
+ }),
562
+ setEntitySyncMetadata: (type, id, metadata) => set((s) => {
563
+ const k = ek(type, id);
564
+ s.syncMetadata[k] = { ...s.syncMetadata[k] ?? defaultSyncMetadata(), ...metadata };
565
+ }),
566
+ clearEntitySyncMetadata: (type, id) => set((s) => {
567
+ delete s.syncMetadata[ek(type, id)];
568
+ }),
569
+ setListResult: (key, ids, meta) => set((s) => {
570
+ const ex = s.lists[key] ?? defaultListState();
571
+ s.lists[key] = { ...ex, ...meta, ids, isFetching: false, isFetchingMore: false, error: null, lastError: null, stale: false, lastFetched: Date.now() };
572
+ }),
573
+ appendListResult: (key, ids, meta) => set((s) => {
574
+ const ex = s.lists[key] ?? defaultListState();
575
+ s.lists[key] = { ...ex, ...meta, ids: Array.from(/* @__PURE__ */ new Set([...ex.ids, ...ids])), isFetching: false, isFetchingMore: false, error: null, lastError: null, stale: false, lastFetched: Date.now() };
576
+ }),
577
+ prependListResult: (key, ids, meta) => set((s) => {
578
+ const ex = s.lists[key] ?? defaultListState();
579
+ s.lists[key] = { ...ex, ...meta ?? {}, ids: Array.from(/* @__PURE__ */ new Set([...ids, ...ex.ids])), isFetching: false, isFetchingMore: false, error: null, lastError: null, stale: false, lastFetched: Date.now() };
580
+ }),
581
+ removeIdFromAllLists: (_type, id) => set((s) => {
582
+ for (const key of Object.keys(s.lists)) {
583
+ const list = s.lists[key];
584
+ const idx = list.ids.indexOf(id);
585
+ if (idx !== -1) {
586
+ list.ids.splice(idx, 1);
587
+ if (list.total !== null) list.total -= 1;
588
+ }
589
+ }
590
+ }),
591
+ insertIdInList: (key, id, position) => set((s) => {
592
+ if (!s.lists[key]) s.lists[key] = defaultListState();
593
+ const ids = s.lists[key].ids;
594
+ const ex = ids.indexOf(id);
595
+ if (ex !== -1) ids.splice(ex, 1);
596
+ if (position === "start") ids.unshift(id);
597
+ else if (position === "end") ids.push(id);
598
+ else ids.splice(position, 0, id);
599
+ }),
600
+ setListFetching: (key, fetching) => set((s) => {
601
+ if (!s.lists[key]) s.lists[key] = defaultListState();
602
+ s.lists[key].isFetching = fetching;
603
+ }),
604
+ setListFetchingMore: (key, fetchingMore) => set((s) => {
605
+ if (!s.lists[key]) s.lists[key] = defaultListState();
606
+ s.lists[key].isFetchingMore = fetchingMore;
607
+ }),
608
+ setListError: (key, error, typed) => set((s) => {
609
+ if (!s.lists[key]) s.lists[key] = defaultListState();
610
+ s.lists[key].error = error;
611
+ s.lists[key].lastError = typed ?? (error === null ? null : s.lists[key].lastError);
612
+ s.lists[key].isFetching = false;
613
+ s.lists[key].isFetchingMore = false;
614
+ s.lists[key].lastFetched = Date.now();
615
+ s.lists[key].stale = false;
616
+ }),
617
+ setListStale: (key, stale) => set((s) => {
618
+ if (!s.lists[key]) s.lists[key] = defaultListState();
619
+ s.lists[key].stale = stale;
620
+ }),
621
+ invalidateEntity: (type, id) => set((s) => {
622
+ if (id) {
623
+ const k = ek(type, id);
624
+ if (s.entityStates[k]) s.entityStates[k].stale = true;
625
+ } else {
626
+ for (const k of Object.keys(s.entityStates)) if (k.startsWith(`${type}:`)) s.entityStates[k].stale = true;
627
+ }
628
+ }),
629
+ invalidateLists: (matcher) => set((s) => {
630
+ const pred = typeof matcher === "string" ? (k) => k.startsWith(matcher) : matcher;
631
+ for (const key of Object.keys(s.lists)) if (pred(key)) s.lists[key].stale = true;
632
+ }),
633
+ invalidateType: (type) => {
634
+ get().invalidateEntity(type);
635
+ get().invalidateLists(type);
636
+ },
637
+ readEntity: (type, id) => {
638
+ const s = get();
639
+ const base = s.entities[type]?.[id];
640
+ if (!base) return null;
641
+ const patch = s.patches[type]?.[id];
642
+ return patch ? { ...base, ...patch } : base;
643
+ },
644
+ readEntitySnapshot: (type, id) => {
645
+ const s = get();
646
+ const base = s.entities[type]?.[id];
647
+ if (!base) {
648
+ snapshotCache.delete(ek(type, id));
649
+ return null;
650
+ }
651
+ const patch = s.patches[type]?.[id];
652
+ const metadata = s.syncMetadata[ek(type, id)] ?? EMPTY_SYNC_METADATA;
653
+ return readCachedEntitySnapshot(
654
+ type,
655
+ id,
656
+ base,
657
+ patch,
658
+ metadata
659
+ );
649
660
  }
650
- const patch = s.patches[type]?.[id];
651
- const metadata = s.syncMetadata[ek(type, id)] ?? EMPTY_SYNC_METADATA;
652
- return readCachedEntitySnapshot(
653
- type,
654
- id,
655
- base,
656
- patch,
657
- metadata
658
- );
659
- }
660
- }))
661
- )
662
- );
661
+ }))
662
+ )
663
+ );
664
+ }
665
+ var graphStore = createGraphStore();
666
+ var useGraphStore = graphStore;
663
667
 
664
668
  // src/object-path.ts
665
669
  function isObject(value) {
@@ -708,13 +712,17 @@ var docs = /* @__PURE__ */ new Map();
708
712
  function ek2(type, id) {
709
713
  return `${type}:${id}`;
710
714
  }
711
- async function createLoroMergeStrategy() {
715
+ async function createLoroMergeStrategy(loadLoro) {
712
716
  let mod;
713
717
  try {
714
- mod = await import(
718
+ const loaded = loadLoro ? await loadLoro() : await import(
715
719
  /* @vite-ignore */
716
720
  'loro-crdt'
717
721
  );
722
+ if (typeof loaded.LoroDoc !== "function") {
723
+ throw new TypeError("loro-crdt did not export a LoroDoc constructor");
724
+ }
725
+ mod = loaded;
718
726
  } catch (cause) {
719
727
  throw new Error(
720
728
  "[merge/loro] createLoroMergeStrategy requires the optional peer dependency 'loro-crdt'. Install it with `pnpm add loro-crdt`.",
@@ -818,8 +826,17 @@ function serializeKey(key) {
818
826
  function sleep(ms) {
819
827
  return new Promise((r) => setTimeout(r, ms));
820
828
  }
821
- var inflight = /* @__PURE__ */ new Map();
822
- function dedupe(key, fn) {
829
+ var inflightByStore = /* @__PURE__ */ new WeakMap();
830
+ function inflightFor(store) {
831
+ let inflight = inflightByStore.get(store);
832
+ if (!inflight) {
833
+ inflight = /* @__PURE__ */ new Map();
834
+ inflightByStore.set(store, inflight);
835
+ }
836
+ return inflight;
837
+ }
838
+ function dedupe(key, fn, store = graphStore) {
839
+ const inflight = inflightFor(store);
823
840
  if (inflight.has(key)) return inflight.get(key);
824
841
  const p = fn().finally(() => inflight.delete(key));
825
842
  inflight.set(key, p);
@@ -829,23 +846,33 @@ var subscriberStatsListeners = /* @__PURE__ */ new Set();
829
846
  function emitSubscriberStatsChange() {
830
847
  for (const cb of subscriberStatsListeners) cb();
831
848
  }
832
- var subscribers = /* @__PURE__ */ new Map();
833
- function registerSubscriber(key) {
849
+ var subscribersByStore = /* @__PURE__ */ new WeakMap();
850
+ function subscribersFor(store) {
851
+ let subscribers = subscribersByStore.get(store);
852
+ if (!subscribers) {
853
+ subscribers = /* @__PURE__ */ new Map();
854
+ subscribersByStore.set(store, subscribers);
855
+ }
856
+ return subscribers;
857
+ }
858
+ function registerSubscriber(key, store = graphStore) {
859
+ const subscribers = subscribersFor(store);
834
860
  const token = Symbol(key);
835
861
  if (!subscribers.has(key)) subscribers.set(key, /* @__PURE__ */ new Set());
836
862
  subscribers.get(key).add(token);
837
863
  emitSubscriberStatsChange();
838
864
  return token;
839
865
  }
840
- function unregisterSubscriber(key, token) {
866
+ function unregisterSubscriber(key, token, store = graphStore) {
867
+ const subscribers = subscribersFor(store);
841
868
  const set = subscribers.get(key);
842
869
  if (!set) return;
843
870
  set.delete(token);
844
871
  if (set.size === 0) subscribers.delete(key);
845
872
  emitSubscriberStatsChange();
846
873
  }
847
- function hasSubscribers(key) {
848
- return (subscribers.get(key)?.size ?? 0) > 0;
874
+ function hasSubscribers(key, store = graphStore) {
875
+ return (subscribersFor(store).get(key)?.size ?? 0) > 0;
849
876
  }
850
877
  var DEFAULT_OPTIONS = {
851
878
  defaultStaleTime: 3e4,
@@ -861,9 +888,9 @@ function subscribeSubscriberStats(onChange) {
861
888
  subscriberStatsListeners.add(onChange);
862
889
  return () => subscriberStatsListeners.delete(onChange);
863
890
  }
864
- function getActiveSubscriberCount() {
891
+ function getActiveSubscriberCount(store = graphStore) {
865
892
  let n = 0;
866
- for (const set of subscribers.values()) n += set.size;
893
+ for (const set of subscribersFor(store).values()) n += set.size;
867
894
  return n;
868
895
  }
869
896
  var devtoolsSubscribers = /* @__PURE__ */ new Set();
@@ -884,9 +911,9 @@ function notifyDevtools(event) {
884
911
  }
885
912
  }
886
913
  }
887
- var gcIntervalId = null;
888
- function runGarbageCollection() {
889
- const store = useGraphStore.getState();
914
+ var gcIntervalsByStore = /* @__PURE__ */ new Map();
915
+ function runGarbageCollection(storeApi = graphStore) {
916
+ const store = storeApi.getState();
890
917
  const { defaultGcTime: gcTime } = getEngineOptions();
891
918
  const now = Date.now();
892
919
  const toRemove = [];
@@ -895,7 +922,7 @@ function runGarbageCollection() {
895
922
  if (!bucket) continue;
896
923
  for (const id of Object.keys(bucket)) {
897
924
  const key = `${type}:${id}`;
898
- if (hasSubscribers(key)) continue;
925
+ if (hasSubscribers(key, storeApi)) continue;
899
926
  const patch = store.patches[type]?.[id];
900
927
  if (patch !== void 0 && Object.keys(patch).length > 0) continue;
901
928
  const entityState = store.entityStates[key];
@@ -911,41 +938,48 @@ function runGarbageCollection() {
911
938
  store.removeIdFromAllLists(type, id);
912
939
  }
913
940
  }
914
- function stopGarbageCollector() {
915
- if (gcIntervalId != null && typeof clearInterval !== "undefined") {
916
- clearInterval(gcIntervalId);
917
- gcIntervalId = null;
941
+ function stopGarbageCollector(storeApi = graphStore) {
942
+ const intervalId = gcIntervalsByStore.get(storeApi);
943
+ if (intervalId != null && typeof clearInterval !== "undefined") {
944
+ clearInterval(intervalId);
945
+ gcIntervalsByStore.delete(storeApi);
918
946
  }
919
947
  }
920
- function startGarbageCollector() {
921
- stopGarbageCollector();
948
+ function startGarbageCollector(storeApi = graphStore) {
949
+ stopGarbageCollector(storeApi);
922
950
  if (typeof window === "undefined" || typeof setInterval === "undefined") return () => {
923
951
  };
924
- gcIntervalId = setInterval(() => runGarbageCollection(), getEngineOptions().gcInterval);
925
- return () => stopGarbageCollector();
952
+ const intervalId = setInterval(
953
+ () => runGarbageCollection(storeApi),
954
+ getEngineOptions().gcInterval
955
+ );
956
+ gcIntervalsByStore.set(storeApi, intervalId);
957
+ return () => stopGarbageCollector(storeApi);
926
958
  }
927
- function restartGarbageCollector() {
928
- startGarbageCollector();
959
+ function restartGarbageCollector(storeApi = graphStore) {
960
+ startGarbageCollector(storeApi);
929
961
  }
930
962
  function configureEngine(opts) {
931
963
  engineOptions = { ...DEFAULT_OPTIONS, ...opts };
932
- restartGarbageCollector();
964
+ const activeStores = [...gcIntervalsByStore.keys()];
965
+ if (activeStores.length === 0) restartGarbageCollector();
966
+ else for (const storeApi of activeStores) restartGarbageCollector(storeApi);
933
967
  }
934
968
  function getEngineOptions() {
935
969
  return engineOptions;
936
970
  }
937
- async function fetchEntity(opts, engineOpts) {
971
+ async function fetchEntity(opts, engineOpts, storeApi = graphStore) {
938
972
  const { type, id, fetch, normalize, sideEffects, idField = "id" } = opts;
939
973
  if (!id) return;
940
- useGraphStore.getState().setEntityFetching(type, id, true);
974
+ storeApi.getState().setEntityFetching(type, id, true);
941
975
  const attempt = async (retries) => {
942
976
  try {
943
977
  const raw = await fetch(id);
944
978
  const normalized = normalize(raw);
945
979
  const resolvedId = normalized[idField] ?? id;
946
- useGraphStore.getState().upsertEntity(type, resolvedId, normalized);
947
- useGraphStore.getState().setEntityFetched(type, resolvedId);
948
- if (sideEffects) sideEffects(raw, useGraphStore);
980
+ storeApi.getState().upsertEntity(type, resolvedId, normalized);
981
+ storeApi.getState().setEntityFetched(type, resolvedId);
982
+ if (sideEffects) sideEffects(raw, storeApi);
949
983
  opts.onSuccess?.(normalized);
950
984
  } catch (err) {
951
985
  const error = err instanceof Error ? err : new Error(String(err));
@@ -953,16 +987,16 @@ async function fetchEntity(opts, engineOpts) {
953
987
  await sleep(engineOpts.retryBaseDelay * Math.pow(2, retries));
954
988
  return attempt(retries + 1);
955
989
  }
956
- useGraphStore.getState().setEntityError(type, id, error.message);
990
+ storeApi.getState().setEntityError(type, id, error.message);
957
991
  opts.onError?.(error);
958
992
  }
959
993
  };
960
- await dedupe(`${type}:${id}`, () => attempt(0));
994
+ await dedupe(`${type}:${id}`, () => attempt(0), storeApi);
961
995
  }
962
- async function fetchList(opts, params, engineOpts, isLoadMore = false) {
996
+ async function fetchList(opts, params, engineOpts, isLoadMore = false, storeApi = graphStore) {
963
997
  const { type, queryKey, fetch, normalize, sideEffects, mode = "replace" } = opts;
964
998
  const key = serializeKey(queryKey);
965
- const store = useGraphStore.getState();
999
+ const store = storeApi.getState();
966
1000
  if (!fetch || !normalize) return;
967
1001
  if (isLoadMore) store.setListFetchingMore(key, true);
968
1002
  else store.setListFetching(key, true);
@@ -970,13 +1004,13 @@ async function fetchList(opts, params, engineOpts, isLoadMore = false) {
970
1004
  try {
971
1005
  const response = await fetch(params);
972
1006
  const normalized = response.items.map(normalize);
973
- useGraphStore.getState().upsertEntities(type, normalized.map(({ id, data }) => ({ id, data })));
974
- for (const { id } of normalized) useGraphStore.getState().setEntityFetched(type, id);
1007
+ storeApi.getState().upsertEntities(type, normalized.map(({ id, data }) => ({ id, data })));
1008
+ for (const { id } of normalized) storeApi.getState().setEntityFetched(type, id);
975
1009
  const ids = normalized.map(({ id }) => id);
976
1010
  const meta = { total: response.total ?? null, nextCursor: response.nextCursor ?? null, prevCursor: response.prevCursor ?? null, hasNextPage: response.hasNextPage ?? !!response.nextCursor, hasPrevPage: response.hasPrevPage ?? !!response.prevCursor, currentPage: response.page ?? null, pageSize: response.pageSize ?? null };
977
- if (mode === "append" && isLoadMore) useGraphStore.getState().appendListResult(key, ids, meta);
978
- else useGraphStore.getState().setListResult(key, ids, meta);
979
- if (sideEffects) sideEffects(response.items, useGraphStore);
1011
+ if (mode === "append" && isLoadMore) storeApi.getState().appendListResult(key, ids, meta);
1012
+ else storeApi.getState().setListResult(key, ids, meta);
1013
+ if (sideEffects) sideEffects(response.items, storeApi);
980
1014
  opts.onSuccess?.(response);
981
1015
  } catch (err) {
982
1016
  const error = err instanceof Error ? err : new Error(String(err));
@@ -984,21 +1018,39 @@ async function fetchList(opts, params, engineOpts, isLoadMore = false) {
984
1018
  await sleep(engineOpts.retryBaseDelay * Math.pow(2, retries));
985
1019
  return attempt(retries + 1);
986
1020
  }
987
- useGraphStore.getState().setListError(key, error.message);
1021
+ storeApi.getState().setListError(key, error.message);
988
1022
  opts.onError?.(error);
989
1023
  }
990
1024
  };
991
- await dedupe(isLoadMore ? `${key}:more` : key, () => attempt(0));
1025
+ await dedupe(isLoadMore ? `${key}:more` : key, () => attempt(0), storeApi);
992
1026
  }
993
- var focusListenerAttached = false;
994
- function attachGlobalListeners() {
995
- if (typeof window === "undefined" || focusListenerAttached) return;
996
- focusListenerAttached = true;
997
- restartGarbageCollector();
1027
+ var globalListenerAttachments = /* @__PURE__ */ new WeakMap();
1028
+ function releaseGlobalListenerAttachment(storeApi, attachment) {
1029
+ let released = false;
1030
+ return () => {
1031
+ if (released) return;
1032
+ released = true;
1033
+ const current = globalListenerAttachments.get(storeApi);
1034
+ if (current !== attachment) return;
1035
+ current.references -= 1;
1036
+ if (current.references > 0) return;
1037
+ current.dispose();
1038
+ globalListenerAttachments.delete(storeApi);
1039
+ };
1040
+ }
1041
+ function attachGlobalListeners(storeApi = graphStore) {
1042
+ if (typeof window === "undefined") return () => {
1043
+ };
1044
+ const existing = globalListenerAttachments.get(storeApi);
1045
+ if (existing) {
1046
+ existing.references += 1;
1047
+ return releaseGlobalListenerAttachment(storeApi, existing);
1048
+ }
1049
+ const stopGc = startGarbageCollector(storeApi);
998
1050
  const revalidateAll = () => {
999
- const state = useGraphStore.getState();
1000
- for (const key of subscribers.keys()) {
1001
- if (!hasSubscribers(key)) continue;
1051
+ const state = storeApi.getState();
1052
+ for (const key of subscribersFor(storeApi).keys()) {
1053
+ if (!hasSubscribers(key, storeApi)) continue;
1002
1054
  const colonIdx = key.indexOf(":");
1003
1055
  if (colonIdx === -1) continue;
1004
1056
  const type = key.slice(0, colonIdx);
@@ -1006,65 +1058,83 @@ function attachGlobalListeners() {
1006
1058
  state.setEntityStale(type, id, true);
1007
1059
  }
1008
1060
  };
1009
- if (engineOptions.revalidateOnFocus) {
1010
- window.addEventListener("visibilitychange", () => {
1011
- if (document.visibilityState === "visible") revalidateAll();
1012
- });
1061
+ const revalidateWhenVisible = () => {
1062
+ if (document.visibilityState === "visible") revalidateAll();
1063
+ };
1064
+ const revalidateOnFocus = engineOptions.revalidateOnFocus;
1065
+ const revalidateOnReconnect = engineOptions.revalidateOnReconnect;
1066
+ if (revalidateOnFocus) {
1067
+ window.addEventListener("visibilitychange", revalidateWhenVisible);
1013
1068
  window.addEventListener("focus", revalidateAll);
1014
1069
  }
1015
- if (engineOptions.revalidateOnReconnect) window.addEventListener("online", revalidateAll);
1070
+ if (revalidateOnReconnect) window.addEventListener("online", revalidateAll);
1071
+ const attachment = {
1072
+ references: 1,
1073
+ dispose: () => {
1074
+ if (revalidateOnFocus) {
1075
+ window.removeEventListener("visibilitychange", revalidateWhenVisible);
1076
+ window.removeEventListener("focus", revalidateAll);
1077
+ }
1078
+ if (revalidateOnReconnect) {
1079
+ window.removeEventListener("online", revalidateAll);
1080
+ }
1081
+ stopGc();
1082
+ }
1083
+ };
1084
+ globalListenerAttachments.set(storeApi, attachment);
1085
+ return releaseGlobalListenerAttachment(storeApi, attachment);
1016
1086
  }
1017
1087
 
1018
1088
  // src/graph-actions.ts
1019
1089
  var graphActionListeners = /* @__PURE__ */ new Set();
1020
1090
  var graphActionReplayers = /* @__PURE__ */ new Map();
1021
- function createGraphTransaction() {
1022
- const baseline = cloneGraphData();
1091
+ function createGraphTransaction(storeApi = graphStore) {
1092
+ const baseline = cloneGraphData(storeApi.getState());
1023
1093
  let closed = false;
1024
1094
  const tx = {
1025
1095
  upsertEntity(type, id, data) {
1026
- useGraphStore.getState().upsertEntity(type, id, data);
1096
+ storeApi.getState().upsertEntity(type, id, data);
1027
1097
  if (typeof process === "undefined" || process.env.NODE_ENV !== "production") {
1028
1098
  notifyDevtools({ kind: "upsert", type, id, data, at: (/* @__PURE__ */ new Date()).toISOString() });
1029
1099
  }
1030
1100
  return tx;
1031
1101
  },
1032
1102
  replaceEntity(type, id, data) {
1033
- useGraphStore.getState().replaceEntity(type, id, data);
1103
+ storeApi.getState().replaceEntity(type, id, data);
1034
1104
  return tx;
1035
1105
  },
1036
1106
  removeEntity(type, id) {
1037
- useGraphStore.getState().removeEntity(type, id);
1107
+ storeApi.getState().removeEntity(type, id);
1038
1108
  return tx;
1039
1109
  },
1040
1110
  patchEntity(type, id, patch) {
1041
- useGraphStore.getState().patchEntity(type, id, patch);
1111
+ storeApi.getState().patchEntity(type, id, patch);
1042
1112
  if (typeof process === "undefined" || process.env.NODE_ENV !== "production") {
1043
1113
  notifyDevtools({ kind: "patch", type, id, patch, at: (/* @__PURE__ */ new Date()).toISOString() });
1044
1114
  }
1045
1115
  return tx;
1046
1116
  },
1047
1117
  clearPatch(type, id) {
1048
- useGraphStore.getState().clearPatch(type, id);
1118
+ storeApi.getState().clearPatch(type, id);
1049
1119
  if (typeof process === "undefined" || process.env.NODE_ENV !== "production") {
1050
1120
  notifyDevtools({ kind: "clearPatch", type, id, at: (/* @__PURE__ */ new Date()).toISOString() });
1051
1121
  }
1052
1122
  return tx;
1053
1123
  },
1054
1124
  insertIdInList(key, id, position) {
1055
- useGraphStore.getState().insertIdInList(key, id, position);
1125
+ storeApi.getState().insertIdInList(key, id, position);
1056
1126
  return tx;
1057
1127
  },
1058
1128
  removeIdFromAllLists(type, id) {
1059
- useGraphStore.getState().removeIdFromAllLists(type, id);
1129
+ storeApi.getState().removeIdFromAllLists(type, id);
1060
1130
  return tx;
1061
1131
  },
1062
1132
  setEntitySyncMetadata(type, id, metadata) {
1063
- useGraphStore.getState().setEntitySyncMetadata(type, id, metadata);
1133
+ storeApi.getState().setEntitySyncMetadata(type, id, metadata);
1064
1134
  return tx;
1065
1135
  },
1066
1136
  markEntityPending(type, id, origin = "optimistic") {
1067
- useGraphStore.getState().setEntitySyncMetadata(type, id, {
1137
+ storeApi.getState().setEntitySyncMetadata(type, id, {
1068
1138
  synced: false,
1069
1139
  origin,
1070
1140
  updatedAt: Date.now()
@@ -1072,7 +1142,7 @@ function createGraphTransaction() {
1072
1142
  return tx;
1073
1143
  },
1074
1144
  markEntitySynced(type, id, origin = "server") {
1075
- useGraphStore.getState().setEntitySyncMetadata(type, id, {
1145
+ storeApi.getState().setEntitySyncMetadata(type, id, {
1076
1146
  synced: true,
1077
1147
  origin,
1078
1148
  updatedAt: Date.now()
@@ -1084,19 +1154,19 @@ function createGraphTransaction() {
1084
1154
  },
1085
1155
  rollback() {
1086
1156
  if (closed) return;
1087
- useGraphStore.setState(cloneGraphData(baseline));
1157
+ storeApi.setState(cloneGraphData(baseline));
1088
1158
  closed = true;
1089
1159
  },
1090
1160
  snapshot() {
1091
- return cloneGraphData();
1161
+ return cloneGraphData(storeApi.getState());
1092
1162
  }
1093
1163
  };
1094
1164
  return tx;
1095
1165
  }
1096
1166
  function createGraphAction(opts) {
1097
1167
  if (opts.key) {
1098
- graphActionReplayers.set(opts.key, async (record) => {
1099
- const tx = createGraphTransaction();
1168
+ graphActionReplayers.set(opts.key, async (record, storeApi) => {
1169
+ const tx = createGraphTransaction(storeApi);
1100
1170
  try {
1101
1171
  const result = await opts.run(tx, record.input);
1102
1172
  tx.commit();
@@ -1136,12 +1206,12 @@ function subscribeGraphActionEvents(listener) {
1136
1206
  graphActionListeners.add(listener);
1137
1207
  return () => graphActionListeners.delete(listener);
1138
1208
  }
1139
- async function replayRegisteredGraphAction(record) {
1209
+ async function replayRegisteredGraphAction(record, storeApi = graphStore) {
1140
1210
  const replayer = graphActionReplayers.get(record.key);
1141
1211
  if (!replayer) throw new Error(`No graph action registered for key "${record.key}"`);
1142
- return replayer(record);
1212
+ return replayer(record, storeApi);
1143
1213
  }
1144
- function cloneGraphData(source = useGraphStore.getState()) {
1214
+ function cloneGraphData(source = graphStore.getState()) {
1145
1215
  return {
1146
1216
  entities: structuredClone(source.entities),
1147
1217
  patches: structuredClone(source.patches),
@@ -1323,7 +1393,7 @@ function createSchemaGraphTool(handler) {
1323
1393
  });
1324
1394
  }
1325
1395
  var DEFAULT_STORAGE_KEY = "prometheus:graph";
1326
- var useGraphSyncStatusStore = create((set) => ({
1396
+ var graphSyncStatusStore = createStore()((set) => ({
1327
1397
  status: {
1328
1398
  phase: "idle",
1329
1399
  isOnline: true,
@@ -1342,19 +1412,20 @@ var useGraphSyncStatusStore = create((set) => ({
1342
1412
  }))
1343
1413
  }));
1344
1414
  var pendingActions = /* @__PURE__ */ new Map();
1345
- function useGraphSyncStatus() {
1346
- return useGraphSyncStatusStore((state) => state.status);
1415
+ function getGraphSyncStatus() {
1416
+ return graphSyncStatusStore.getState().status;
1347
1417
  }
1348
1418
  async function persistGraphToStorage(opts) {
1419
+ const storeApi = opts.store ?? graphStore;
1349
1420
  const payload = {
1350
1421
  version: 1,
1351
- snapshot: cloneGraphSnapshot(),
1422
+ snapshot: cloneGraphSnapshot(storeApi),
1352
1423
  pendingActions: opts.pendingActions ?? Array.from(pendingActions.values())
1353
1424
  };
1354
1425
  const json = JSON.stringify(payload);
1355
1426
  await opts.storage.set(opts.key, json);
1356
1427
  const persistedAt = (/* @__PURE__ */ new Date()).toISOString();
1357
- useGraphSyncStatusStore.getState().setStatus({
1428
+ graphSyncStatusStore.getState().setStatus({
1358
1429
  lastPersistedAt: persistedAt,
1359
1430
  storageKey: opts.key,
1360
1431
  pendingActions: payload.pendingActions.length
@@ -1367,6 +1438,7 @@ async function persistGraphToStorage(opts) {
1367
1438
  };
1368
1439
  }
1369
1440
  async function hydrateGraphFromStorage(opts) {
1441
+ const storeApi = opts.store ?? graphStore;
1370
1442
  const raw = await opts.storage.get(opts.key);
1371
1443
  if (!raw) {
1372
1444
  return {
@@ -1379,11 +1451,11 @@ async function hydrateGraphFromStorage(opts) {
1379
1451
  }
1380
1452
  try {
1381
1453
  const parsed = JSON.parse(raw);
1382
- useGraphStore.setState(parsed.snapshot);
1454
+ storeApi.setState(parsed.snapshot);
1383
1455
  pendingActions.clear();
1384
1456
  for (const action of parsed.pendingActions ?? []) pendingActions.set(action.id, action);
1385
1457
  const hydratedAt = (/* @__PURE__ */ new Date()).toISOString();
1386
- useGraphSyncStatusStore.getState().setStatus({
1458
+ graphSyncStatusStore.getState().setStatus({
1387
1459
  lastHydratedAt: hydratedAt,
1388
1460
  storageKey: opts.key,
1389
1461
  pendingActions: pendingActions.size,
@@ -1400,7 +1472,7 @@ async function hydrateGraphFromStorage(opts) {
1400
1472
  };
1401
1473
  } catch (error) {
1402
1474
  const message = error instanceof Error ? error.message : String(error);
1403
- useGraphSyncStatusStore.getState().setStatus({
1475
+ graphSyncStatusStore.getState().setStatus({
1404
1476
  phase: "error",
1405
1477
  error: message,
1406
1478
  storageKey: opts.key
@@ -1415,9 +1487,10 @@ async function hydrateGraphFromStorage(opts) {
1415
1487
  }
1416
1488
  }
1417
1489
  function startLocalFirstGraph(opts) {
1490
+ const storeApi = opts.store ?? graphStore;
1418
1491
  const key = opts.key ?? DEFAULT_STORAGE_KEY;
1419
1492
  const persistDebounceMs = opts.persistDebounceMs ?? 50;
1420
- const statusStore = useGraphSyncStatusStore.getState();
1493
+ const statusStore = graphSyncStatusStore.getState();
1421
1494
  statusStore.setStatus({
1422
1495
  phase: "hydrating",
1423
1496
  storageKey: key,
@@ -1429,16 +1502,16 @@ function startLocalFirstGraph(opts) {
1429
1502
  const schedulePersist = () => {
1430
1503
  if (persistTimer) clearTimeout(persistTimer);
1431
1504
  persistTimer = setTimeout(() => {
1432
- void persistGraphToStorage({ storage: opts.storage, key });
1505
+ void persistGraphToStorage({ storage: opts.storage, key, store: storeApi });
1433
1506
  }, persistDebounceMs);
1434
1507
  };
1435
- const graphUnsub = useGraphStore.subscribe(() => {
1508
+ const graphUnsub = storeApi.subscribe(() => {
1436
1509
  schedulePersist();
1437
1510
  });
1438
1511
  const actionUnsub = subscribeGraphActionEvents((event) => {
1439
1512
  if (event.type === "enqueued") pendingActions.set(event.record.id, event.record);
1440
1513
  if (event.type === "settled") pendingActions.delete(event.record.id);
1441
- useGraphSyncStatusStore.getState().setStatus({
1514
+ graphSyncStatusStore.getState().setStatus({
1442
1515
  pendingActions: pendingActions.size,
1443
1516
  isSynced: pendingActions.size === 0
1444
1517
  });
@@ -1446,27 +1519,27 @@ function startLocalFirstGraph(opts) {
1446
1519
  });
1447
1520
  const onlineSource = opts.onlineSource ?? getDefaultOnlineSource();
1448
1521
  const onlineUnsub = onlineSource.subscribe((online) => {
1449
- useGraphSyncStatusStore.getState().setStatus({
1522
+ graphSyncStatusStore.getState().setStatus({
1450
1523
  isOnline: online,
1451
1524
  phase: online ? "ready" : "offline"
1452
1525
  });
1453
1526
  });
1454
1527
  const ready = (async () => {
1455
- const hydrated = await hydrateGraphFromStorage({ storage: opts.storage, key });
1528
+ const hydrated = await hydrateGraphFromStorage({ storage: opts.storage, key, store: storeApi });
1456
1529
  if (opts.replayPendingActions && hydrated.ok && pendingActions.size > 0) {
1457
- useGraphSyncStatusStore.getState().setStatus({
1530
+ graphSyncStatusStore.getState().setStatus({
1458
1531
  phase: "syncing",
1459
1532
  isSynced: false
1460
1533
  });
1461
1534
  const policy = resolveRetryPolicy(opts.retryPolicy);
1462
1535
  for (const action of Array.from(pendingActions.values())) {
1463
- await replayActionWithRetry(action, policy);
1536
+ await replayActionWithRetry(action, policy, storeApi);
1464
1537
  pendingActions.delete(action.id);
1465
1538
  }
1466
- await persistGraphToStorage({ storage: opts.storage, key });
1539
+ await persistGraphToStorage({ storage: opts.storage, key, store: storeApi });
1467
1540
  }
1468
1541
  const online = onlineSource.getIsOnline();
1469
- useGraphSyncStatusStore.getState().setStatus({
1542
+ graphSyncStatusStore.getState().setStatus({
1470
1543
  phase: online ? "ready" : "offline",
1471
1544
  isOnline: online,
1472
1545
  isSynced: pendingActions.size === 0,
@@ -1482,18 +1555,18 @@ function startLocalFirstGraph(opts) {
1482
1555
  if (persistTimer) clearTimeout(persistTimer);
1483
1556
  },
1484
1557
  async persistNow() {
1485
- await persistGraphToStorage({ storage: opts.storage, key });
1558
+ await persistGraphToStorage({ storage: opts.storage, key, store: storeApi });
1486
1559
  },
1487
1560
  hydrate() {
1488
- return hydrateGraphFromStorage({ storage: opts.storage, key });
1561
+ return hydrateGraphFromStorage({ storage: opts.storage, key, store: storeApi });
1489
1562
  },
1490
1563
  getStatus() {
1491
- return useGraphSyncStatusStore.getState().status;
1564
+ return graphSyncStatusStore.getState().status;
1492
1565
  }
1493
1566
  };
1494
1567
  }
1495
- function cloneGraphSnapshot() {
1496
- const state = useGraphStore.getState();
1568
+ function cloneGraphSnapshot(storeApi) {
1569
+ const state = storeApi.getState();
1497
1570
  return {
1498
1571
  entities: structuredClone(state.entities),
1499
1572
  patches: structuredClone(state.patches),
@@ -1531,11 +1604,11 @@ function sleep2(ms) {
1531
1604
  if (ms <= 0) return Promise.resolve();
1532
1605
  return new Promise((r) => setTimeout(r, ms));
1533
1606
  }
1534
- async function replayActionWithRetry(action, policy) {
1607
+ async function replayActionWithRetry(action, policy, storeApi = graphStore) {
1535
1608
  let lastError = null;
1536
1609
  for (let attempt = 1; attempt <= policy.maxAttempts; attempt++) {
1537
1610
  try {
1538
- await replayRegisteredGraphAction(action);
1611
+ await replayRegisteredGraphAction(action, storeApi);
1539
1612
  return { ok: true };
1540
1613
  } catch (error) {
1541
1614
  lastError = error;
@@ -1595,9 +1668,9 @@ function createDevtoolsEventBus(opts) {
1595
1668
  if (count < bufferSize) return buffer.slice(0, count);
1596
1669
  return [...buffer.slice(head), ...buffer.slice(0, head)];
1597
1670
  }
1598
- const subscribers2 = /* @__PURE__ */ new Set();
1671
+ const subscribers = /* @__PURE__ */ new Set();
1599
1672
  function dispatchToSubscribers(event) {
1600
- const snap = [...subscribers2];
1673
+ const snap = [...subscribers];
1601
1674
  for (const cb of snap) {
1602
1675
  try {
1603
1676
  cb(event);
@@ -1673,9 +1746,9 @@ function createDevtoolsEventBus(opts) {
1673
1746
  console.warn("[devtools-event-bus] subscriber threw during replay:", err);
1674
1747
  }
1675
1748
  }
1676
- subscribers2.add(cb);
1749
+ subscribers.add(cb);
1677
1750
  return () => {
1678
- subscribers2.delete(cb);
1751
+ subscribers.delete(cb);
1679
1752
  };
1680
1753
  },
1681
1754
  getBuffer,
@@ -1687,7 +1760,7 @@ function createDevtoolsEventBus(opts) {
1687
1760
  if (destroyed) return;
1688
1761
  destroyed = true;
1689
1762
  unsubscribeEngine();
1690
- subscribers2.clear();
1763
+ subscribers.clear();
1691
1764
  pendingBurst = [];
1692
1765
  dispatchedCount = 0;
1693
1766
  flushScheduled = false;
@@ -1753,8 +1826,8 @@ function configureTimeTravel(opts) {
1753
1826
  if (ring.length > capacity) ring = ring.slice(-capacity);
1754
1827
  notify();
1755
1828
  }
1756
- function recordGraphSnapshot(label) {
1757
- const s = useGraphStore.getState();
1829
+ function recordGraphSnapshot(label, storeApi = graphStore) {
1830
+ const s = storeApi.getState();
1758
1831
  const snap = {
1759
1832
  seq: seqCounter++,
1760
1833
  at: Date.now(),
@@ -1767,23 +1840,23 @@ function recordGraphSnapshot(label) {
1767
1840
  notify();
1768
1841
  return snap.seq;
1769
1842
  }
1770
- function restoreGraphSnapshot(index) {
1843
+ function restoreGraphSnapshot(index, storeApi = graphStore) {
1771
1844
  const snap = ring[index];
1772
1845
  if (!snap) return false;
1773
1846
  const clone2 = cloneSlice(snap.data);
1774
- useGraphStore.setState(clone2);
1847
+ storeApi.setState(clone2);
1775
1848
  cursor = index;
1776
1849
  notify();
1777
1850
  return true;
1778
1851
  }
1779
- function restoreGraphSnapshotBySeq(seq) {
1852
+ function restoreGraphSnapshotBySeq(seq, storeApi = graphStore) {
1780
1853
  const index = ring.findIndex((s) => s.seq === seq);
1781
- return index === -1 ? false : restoreGraphSnapshot(index);
1854
+ return index === -1 ? false : restoreGraphSnapshot(index, storeApi);
1782
1855
  }
1783
- function stepTimeTravel(delta) {
1856
+ function stepTimeTravel(delta, storeApi = graphStore) {
1784
1857
  const base = cursor ?? ring.length - 1;
1785
1858
  const target = Math.max(0, Math.min(ring.length - 1, base + delta));
1786
- return restoreGraphSnapshot(target);
1859
+ return restoreGraphSnapshot(target, storeApi);
1787
1860
  }
1788
1861
  function getTimeTravelState() {
1789
1862
  return {
@@ -2026,10 +2099,10 @@ function registerSchema(schema) {
2026
2099
  function getSchema(type) {
2027
2100
  return schemaRegistry2.get(type) ?? null;
2028
2101
  }
2029
- function cascadeInvalidation(ctx) {
2102
+ function cascadeInvalidation(ctx, storeApi = graphStore) {
2030
2103
  const schema = schemaRegistry2.get(ctx.type);
2031
2104
  if (!schema) return;
2032
- const store = useGraphStore.getState();
2105
+ const store = storeApi.getState();
2033
2106
  if (schema.globalListKeys) for (const key of schema.globalListKeys) store.invalidateLists(key);
2034
2107
  if (!schema.relations) return;
2035
2108
  for (const [, relation] of Object.entries(schema.relations)) {
@@ -2062,10 +2135,10 @@ function cascadeInvalidation(ctx) {
2062
2135
  }
2063
2136
  }
2064
2137
  }
2065
- function readRelations(type, entity) {
2138
+ function readRelations(type, entity, storeApi = graphStore) {
2066
2139
  const schema = schemaRegistry2.get(type);
2067
2140
  if (!schema?.relations) return {};
2068
- const store = useGraphStore.getState();
2141
+ const store = storeApi.getState();
2069
2142
  const result = {};
2070
2143
  for (const [name, relation] of Object.entries(schema.relations)) {
2071
2144
  switch (relation.cardinality) {
@@ -2281,7 +2354,9 @@ var RealtimeManager = class {
2281
2354
  pendingListKeys = /* @__PURE__ */ new Set();
2282
2355
  flushTimer = null;
2283
2356
  opts;
2357
+ store;
2284
2358
  constructor(opts = {}) {
2359
+ this.store = opts.store ?? graphStore;
2285
2360
  this.opts = {
2286
2361
  flushInterval: opts.flushInterval ?? 16,
2287
2362
  onStatusChange: opts.onStatusChange ?? (() => {
@@ -2341,7 +2416,7 @@ var RealtimeManager = class {
2341
2416
  const changes = this.pendingChanges.splice(0);
2342
2417
  const listKeys = new Set(this.pendingListKeys);
2343
2418
  this.pendingListKeys.clear();
2344
- const store = useGraphStore.getState();
2419
+ const store = this.store.getState();
2345
2420
  for (const change of coalesceChanges(changes)) {
2346
2421
  switch (change.op) {
2347
2422
  case "insert":
@@ -2390,6 +2465,13 @@ function coalesceChanges(changes) {
2390
2465
  continue;
2391
2466
  }
2392
2467
  if (existing.op === "delete") continue;
2468
+ if (existing.op === "update" && c.op === "update") {
2469
+ byKey.set(key, {
2470
+ ...existing,
2471
+ patch: { ...existing.patch ?? {}, ...c.patch ?? {} }
2472
+ });
2473
+ continue;
2474
+ }
2393
2475
  byKey.set(key, { ...existing, op: "upsert", data: c.data ?? existing.data, patch: c.patch ? { ...existing.patch ?? {}, ...c.patch } : existing.patch });
2394
2476
  }
2395
2477
  return Array.from(byKey.values());
@@ -3753,6 +3835,6 @@ function getFacetedRowModel(preFilteredRowModel, columnId, allFilteredRowModel)
3753
3835
  return preFilteredRowModel;
3754
3836
  }
3755
3837
 
3756
- export { EMPTY_ENTITY_STATE, EMPTY_IDS, EMPTY_LIST_STATE, EMPTY_SYNC_METADATA, IncrementalView, RealtimeManager, TerminalError, TransientError, __resetEntityTransports, __resetStoreRegistry, applyAgUiDelta, applyAgUiSnapshot, applyJsonPatch, applyView, attachGlobalListeners, buildTenantWhere, cascadeInvalidation, checkCompleteness, collectDirtyPaths, compareEntities, configureEngine, configureTimeTravel, createConvexAdapter, createDevtoolsEventBus, createElectricAdapter, createFlintAdapter, createGraphAction, createGraphEffect, createGraphQLSubscriptionAdapter, createGraphTool, createGraphTransaction, createLoroMergeStrategy, createPGlitePersistenceAdapter, createRow, createSchemaGraphTool, createSupabaseRealtimeAdapter, createSurrealLiveAdapter, createTauriSqlPersistenceAdapter, createTenantScopedElectricAdapter, createWebSocketAdapter, dedupe, escapeHtml, exportGraphSnapshot, exportGraphSnapshotWithSchemas, fetchEntity, fetchList, findInsertionIndex, flattenClauses, getActiveSubscriberCount, getCoreRowModel, getRealtimeManager as getElectricRealtimeManager, getEngineOptions, getEntityJsonSchema, getEntityTransport, getExpandedRowModel, getFacetedMinMaxValues, getFacetedRowModel, getFacetedUniqueValues, getFilteredRowModel, getGroupedRowModel, getMergeStrategy, getPaginatedRowModel, getRealtimeManager, getRegisteredEntityTypes, getRegisteredStores, getSchema, getSelectedRowModel, getSortedRowModel, getTimeTravelState, getValueAtPath, hasCustomPredicates, hasMergeStrategy, humanizeFieldName, hydrateGraphFromStorage, inferSchemaFieldType, lwwStrategy, makeRestTransport, matchesFilter, matchesSearch, parseCreateTable, persistGraphToStorage, publishFlintMutation, queryOnce, readRelations, recordGraphSnapshot, registerEntityFromSql, registerEntityJsonSchema, registerEntityTransport, registerMergeStrategy, registerRuntimeSchema, registerSchema, registerStore, registerSubscriber, registryKey, renderMarkdownToHtml, replayActionWithRetry, resetRealtimeManager, restoreGraphSnapshot, restoreGraphSnapshotBySeq, selectGraph, serializeKey, setDefaultMergeStrategy, setValueAtPath, sqlTypeToJsonSchema, startGarbageCollector, startLocalFirstGraph, stepTimeTravel, stopGarbageCollector, subscribeDevtoolsEvent, subscribeSubscriberStats, subscribeTimeTravel, toEntityError, toGraphQLVariables, toPrismaOrderBy, toPrismaWhere, toRestParams, toSQLClauses, unregisterSubscriber, useGraphStore, useGraphSyncStatus };
3838
+ export { EMPTY_ENTITY_STATE, EMPTY_IDS, EMPTY_LIST_STATE, EMPTY_SYNC_METADATA, IncrementalView, RealtimeManager, TerminalError, TransientError, __resetEntityTransports, __resetStoreRegistry, applyAgUiDelta, applyAgUiSnapshot, applyJsonPatch, applyView, attachGlobalListeners, buildTenantWhere, cascadeInvalidation, checkCompleteness, collectDirtyPaths, compareEntities, configureEngine, configureTimeTravel, createConvexAdapter, createDevtoolsEventBus, createElectricAdapter, createFlintAdapter, createGraphAction, createGraphEffect, createGraphQLSubscriptionAdapter, createGraphStore, createGraphTool, createGraphTransaction, createLoroMergeStrategy, createPGlitePersistenceAdapter, createRow, createSchemaGraphTool, createSupabaseRealtimeAdapter, createSurrealLiveAdapter, createTauriSqlPersistenceAdapter, createTenantScopedElectricAdapter, createWebSocketAdapter, dedupe, escapeHtml, exportGraphSnapshot, exportGraphSnapshotWithSchemas, fetchEntity, fetchList, findInsertionIndex, flattenClauses, getActiveSubscriberCount, getCoreRowModel, getRealtimeManager as getElectricRealtimeManager, getEngineOptions, getEntityJsonSchema, getEntityTransport, getExpandedRowModel, getFacetedMinMaxValues, getFacetedRowModel, getFacetedUniqueValues, getFilteredRowModel, getGraphSyncStatus, getGroupedRowModel, getMergeStrategy, getPaginatedRowModel, getRealtimeManager, getRegisteredEntityTypes, getRegisteredStores, getSchema, getSelectedRowModel, getSortedRowModel, getTimeTravelState, getValueAtPath, graphStore, graphSyncStatusStore, hasCustomPredicates, hasMergeStrategy, humanizeFieldName, hydrateGraphFromStorage, inferSchemaFieldType, lwwStrategy, makeRestTransport, matchesFilter, matchesSearch, parseCreateTable, persistGraphToStorage, publishFlintMutation, queryOnce, readRelations, recordGraphSnapshot, registerEntityFromSql, registerEntityJsonSchema, registerEntityTransport, registerMergeStrategy, registerRuntimeSchema, registerSchema, registerStore, registerSubscriber, registryKey, renderMarkdownToHtml, replayActionWithRetry, resetRealtimeManager, restoreGraphSnapshot, restoreGraphSnapshotBySeq, selectGraph, serializeKey, setDefaultMergeStrategy, setValueAtPath, sqlTypeToJsonSchema, startGarbageCollector, startLocalFirstGraph, stepTimeTravel, stopGarbageCollector, subscribeDevtoolsEvent, subscribeSubscriberStats, subscribeTimeTravel, toEntityError, toGraphQLVariables, toPrismaOrderBy, toPrismaWhere, toRestParams, toSQLClauses, unregisterSubscriber, useGraphStore };
3757
3839
  //# sourceMappingURL=index.mjs.map
3758
3840
  //# sourceMappingURL=index.mjs.map