@prometheus-ags/entity-graph-core 3.0.0-alpha.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/README.md +124 -0
- package/dist/{index.js → index.cjs} +376 -291
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +458 -127
- package/dist/index.d.ts +458 -127
- package/dist/index.mjs +371 -289
- package/dist/index.mjs.map +1 -1
- package/package.json +37 -13
- package/dist/index.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
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
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
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
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
s
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
s
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
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]
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
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
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
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
|
-
|
|
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
|
|
824
|
-
function
|
|
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
|
|
835
|
-
function
|
|
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 (
|
|
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
|
|
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
|
|
890
|
-
function runGarbageCollection() {
|
|
891
|
-
const store =
|
|
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
|
-
|
|
918
|
-
|
|
919
|
-
|
|
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
|
-
|
|
927
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
949
|
-
|
|
950
|
-
if (sideEffects) sideEffects(raw,
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
976
|
-
for (const { id } of normalized)
|
|
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)
|
|
980
|
-
else
|
|
981
|
-
if (sideEffects) sideEffects(response.items,
|
|
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
|
-
|
|
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
|
|
996
|
-
function
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
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 =
|
|
1002
|
-
for (const key of
|
|
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
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
1105
|
+
storeApi.getState().replaceEntity(type, id, data);
|
|
1036
1106
|
return tx;
|
|
1037
1107
|
},
|
|
1038
1108
|
removeEntity(type, id) {
|
|
1039
|
-
|
|
1109
|
+
storeApi.getState().removeEntity(type, id);
|
|
1040
1110
|
return tx;
|
|
1041
1111
|
},
|
|
1042
1112
|
patchEntity(type, id, patch) {
|
|
1043
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1127
|
+
storeApi.getState().insertIdInList(key, id, position);
|
|
1058
1128
|
return tx;
|
|
1059
1129
|
},
|
|
1060
1130
|
removeIdFromAllLists(type, id) {
|
|
1061
|
-
|
|
1131
|
+
storeApi.getState().removeIdFromAllLists(type, id);
|
|
1062
1132
|
return tx;
|
|
1063
1133
|
},
|
|
1064
1134
|
setEntitySyncMetadata(type, id, metadata) {
|
|
1065
|
-
|
|
1135
|
+
storeApi.getState().setEntitySyncMetadata(type, id, metadata);
|
|
1066
1136
|
return tx;
|
|
1067
1137
|
},
|
|
1068
1138
|
markEntityPending(type, id, origin = "optimistic") {
|
|
1069
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
|
1348
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1566
|
+
return graphSyncStatusStore.getState().status;
|
|
1494
1567
|
}
|
|
1495
1568
|
};
|
|
1496
1569
|
}
|
|
1497
|
-
function cloneGraphSnapshot() {
|
|
1498
|
-
const state =
|
|
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
|
|
1673
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
1601
1674
|
function dispatchToSubscribers(event) {
|
|
1602
|
-
const snap = [...
|
|
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
|
-
|
|
1751
|
+
subscribers.add(cb);
|
|
1679
1752
|
return () => {
|
|
1680
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
3878
|
-
//# sourceMappingURL=index.
|
|
3879
|
-
//# sourceMappingURL=index.js.map
|
|
3963
|
+
//# sourceMappingURL=index.cjs.map
|
|
3964
|
+
//# sourceMappingURL=index.cjs.map
|