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