@mearie/core 0.6.1 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +759 -755
- package/dist/index.mjs +759 -755
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -589,7 +589,7 @@ const normalize = (schemaMeta, selections, storage, data, variables, accessor) =
|
|
|
589
589
|
|
|
590
590
|
//#endregion
|
|
591
591
|
//#region src/cache/denormalize.ts
|
|
592
|
-
const typenameFieldKey = makeFieldKey({
|
|
592
|
+
const typenameFieldKey$1 = makeFieldKey({
|
|
593
593
|
kind: "Field",
|
|
594
594
|
name: "__typename",
|
|
595
595
|
type: "String"
|
|
@@ -604,7 +604,7 @@ const denormalize = (selections, storage, value, variables, accessor, options) =
|
|
|
604
604
|
const entityKey = data[EntityLinkKey];
|
|
605
605
|
const entity = storage[entityKey];
|
|
606
606
|
if (!entity) {
|
|
607
|
-
accessor?.(entityKey, typenameFieldKey, path);
|
|
607
|
+
accessor?.(entityKey, typenameFieldKey$1, path);
|
|
608
608
|
partial = true;
|
|
609
609
|
return null;
|
|
610
610
|
}
|
|
@@ -651,7 +651,7 @@ const denormalize = (selections, storage, value, variables, accessor, options) =
|
|
|
651
651
|
if (denormalize(selection.selections, storage, storage[RootFieldKey], variables, options?.trackFragmentDeps === false ? void 0 : accessor, options).partial) partial = true;
|
|
652
652
|
}
|
|
653
653
|
} else mergeFields(fields, denormalizeField(storageKey, selection.selections, value, path), true);
|
|
654
|
-
else if (selection.kind === "InlineFragment" && selection.on === data[typenameFieldKey]) mergeFields(fields, denormalizeField(storageKey, selection.selections, value, path), true);
|
|
654
|
+
else if (selection.kind === "InlineFragment" && selection.on === data[typenameFieldKey$1]) mergeFields(fields, denormalizeField(storageKey, selection.selections, value, path), true);
|
|
655
655
|
return fields;
|
|
656
656
|
};
|
|
657
657
|
return {
|
|
@@ -661,159 +661,146 @@ const denormalize = (selections, storage, value, variables, accessor, options) =
|
|
|
661
661
|
};
|
|
662
662
|
|
|
663
663
|
//#endregion
|
|
664
|
-
//#region src/cache/
|
|
664
|
+
//#region src/cache/cursor.ts
|
|
665
665
|
/**
|
|
666
|
+
* Reverse index mapping dependency keys to cursor entries.
|
|
666
667
|
* @internal
|
|
667
668
|
*/
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
for (const element of path) {
|
|
676
|
-
const key = String(element);
|
|
677
|
-
let child = current.children.get(key);
|
|
678
|
-
if (!child) {
|
|
679
|
-
child = {
|
|
680
|
-
depKey: "",
|
|
681
|
-
children: /* @__PURE__ */ new Map()
|
|
682
|
-
};
|
|
683
|
-
current.children.set(key, child);
|
|
684
|
-
}
|
|
685
|
-
current = child;
|
|
669
|
+
var CursorRegistry = class {
|
|
670
|
+
#index = /* @__PURE__ */ new Map();
|
|
671
|
+
add(depKey, entry) {
|
|
672
|
+
let set = this.#index.get(depKey);
|
|
673
|
+
if (!set) {
|
|
674
|
+
set = /* @__PURE__ */ new Set();
|
|
675
|
+
this.#index.set(depKey, set);
|
|
686
676
|
}
|
|
687
|
-
|
|
688
|
-
if (selections) current.selections = selections;
|
|
677
|
+
set.add(entry);
|
|
689
678
|
}
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
/**
|
|
693
|
-
* @internal
|
|
694
|
-
*/
|
|
695
|
-
const findEntryTreeNode = (root, path) => {
|
|
696
|
-
let current = root;
|
|
697
|
-
for (const segment of path) {
|
|
698
|
-
if (!current) return void 0;
|
|
699
|
-
current = current.children.get(String(segment));
|
|
679
|
+
get(depKey) {
|
|
680
|
+
return this.#index.get(depKey);
|
|
700
681
|
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
* are cleaned up atomically to avoid stale references.
|
|
707
|
-
* @internal
|
|
708
|
-
*/
|
|
709
|
-
const removeSubtreeEntries = (node, subscription, subscriptions) => {
|
|
710
|
-
const entries = subscriptions.get(node.depKey);
|
|
711
|
-
if (entries) {
|
|
712
|
-
for (const entry of entries) if (entry.subscription === subscription) {
|
|
713
|
-
entries.delete(entry);
|
|
714
|
-
break;
|
|
682
|
+
remove(depKey, entry) {
|
|
683
|
+
const set = this.#index.get(depKey);
|
|
684
|
+
if (set) {
|
|
685
|
+
set.delete(entry);
|
|
686
|
+
if (set.size === 0) this.#index.delete(depKey);
|
|
715
687
|
}
|
|
716
|
-
if (entries.size === 0) subscriptions.delete(node.depKey);
|
|
717
688
|
}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
if (fields) result.set(fieldName, fields[fieldKey]);
|
|
689
|
+
removeAll(cursors) {
|
|
690
|
+
for (const [depKey, set] of this.#index) {
|
|
691
|
+
for (const cursor of cursors) if (set.has(cursor)) set.delete(cursor);
|
|
692
|
+
if (set.size === 0) this.#index.delete(depKey);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
forEachByPrefix(prefix, callback) {
|
|
696
|
+
for (const [depKey, set] of this.#index) if (depKey.startsWith(prefix)) for (const entry of set) callback(entry);
|
|
697
|
+
}
|
|
698
|
+
clear() {
|
|
699
|
+
this.#index.clear();
|
|
730
700
|
}
|
|
731
|
-
return result;
|
|
732
701
|
};
|
|
702
|
+
const typenameFieldKey = makeFieldKey({
|
|
703
|
+
kind: "Field",
|
|
704
|
+
name: "__typename",
|
|
705
|
+
type: "String"
|
|
706
|
+
}, {});
|
|
733
707
|
/**
|
|
708
|
+
* Walks selections against storage to produce cursor entries, check completeness,
|
|
709
|
+
* and build denormalized data.
|
|
734
710
|
* @internal
|
|
735
711
|
*/
|
|
736
|
-
const
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
rebuiltDepKeys.add(depKey);
|
|
755
|
-
const relativePath = tuple.path.slice(basePath.length);
|
|
756
|
-
let current = node;
|
|
757
|
-
for (const element of relativePath) {
|
|
758
|
-
const key = String(element);
|
|
759
|
-
let child = current.children.get(key);
|
|
760
|
-
if (!child) {
|
|
761
|
-
child = {
|
|
762
|
-
depKey: "",
|
|
763
|
-
children: /* @__PURE__ */ new Map()
|
|
764
|
-
};
|
|
765
|
-
current.children.set(key, child);
|
|
712
|
+
const traceSelections = (selections, storage, value, variables, storageKey, basePath, subscriptionId) => {
|
|
713
|
+
const cursors = [];
|
|
714
|
+
const missingDeps = /* @__PURE__ */ new Set();
|
|
715
|
+
let complete = true;
|
|
716
|
+
const traceField = (sk, sels, val, path, trackCursors) => {
|
|
717
|
+
if (isNullish(val)) return val;
|
|
718
|
+
if (Array.isArray(val)) return val.map((item, i) => traceField(sk, sels, item, [...path, i], trackCursors));
|
|
719
|
+
const data = val;
|
|
720
|
+
if (isEntityLink(data)) {
|
|
721
|
+
const entityKey = data[EntityLinkKey];
|
|
722
|
+
const entity = storage[entityKey];
|
|
723
|
+
if (!entity) {
|
|
724
|
+
if (trackCursors) {
|
|
725
|
+
const depKey = makeDependencyKey(entityKey, typenameFieldKey);
|
|
726
|
+
missingDeps.add(depKey);
|
|
727
|
+
}
|
|
728
|
+
complete = false;
|
|
729
|
+
return null;
|
|
766
730
|
}
|
|
767
|
-
|
|
731
|
+
return traceField(entityKey, sels, entity, path, trackCursors);
|
|
768
732
|
}
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
733
|
+
const fields = {};
|
|
734
|
+
for (const selection of sels) if (selection.kind === "Field") {
|
|
735
|
+
const fieldKey = makeFieldKey(selection, variables);
|
|
736
|
+
const fieldValue = data[fieldKey];
|
|
737
|
+
const fieldPath = [...path, selection.alias ?? selection.name];
|
|
738
|
+
if (sk !== null && trackCursors) {
|
|
739
|
+
const depKey = makeDependencyKey(sk, fieldKey);
|
|
740
|
+
const entry = {
|
|
741
|
+
subscriptionId,
|
|
742
|
+
path: fieldPath,
|
|
743
|
+
...selection.selections && { selections: selection.selections }
|
|
744
|
+
};
|
|
745
|
+
cursors.push({
|
|
746
|
+
depKey,
|
|
747
|
+
entry
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
if (fieldValue === void 0) {
|
|
751
|
+
if (sk !== null) {
|
|
752
|
+
const depKey = makeDependencyKey(sk, fieldKey);
|
|
753
|
+
missingDeps.add(depKey);
|
|
754
|
+
}
|
|
755
|
+
complete = false;
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
const name = selection.alias ?? selection.name;
|
|
759
|
+
const resolvedValue = selection.selections ? traceField(null, selection.selections, fieldValue, fieldPath, trackCursors) : fieldValue;
|
|
760
|
+
if (name in fields) mergeFields(fields, { [name]: resolvedValue }, true);
|
|
761
|
+
else fields[name] = resolvedValue;
|
|
762
|
+
} else if (selection.kind === "FragmentSpread") if (sk !== null && sk !== RootFieldKey) {
|
|
763
|
+
fields[FragmentRefKey] = sk;
|
|
764
|
+
const merged = selection.args ? {
|
|
765
|
+
...variables,
|
|
766
|
+
...resolveArguments(selection.args, variables)
|
|
767
|
+
} : { ...variables };
|
|
768
|
+
fields[FragmentVarsKey] = {
|
|
769
|
+
...fields[FragmentVarsKey],
|
|
770
|
+
[selection.name]: merged
|
|
771
|
+
};
|
|
772
|
+
const inner = traceSelections(selection.selections, storage, storage[sk], variables, sk, path, subscriptionId);
|
|
773
|
+
if (!inner.complete) {
|
|
774
|
+
complete = false;
|
|
775
|
+
for (const dep of inner.missingDeps) missingDeps.add(dep);
|
|
776
|
+
}
|
|
777
|
+
} else if (sk === RootFieldKey) {
|
|
778
|
+
fields[FragmentRefKey] = RootFieldKey;
|
|
779
|
+
const merged = selection.args ? {
|
|
780
|
+
...variables,
|
|
781
|
+
...resolveArguments(selection.args, variables)
|
|
782
|
+
} : { ...variables };
|
|
783
|
+
fields[FragmentVarsKey] = {
|
|
784
|
+
...fields[FragmentVarsKey],
|
|
785
|
+
[selection.name]: merged
|
|
786
|
+
};
|
|
787
|
+
const inner = traceSelections(selection.selections, storage, storage[RootFieldKey], variables, RootFieldKey, path, subscriptionId);
|
|
788
|
+
if (!inner.complete) {
|
|
789
|
+
complete = false;
|
|
790
|
+
for (const dep of inner.missingDeps) missingDeps.add(dep);
|
|
791
|
+
}
|
|
792
|
+
} else mergeFields(fields, traceField(sk, selection.selections, val, path, trackCursors), true);
|
|
793
|
+
else if (selection.kind === "InlineFragment" && selection.on === data[typenameFieldKey]) mergeFields(fields, traceField(sk, selection.selections, val, path, trackCursors), true);
|
|
794
|
+
return fields;
|
|
795
|
+
};
|
|
796
|
+
const data = traceField(storageKey, selections, value, basePath, true);
|
|
786
797
|
return {
|
|
787
|
-
|
|
788
|
-
|
|
798
|
+
complete,
|
|
799
|
+
cursors,
|
|
800
|
+
missingDeps,
|
|
801
|
+
data
|
|
789
802
|
};
|
|
790
803
|
};
|
|
791
|
-
const updateSubtreePaths = (node, basePath, newIndex, baseLen, subscription, subscriptions) => {
|
|
792
|
-
const entries = subscriptions.get(node.depKey);
|
|
793
|
-
if (entries) {
|
|
794
|
-
for (const entry of entries) if (entry.subscription === subscription && entry.path.length > baseLen) entry.path = [
|
|
795
|
-
...basePath,
|
|
796
|
-
newIndex,
|
|
797
|
-
...entry.path.slice(baseLen + 1)
|
|
798
|
-
];
|
|
799
|
-
}
|
|
800
|
-
for (const child of node.children.values()) updateSubtreePaths(child, basePath, newIndex, baseLen, subscription, subscriptions);
|
|
801
|
-
};
|
|
802
|
-
/**
|
|
803
|
-
* @internal
|
|
804
|
-
*/
|
|
805
|
-
const rebuildArrayIndices = (node, entry, subscriptions) => {
|
|
806
|
-
const basePath = entry.path;
|
|
807
|
-
const baseLen = basePath.length;
|
|
808
|
-
const children = [...node.children.entries()].toSorted(([a], [b]) => Number(a) - Number(b));
|
|
809
|
-
node.children.clear();
|
|
810
|
-
for (const [newIdx, child_] of children.entries()) {
|
|
811
|
-
const [, child] = child_;
|
|
812
|
-
const newKey = String(newIdx);
|
|
813
|
-
node.children.set(newKey, child);
|
|
814
|
-
updateSubtreePaths(child, basePath, newIdx, baseLen, entry.subscription, subscriptions);
|
|
815
|
-
}
|
|
816
|
-
};
|
|
817
804
|
|
|
818
805
|
//#endregion
|
|
819
806
|
//#region src/cache/diff.ts
|
|
@@ -855,27 +842,139 @@ const computeSwaps = (oldKeys, newKeys) => {
|
|
|
855
842
|
}
|
|
856
843
|
return swaps;
|
|
857
844
|
};
|
|
845
|
+
/**
|
|
846
|
+
* @internal
|
|
847
|
+
*/
|
|
848
|
+
const extractEntityKey = (item) => {
|
|
849
|
+
if (item !== null && item !== void 0 && typeof item === "object" && EntityLinkKey in item) return String(item[EntityLinkKey]);
|
|
850
|
+
return null;
|
|
851
|
+
};
|
|
852
|
+
/**
|
|
853
|
+
* @internal
|
|
854
|
+
*/
|
|
855
|
+
const computeEntityArrayPatches = (oldValue, newValue, path, denormalizedArray) => {
|
|
856
|
+
const patches = [];
|
|
857
|
+
const oldKeys = oldValue.map((item) => extractEntityKey(item));
|
|
858
|
+
const newKeys = newValue.map((item) => extractEntityKey(item));
|
|
859
|
+
const { start, oldEnd, newEnd } = findCommonBounds(oldKeys, newKeys);
|
|
860
|
+
const oldMiddle = oldKeys.slice(start, oldEnd);
|
|
861
|
+
const newMiddle = newKeys.slice(start, newEnd);
|
|
862
|
+
const oldMiddleSet = new Set(oldMiddle.filter((k) => k !== null));
|
|
863
|
+
const newMiddleSet = new Set(newMiddle.filter((k) => k !== null));
|
|
864
|
+
for (let i = oldMiddle.length - 1; i >= 0; i--) if (oldMiddle[i] !== null && !newMiddleSet.has(oldMiddle[i])) patches.push({
|
|
865
|
+
type: "splice",
|
|
866
|
+
path,
|
|
867
|
+
index: start + i,
|
|
868
|
+
deleteCount: 1,
|
|
869
|
+
items: []
|
|
870
|
+
});
|
|
871
|
+
const retainedOld = oldMiddle.filter((k) => k !== null && newMiddleSet.has(k));
|
|
872
|
+
const retainedNew = newMiddle.filter((k) => k !== null && oldMiddleSet.has(k));
|
|
873
|
+
if (retainedOld.length > 0) {
|
|
874
|
+
const swaps = computeSwaps(retainedOld, retainedNew);
|
|
875
|
+
for (const { i, j } of swaps) patches.push({
|
|
876
|
+
type: "swap",
|
|
877
|
+
path,
|
|
878
|
+
i: start + i,
|
|
879
|
+
j: start + j
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
const addedKeys = newMiddle.filter((k) => k !== null && !oldMiddleSet.has(k));
|
|
883
|
+
for (const key of addedKeys) {
|
|
884
|
+
const idx = start + newMiddle.indexOf(key);
|
|
885
|
+
const data = denormalizedArray[idx] ?? null;
|
|
886
|
+
patches.push({
|
|
887
|
+
type: "splice",
|
|
888
|
+
path,
|
|
889
|
+
index: idx,
|
|
890
|
+
deleteCount: 0,
|
|
891
|
+
items: [data]
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
return patches;
|
|
895
|
+
};
|
|
896
|
+
const pathToKey = (path) => path.map(String).join("\0");
|
|
897
|
+
/**
|
|
898
|
+
* Diffs two denormalized data snapshots to produce patches.
|
|
899
|
+
* Handles entity link arrays with identity-aware splice/swap patches.
|
|
900
|
+
* @internal
|
|
901
|
+
*/
|
|
902
|
+
const diffSnapshots = (oldData, newData, entityArrayChanges) => {
|
|
903
|
+
const patches = [];
|
|
904
|
+
const diff = (old, cur, path) => {
|
|
905
|
+
if (isEqual(old, cur)) return;
|
|
906
|
+
if (cur === null || cur === void 0 || old === null || old === void 0 || typeof cur !== "object" || typeof old !== "object") {
|
|
907
|
+
patches.push({
|
|
908
|
+
type: "set",
|
|
909
|
+
path,
|
|
910
|
+
value: cur
|
|
911
|
+
});
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
if (Array.isArray(cur)) {
|
|
915
|
+
if (entityArrayChanges && Array.isArray(old)) {
|
|
916
|
+
const key = pathToKey(path);
|
|
917
|
+
const change = entityArrayChanges.get(key);
|
|
918
|
+
if (change) {
|
|
919
|
+
diffEntityArray(old, cur, path, change);
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
patches.push({
|
|
924
|
+
type: "set",
|
|
925
|
+
path,
|
|
926
|
+
value: cur
|
|
927
|
+
});
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
if (Array.isArray(old)) {
|
|
931
|
+
patches.push({
|
|
932
|
+
type: "set",
|
|
933
|
+
path,
|
|
934
|
+
value: cur
|
|
935
|
+
});
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
const oldObj = old;
|
|
939
|
+
const curObj = cur;
|
|
940
|
+
for (const key of Object.keys(curObj)) {
|
|
941
|
+
if (key === "__fragmentRef" || key === "__fragmentVars") continue;
|
|
942
|
+
diff(oldObj[key], curObj[key], [...path, key]);
|
|
943
|
+
}
|
|
944
|
+
};
|
|
945
|
+
const diffEntityArray = (old, cur, path, change) => {
|
|
946
|
+
const { oldKeys, newKeys } = change;
|
|
947
|
+
const oldByKey = /* @__PURE__ */ new Map();
|
|
948
|
+
for (const [i, key] of oldKeys.entries()) if (key) oldByKey.set(key, old[i]);
|
|
949
|
+
const arrayPatches = computeEntityArrayPatches(oldKeys.map((k) => k ? { [EntityLinkKey]: k } : null), newKeys.map((k) => k ? { [EntityLinkKey]: k } : null), path, cur);
|
|
950
|
+
patches.push(...arrayPatches);
|
|
951
|
+
for (const [i, item] of cur.entries()) {
|
|
952
|
+
const entityKey = newKeys[i];
|
|
953
|
+
diff(entityKey ? oldByKey.get(entityKey) : void 0, item, [...path, i]);
|
|
954
|
+
}
|
|
955
|
+
};
|
|
956
|
+
diff(oldData, newData, []);
|
|
957
|
+
return patches;
|
|
958
|
+
};
|
|
959
|
+
/**
|
|
960
|
+
* @internal
|
|
961
|
+
*/
|
|
962
|
+
const pathToKeyFn = pathToKey;
|
|
858
963
|
|
|
859
964
|
//#endregion
|
|
860
965
|
//#region src/cache/change.ts
|
|
861
966
|
/**
|
|
862
967
|
* @internal
|
|
863
968
|
*/
|
|
864
|
-
const classifyChanges = (
|
|
969
|
+
const classifyChanges = (changes) => {
|
|
865
970
|
const structural = [];
|
|
866
971
|
const scalar = [];
|
|
867
|
-
for (const
|
|
972
|
+
for (const change of changes) {
|
|
973
|
+
const { oldValue, newValue } = change;
|
|
868
974
|
if (isEntityLink(oldValue) && isEntityLink(newValue) && oldValue[EntityLinkKey] === newValue[EntityLinkKey]) continue;
|
|
869
975
|
if (isEntityLinkArray(oldValue) && isEntityLinkArray(newValue) && isEntityLinkArrayEqual(oldValue, newValue)) continue;
|
|
870
|
-
if (isEntityLink(oldValue) || isEntityLink(newValue) || isEntityLinkArray(oldValue) || isEntityLinkArray(newValue)) structural.push(
|
|
871
|
-
|
|
872
|
-
oldValue,
|
|
873
|
-
newValue
|
|
874
|
-
});
|
|
875
|
-
else scalar.push({
|
|
876
|
-
depKey,
|
|
877
|
-
newValue
|
|
878
|
-
});
|
|
976
|
+
if (isEntityLink(oldValue) || isEntityLink(newValue) || isEntityLinkArray(oldValue) || isEntityLinkArray(newValue)) structural.push(change);
|
|
977
|
+
else scalar.push(change);
|
|
879
978
|
}
|
|
880
979
|
return {
|
|
881
980
|
structural,
|
|
@@ -885,207 +984,229 @@ const classifyChanges = (changedKeys) => {
|
|
|
885
984
|
/**
|
|
886
985
|
* @internal
|
|
887
986
|
*/
|
|
888
|
-
const
|
|
889
|
-
const
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
987
|
+
const processScalarChanges = (changes, registry, subscriptions) => {
|
|
988
|
+
const result = /* @__PURE__ */ new Map();
|
|
989
|
+
for (const change of changes) {
|
|
990
|
+
const entries = registry.get(change.depKey);
|
|
991
|
+
if (!entries) continue;
|
|
992
|
+
for (const entry of entries) {
|
|
993
|
+
const sub = subscriptions.get(entry.subscriptionId);
|
|
994
|
+
if (!sub) continue;
|
|
995
|
+
let patchValue = change.newValue;
|
|
996
|
+
if (entry.selections && isNormalizedRecord(change.newValue)) {
|
|
997
|
+
const { data } = denormalize(entry.selections, {}, change.newValue, sub.variables);
|
|
998
|
+
patchValue = data;
|
|
999
|
+
}
|
|
1000
|
+
const patches = result.get(entry.subscriptionId) ?? [];
|
|
893
1001
|
patches.push({
|
|
894
1002
|
type: "set",
|
|
895
1003
|
path: entry.path,
|
|
896
|
-
value:
|
|
1004
|
+
value: patchValue
|
|
897
1005
|
});
|
|
898
|
-
|
|
1006
|
+
result.set(entry.subscriptionId, patches);
|
|
899
1007
|
}
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1008
|
+
}
|
|
1009
|
+
return result;
|
|
1010
|
+
};
|
|
1011
|
+
/**
|
|
1012
|
+
* @internal
|
|
1013
|
+
*/
|
|
1014
|
+
const buildEntityArrayContext = (changes, cursors) => {
|
|
1015
|
+
const result = /* @__PURE__ */ new Map();
|
|
1016
|
+
for (const change of changes) {
|
|
1017
|
+
if (!isEntityLinkArray(change.oldValue) && !isEntityLinkArray(change.newValue)) continue;
|
|
1018
|
+
for (const { depKey, entry } of cursors) if (depKey === change.depKey) {
|
|
1019
|
+
const oldArr = Array.isArray(change.oldValue) ? change.oldValue : [];
|
|
1020
|
+
const newArr = Array.isArray(change.newValue) ? change.newValue : [];
|
|
1021
|
+
const key = pathToKeyFn(entry.path);
|
|
1022
|
+
result.set(key, {
|
|
1023
|
+
oldKeys: oldArr.map((item) => extractEntityKey(item)),
|
|
1024
|
+
newKeys: newArr.map((item) => extractEntityKey(item))
|
|
913
1025
|
});
|
|
914
|
-
|
|
1026
|
+
break;
|
|
915
1027
|
}
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
1028
|
+
}
|
|
1029
|
+
return result.size > 0 ? result : void 0;
|
|
1030
|
+
};
|
|
1031
|
+
/**
|
|
1032
|
+
* @internal
|
|
1033
|
+
*/
|
|
1034
|
+
const processStructuralChanges = (changes, registry, subscriptions, storage, stalled) => {
|
|
1035
|
+
const result = /* @__PURE__ */ new Map();
|
|
1036
|
+
const processedSubs = /* @__PURE__ */ new Set();
|
|
1037
|
+
for (const change of changes) {
|
|
1038
|
+
const entries = registry.get(change.depKey);
|
|
1039
|
+
if (!entries) continue;
|
|
1040
|
+
for (const entry of entries) {
|
|
1041
|
+
const subId = entry.subscriptionId;
|
|
1042
|
+
if (processedSubs.has(subId)) continue;
|
|
1043
|
+
const sub = subscriptions.get(subId);
|
|
1044
|
+
if (!sub) continue;
|
|
1045
|
+
processedSubs.add(subId);
|
|
1046
|
+
registry.removeAll(sub.cursors);
|
|
1047
|
+
const rootStorageKey = sub.entityKey ?? "__root";
|
|
1048
|
+
const rootValue = storage[rootStorageKey];
|
|
1049
|
+
if (!rootValue) continue;
|
|
1050
|
+
const traceResult = traceSelections(sub.artifact.selections, storage, rootValue, sub.variables, rootStorageKey, [], sub.id);
|
|
1051
|
+
sub.cursors = new Set(traceResult.cursors.map((c) => c.entry));
|
|
1052
|
+
for (const { depKey, entry: cursorEntry } of traceResult.cursors) registry.add(depKey, cursorEntry);
|
|
1053
|
+
if (traceResult.complete) {
|
|
1054
|
+
stalled.delete(subId);
|
|
1055
|
+
const entityArrayChanges = buildEntityArrayContext(changes, traceResult.cursors);
|
|
1056
|
+
const patches = diffSnapshots(sub.data, traceResult.data, entityArrayChanges);
|
|
1057
|
+
sub.data = traceResult.data;
|
|
1058
|
+
if (patches.length > 0) result.set(subId, patches);
|
|
1059
|
+
} else stalled.set(subId, {
|
|
1060
|
+
subscription: sub,
|
|
1061
|
+
missingDeps: traceResult.missingDeps
|
|
924
1062
|
});
|
|
925
|
-
return patches;
|
|
926
1063
|
}
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1064
|
+
}
|
|
1065
|
+
return result;
|
|
1066
|
+
};
|
|
1067
|
+
|
|
1068
|
+
//#endregion
|
|
1069
|
+
//#region src/cache/optimistic.ts
|
|
1070
|
+
/**
|
|
1071
|
+
* CoW optimistic stack that tracks field-level changes for rollback.
|
|
1072
|
+
* @internal
|
|
1073
|
+
*/
|
|
1074
|
+
var OptimisticStack = class {
|
|
1075
|
+
#stack = [];
|
|
1076
|
+
push(key, changes) {
|
|
1077
|
+
this.#stack.push({
|
|
1078
|
+
key,
|
|
1079
|
+
changes
|
|
937
1080
|
});
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
const
|
|
944
|
-
|
|
945
|
-
const
|
|
946
|
-
|
|
947
|
-
const
|
|
948
|
-
const
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
}
|
|
955
|
-
for (const idx of removedIndices) {
|
|
956
|
-
const childKey = String(idx);
|
|
957
|
-
const child = node.children.get(childKey);
|
|
958
|
-
if (child) {
|
|
959
|
-
removeSubtreeEntries(child, entry.subscription, subscriptions);
|
|
960
|
-
node.children.delete(childKey);
|
|
1081
|
+
}
|
|
1082
|
+
has(key) {
|
|
1083
|
+
return this.#stack.some((e) => e.key === key);
|
|
1084
|
+
}
|
|
1085
|
+
rollback(key) {
|
|
1086
|
+
const idx = this.#stack.findIndex((e) => e.key === key);
|
|
1087
|
+
if (idx === -1) return [];
|
|
1088
|
+
const entry = this.#stack[idx];
|
|
1089
|
+
this.#stack.splice(idx, 1);
|
|
1090
|
+
const restorations = [];
|
|
1091
|
+
for (const [depKey, { old: oldVal, new: newVal }] of entry.changes) {
|
|
1092
|
+
const laterIdx = this.#stack.slice(idx).findIndex((later) => later.changes.has(depKey));
|
|
1093
|
+
if (laterIdx !== -1) {
|
|
1094
|
+
const laterChange = this.#stack[idx + laterIdx].changes.get(depKey);
|
|
1095
|
+
laterChange.old = oldVal;
|
|
1096
|
+
continue;
|
|
961
1097
|
}
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1098
|
+
const earlierEntry = this.#findClosestEarlier(depKey, idx);
|
|
1099
|
+
const restoreValue = earlierEntry === void 0 ? oldVal : earlierEntry;
|
|
1100
|
+
const { storageKey, fieldKey } = parseDependencyKey(depKey);
|
|
1101
|
+
restorations.push({
|
|
1102
|
+
depKey,
|
|
1103
|
+
storageKey,
|
|
1104
|
+
fieldKey,
|
|
1105
|
+
oldValue: newVal,
|
|
1106
|
+
newValue: restoreValue
|
|
968
1107
|
});
|
|
969
1108
|
}
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
const
|
|
975
|
-
|
|
976
|
-
const absI = start + i;
|
|
977
|
-
const absJ = start + j;
|
|
978
|
-
patches.push({
|
|
979
|
-
type: "swap",
|
|
980
|
-
path: entry.path,
|
|
981
|
-
i: absI,
|
|
982
|
-
j: absJ
|
|
983
|
-
});
|
|
984
|
-
const childI = node.children.get(String(absI));
|
|
985
|
-
const childJ = node.children.get(String(absJ));
|
|
986
|
-
if (childI && childJ) {
|
|
987
|
-
node.children.set(String(absI), childJ);
|
|
988
|
-
node.children.set(String(absJ), childI);
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
const siblingSelections = findSiblingSelections(node);
|
|
993
|
-
const addedKeys = newMiddle.filter((k) => k !== null && !oldMiddleSet.has(k));
|
|
994
|
-
for (const key of addedKeys) {
|
|
995
|
-
const idx = start + newMiddle.indexOf(key);
|
|
996
|
-
shiftChildrenRight(node, idx);
|
|
997
|
-
const entity = storage[key];
|
|
998
|
-
const insertNode = {
|
|
999
|
-
depKey: "",
|
|
1000
|
-
children: /* @__PURE__ */ new Map(),
|
|
1001
|
-
...siblingSelections && { selections: siblingSelections }
|
|
1002
|
-
};
|
|
1003
|
-
if (entity) {
|
|
1004
|
-
const { data } = partialDenormalize(insertNode, entity, [...entry.path, idx], rebuiltDepKeys, storage, subscriptions, entry.subscription);
|
|
1005
|
-
node.children.set(String(idx), insertNode);
|
|
1006
|
-
patches.push({
|
|
1007
|
-
type: "splice",
|
|
1008
|
-
path: entry.path,
|
|
1009
|
-
index: idx,
|
|
1010
|
-
deleteCount: 0,
|
|
1011
|
-
items: [data]
|
|
1012
|
-
});
|
|
1013
|
-
} else {
|
|
1014
|
-
node.children.set(String(idx), insertNode);
|
|
1015
|
-
patches.push({
|
|
1016
|
-
type: "splice",
|
|
1017
|
-
path: entry.path,
|
|
1018
|
-
index: idx,
|
|
1019
|
-
deleteCount: 0,
|
|
1020
|
-
items: [null]
|
|
1021
|
-
});
|
|
1022
|
-
}
|
|
1109
|
+
return restorations;
|
|
1110
|
+
}
|
|
1111
|
+
#findClosestEarlier(depKey, beforeIdx) {
|
|
1112
|
+
for (let i = beforeIdx - 1; i >= 0; i--) {
|
|
1113
|
+
const entry = this.#stack[i];
|
|
1114
|
+
if (entry.changes.has(depKey)) return entry.changes.get(depKey).new;
|
|
1023
1115
|
}
|
|
1024
|
-
rebuildArrayIndices(node, entry, subscriptions);
|
|
1025
|
-
return patches;
|
|
1026
1116
|
}
|
|
1027
|
-
return patches;
|
|
1028
1117
|
};
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1118
|
+
|
|
1119
|
+
//#endregion
|
|
1120
|
+
//#region src/cache/patch.ts
|
|
1121
|
+
const copyNode = (node) => Array.isArray(node) ? [...node] : { ...node };
|
|
1122
|
+
const shallowCopyPath = (root, path) => {
|
|
1123
|
+
if (path.length === 0) return root;
|
|
1124
|
+
let result = copyNode(root);
|
|
1125
|
+
const top = result;
|
|
1126
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
1127
|
+
const key = path[i];
|
|
1128
|
+
result[key] = copyNode(result[key]);
|
|
1129
|
+
result = result[key];
|
|
1130
|
+
}
|
|
1131
|
+
return top;
|
|
1033
1132
|
};
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1133
|
+
/**
|
|
1134
|
+
* Sets a value at a nested path within an object.
|
|
1135
|
+
* @param obj - The object to modify.
|
|
1136
|
+
* @param path - The path to the target location.
|
|
1137
|
+
* @param value - The value to set.
|
|
1138
|
+
*/
|
|
1139
|
+
const setPath = (obj, path, value) => {
|
|
1140
|
+
let current = obj;
|
|
1141
|
+
for (let i = 0; i < path.length - 1; i++) current = current[path[i]];
|
|
1142
|
+
current[path.at(-1)] = value;
|
|
1037
1143
|
};
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1144
|
+
/**
|
|
1145
|
+
* Gets a value at a nested path within an object.
|
|
1146
|
+
* @param obj - The object to read from.
|
|
1147
|
+
* @param path - The path to the target location.
|
|
1148
|
+
* @returns The value at the path, or the object itself if path is empty.
|
|
1149
|
+
*/
|
|
1150
|
+
const getPath = (obj, path) => {
|
|
1151
|
+
let current = obj;
|
|
1152
|
+
for (const segment of path) {
|
|
1153
|
+
if (current === void 0 || current === null) return void 0;
|
|
1154
|
+
current = current[segment];
|
|
1045
1155
|
}
|
|
1156
|
+
return current;
|
|
1046
1157
|
};
|
|
1047
1158
|
/**
|
|
1048
|
-
*
|
|
1159
|
+
* Applies cache patches to data immutably, shallow-copying only along changed paths.
|
|
1049
1160
|
*/
|
|
1050
|
-
const
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
const
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
for (const entry of entries) {
|
|
1058
|
-
const node = findEntryTreeNode(entry.subscription.entryTree, entry.path);
|
|
1059
|
-
if (!node) continue;
|
|
1060
|
-
const patches = processStructuralChange(entry, node, oldValue, newValue, rebuiltDepKeys, storage, subscriptions);
|
|
1061
|
-
if (patches.length > 0) {
|
|
1062
|
-
const existing = patchesBySubscription.get(entry.subscription) ?? [];
|
|
1063
|
-
existing.push(...patches);
|
|
1064
|
-
patchesBySubscription.set(entry.subscription, existing);
|
|
1065
|
-
}
|
|
1161
|
+
const applyPatchesImmutable = (data, patches) => {
|
|
1162
|
+
if (patches.length === 0) return data;
|
|
1163
|
+
let result = data;
|
|
1164
|
+
for (const patch of patches) if (patch.type === "set") {
|
|
1165
|
+
if (patch.path.length === 0) {
|
|
1166
|
+
result = patch.value;
|
|
1167
|
+
continue;
|
|
1066
1168
|
}
|
|
1169
|
+
result = shallowCopyPath(result, patch.path);
|
|
1170
|
+
let target = result;
|
|
1171
|
+
for (let i = 0; i < patch.path.length - 1; i++) target = target[patch.path[i]];
|
|
1172
|
+
target[patch.path.at(-1)] = patch.value;
|
|
1173
|
+
} else if (patch.type === "splice") {
|
|
1174
|
+
result = shallowCopyPath(result, patch.path);
|
|
1175
|
+
let target = result;
|
|
1176
|
+
for (const segment of patch.path) target = target[segment];
|
|
1177
|
+
const arr = [...target];
|
|
1178
|
+
arr.splice(patch.index, patch.deleteCount, ...patch.items);
|
|
1179
|
+
let parent = result;
|
|
1180
|
+
for (let i = 0; i < patch.path.length - 1; i++) parent = parent[patch.path[i]];
|
|
1181
|
+
parent[patch.path.at(-1)] = arr;
|
|
1182
|
+
} else if (patch.type === "swap") {
|
|
1183
|
+
result = shallowCopyPath(result, patch.path);
|
|
1184
|
+
let target = result;
|
|
1185
|
+
for (const segment of patch.path) target = target[segment];
|
|
1186
|
+
const arr = [...target];
|
|
1187
|
+
[arr[patch.i], arr[patch.j]] = [arr[patch.j], arr[patch.i]];
|
|
1188
|
+
let parent = result;
|
|
1189
|
+
for (let i = 0; i < patch.path.length - 1; i++) parent = parent[patch.path[i]];
|
|
1190
|
+
parent[patch.path.at(-1)] = arr;
|
|
1067
1191
|
}
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
});
|
|
1085
|
-
patchesBySubscription.set(entry.subscription, existing);
|
|
1086
|
-
}
|
|
1192
|
+
return result;
|
|
1193
|
+
};
|
|
1194
|
+
/**
|
|
1195
|
+
* Applies cache patches to a mutable target object in place.
|
|
1196
|
+
* @param target - The mutable object to apply patches to.
|
|
1197
|
+
* @param patches - The patches to apply.
|
|
1198
|
+
* @returns The new root value if a root-level set patch was applied, otherwise undefined.
|
|
1199
|
+
*/
|
|
1200
|
+
const applyPatchesMutable = (target, patches) => {
|
|
1201
|
+
let root;
|
|
1202
|
+
for (const patch of patches) if (patch.type === "set") if (patch.path.length === 0) root = patch.value;
|
|
1203
|
+
else setPath(target, patch.path, patch.value);
|
|
1204
|
+
else if (patch.type === "splice") getPath(target, patch.path).splice(patch.index, patch.deleteCount, ...patch.items);
|
|
1205
|
+
else if (patch.type === "swap") {
|
|
1206
|
+
const arr = getPath(target, patch.path);
|
|
1207
|
+
[arr[patch.i], arr[patch.j]] = [arr[patch.j], arr[patch.i]];
|
|
1087
1208
|
}
|
|
1088
|
-
return
|
|
1209
|
+
return root;
|
|
1089
1210
|
};
|
|
1090
1211
|
|
|
1091
1212
|
//#endregion
|
|
@@ -1097,141 +1218,39 @@ const generatePatches = (changedKeys, subscriptions, storage) => {
|
|
|
1097
1218
|
var Cache = class {
|
|
1098
1219
|
#schemaMeta;
|
|
1099
1220
|
#storage = { [RootFieldKey]: {} };
|
|
1221
|
+
#registry = new CursorRegistry();
|
|
1100
1222
|
#subscriptions = /* @__PURE__ */ new Map();
|
|
1223
|
+
#stalled = /* @__PURE__ */ new Map();
|
|
1224
|
+
#optimistic = new OptimisticStack();
|
|
1225
|
+
#nextId = 1;
|
|
1101
1226
|
#stale = /* @__PURE__ */ new Set();
|
|
1102
|
-
#optimisticKeys = [];
|
|
1103
|
-
#optimisticLayers = /* @__PURE__ */ new Map();
|
|
1104
|
-
#storageView = null;
|
|
1105
1227
|
constructor(schemaMetadata) {
|
|
1106
1228
|
this.#schemaMeta = schemaMetadata;
|
|
1107
1229
|
}
|
|
1108
|
-
#getStorageView() {
|
|
1109
|
-
if (this.#optimisticKeys.length === 0) return this.#storage;
|
|
1110
|
-
if (this.#storageView) return this.#storageView;
|
|
1111
|
-
const merged = { ...this.#storage };
|
|
1112
|
-
for (const storageKey of Object.keys(this.#storage)) merged[storageKey] = { ...this.#storage[storageKey] };
|
|
1113
|
-
for (const key of this.#optimisticKeys) {
|
|
1114
|
-
const layer = this.#optimisticLayers.get(key);
|
|
1115
|
-
if (!layer) continue;
|
|
1116
|
-
for (const storageKey of Object.keys(layer.storage)) merged[storageKey] = merged[storageKey] ? {
|
|
1117
|
-
...merged[storageKey],
|
|
1118
|
-
...layer.storage[storageKey]
|
|
1119
|
-
} : { ...layer.storage[storageKey] };
|
|
1120
|
-
}
|
|
1121
|
-
this.#storageView = merged;
|
|
1122
|
-
return merged;
|
|
1123
|
-
}
|
|
1124
|
-
/**
|
|
1125
|
-
* Writes an optimistic response to a separate cache layer.
|
|
1126
|
-
* The optimistic data is immediately visible in reads but does not affect the base storage.
|
|
1127
|
-
* @internal
|
|
1128
|
-
* @param key - Unique key identifying this optimistic mutation (typically the operation key).
|
|
1129
|
-
* @param artifact - GraphQL document artifact.
|
|
1130
|
-
* @param variables - Operation variables.
|
|
1131
|
-
* @param data - The optimistic response data.
|
|
1132
|
-
*/
|
|
1133
|
-
writeOptimistic(key, artifact, variables, data) {
|
|
1134
|
-
const layerStorage = { [RootFieldKey]: {} };
|
|
1135
|
-
const layerDependencies = /* @__PURE__ */ new Set();
|
|
1136
|
-
normalize(this.#schemaMeta, artifact.selections, layerStorage, data, variables, (storageKey, fieldKey) => {
|
|
1137
|
-
layerDependencies.add(makeDependencyKey(storageKey, fieldKey));
|
|
1138
|
-
});
|
|
1139
|
-
const oldValues = /* @__PURE__ */ new Map();
|
|
1140
|
-
const currentView = this.#getStorageView();
|
|
1141
|
-
for (const depKey of layerDependencies) {
|
|
1142
|
-
const { storageKey: sk, fieldKey: fk } = this.#parseDepKey(depKey);
|
|
1143
|
-
oldValues.set(depKey, currentView[sk]?.[fk]);
|
|
1144
|
-
}
|
|
1145
|
-
this.#optimisticKeys.push(key);
|
|
1146
|
-
this.#optimisticLayers.set(key, {
|
|
1147
|
-
storage: layerStorage,
|
|
1148
|
-
dependencies: layerDependencies
|
|
1149
|
-
});
|
|
1150
|
-
this.#storageView = null;
|
|
1151
|
-
const newView = this.#getStorageView();
|
|
1152
|
-
const changedKeys = /* @__PURE__ */ new Map();
|
|
1153
|
-
for (const depKey of layerDependencies) {
|
|
1154
|
-
const { storageKey: sk, fieldKey: fk } = this.#parseDepKey(depKey);
|
|
1155
|
-
const newVal = newView[sk]?.[fk];
|
|
1156
|
-
const oldVal = oldValues.get(depKey);
|
|
1157
|
-
if (oldVal !== newVal) changedKeys.set(depKey, {
|
|
1158
|
-
oldValue: oldVal,
|
|
1159
|
-
newValue: newVal
|
|
1160
|
-
});
|
|
1161
|
-
}
|
|
1162
|
-
const patchesBySubscription = generatePatches(changedKeys, this.#subscriptions, newView);
|
|
1163
|
-
for (const [subscription, patches] of patchesBySubscription) subscription.listener(patches);
|
|
1164
|
-
}
|
|
1165
|
-
/**
|
|
1166
|
-
* Removes an optimistic layer and notifies affected subscribers.
|
|
1167
|
-
* @internal
|
|
1168
|
-
* @param key - The key of the optimistic layer to remove.
|
|
1169
|
-
*/
|
|
1170
|
-
removeOptimistic(key) {
|
|
1171
|
-
const layer = this.#optimisticLayers.get(key);
|
|
1172
|
-
if (!layer) return;
|
|
1173
|
-
const currentView = this.#getStorageView();
|
|
1174
|
-
const oldValues = /* @__PURE__ */ new Map();
|
|
1175
|
-
for (const depKey of layer.dependencies) {
|
|
1176
|
-
const { storageKey: sk, fieldKey: fk } = this.#parseDepKey(depKey);
|
|
1177
|
-
oldValues.set(depKey, currentView[sk]?.[fk]);
|
|
1178
|
-
}
|
|
1179
|
-
this.#optimisticLayers.delete(key);
|
|
1180
|
-
this.#optimisticKeys = this.#optimisticKeys.filter((k) => k !== key);
|
|
1181
|
-
this.#storageView = null;
|
|
1182
|
-
const newView = this.#getStorageView();
|
|
1183
|
-
const changedKeys = /* @__PURE__ */ new Map();
|
|
1184
|
-
for (const depKey of layer.dependencies) {
|
|
1185
|
-
const { storageKey: sk, fieldKey: fk } = this.#parseDepKey(depKey);
|
|
1186
|
-
const newVal = newView[sk]?.[fk];
|
|
1187
|
-
const oldVal = oldValues.get(depKey);
|
|
1188
|
-
if (oldVal !== newVal) changedKeys.set(depKey, {
|
|
1189
|
-
oldValue: oldVal,
|
|
1190
|
-
newValue: newVal
|
|
1191
|
-
});
|
|
1192
|
-
}
|
|
1193
|
-
const patchesBySubscription = generatePatches(changedKeys, this.#subscriptions, newView);
|
|
1194
|
-
for (const [subscription, patches] of patchesBySubscription) subscription.listener(patches);
|
|
1195
|
-
}
|
|
1196
1230
|
/**
|
|
1197
1231
|
* Writes a query result to the cache, normalizing entities.
|
|
1198
|
-
* In addition to field-level stale clearing, this also clears entity-level stale entries
|
|
1199
|
-
* (e.g., `"User:1"`) when any field of that entity is written, because {@link invalidate}
|
|
1200
|
-
* supports entity-level invalidation without specifying a field.
|
|
1201
1232
|
* @param artifact - GraphQL document artifact.
|
|
1202
1233
|
* @param variables - Query variables.
|
|
1203
1234
|
* @param data - Query result data.
|
|
1204
1235
|
*/
|
|
1205
1236
|
writeQuery(artifact, variables, data) {
|
|
1206
|
-
const
|
|
1237
|
+
const changes = [];
|
|
1207
1238
|
const staleClearedKeys = /* @__PURE__ */ new Set();
|
|
1208
1239
|
const entityStaleCleared = /* @__PURE__ */ new Set();
|
|
1209
1240
|
normalize(this.#schemaMeta, artifact.selections, this.#storage, data, variables, (storageKey, fieldKey, oldValue, newValue) => {
|
|
1210
1241
|
const depKey = makeDependencyKey(storageKey, fieldKey);
|
|
1211
1242
|
if (this.#stale.delete(depKey)) staleClearedKeys.add(depKey);
|
|
1212
1243
|
if (!entityStaleCleared.has(storageKey) && this.#stale.delete(storageKey)) entityStaleCleared.add(storageKey);
|
|
1213
|
-
if (oldValue
|
|
1244
|
+
if (!isEqual(oldValue, newValue)) changes.push({
|
|
1245
|
+
depKey,
|
|
1246
|
+
storageKey,
|
|
1247
|
+
fieldKey,
|
|
1214
1248
|
oldValue,
|
|
1215
1249
|
newValue
|
|
1216
1250
|
});
|
|
1217
1251
|
});
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
const staleOnlySubscriptions = /* @__PURE__ */ new Set();
|
|
1221
|
-
for (const depKey of staleClearedKeys) {
|
|
1222
|
-
if (changedKeys.has(depKey)) continue;
|
|
1223
|
-
const entries = this.#subscriptions.get(depKey);
|
|
1224
|
-
if (entries) {
|
|
1225
|
-
for (const entry of entries) if (!patchesBySubscription.has(entry.subscription)) staleOnlySubscriptions.add(entry.subscription);
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
|
-
for (const entityKey of entityStaleCleared) {
|
|
1229
|
-
const prefix = `${entityKey}.`;
|
|
1230
|
-
for (const [depKey, entries] of this.#subscriptions) if (depKey.startsWith(prefix)) {
|
|
1231
|
-
for (const entry of entries) if (!patchesBySubscription.has(entry.subscription)) staleOnlySubscriptions.add(entry.subscription);
|
|
1232
|
-
}
|
|
1233
|
-
}
|
|
1234
|
-
for (const subscription of staleOnlySubscriptions) subscription.listener(null);
|
|
1252
|
+
this.#notifySubscribers(changes);
|
|
1253
|
+
this.#notifyStaleCleared(staleClearedKeys, entityStaleCleared, changes);
|
|
1235
1254
|
}
|
|
1236
1255
|
/**
|
|
1237
1256
|
* Reads a query result from the cache, denormalizing entities if available.
|
|
@@ -1241,8 +1260,7 @@ var Cache = class {
|
|
|
1241
1260
|
*/
|
|
1242
1261
|
readQuery(artifact, variables) {
|
|
1243
1262
|
let stale = false;
|
|
1244
|
-
const
|
|
1245
|
-
const { data, partial } = denormalize(artifact.selections, storage, storage[RootFieldKey], variables, (storageKey, fieldKey) => {
|
|
1263
|
+
const { data, partial } = denormalize(artifact.selections, this.#storage, this.#storage[RootFieldKey], variables, (storageKey, fieldKey) => {
|
|
1246
1264
|
if (this.#stale.has(storageKey) || this.#stale.has(makeDependencyKey(storageKey, fieldKey))) stale = true;
|
|
1247
1265
|
});
|
|
1248
1266
|
if (partial) return {
|
|
@@ -1259,49 +1277,46 @@ var Cache = class {
|
|
|
1259
1277
|
* @param artifact - GraphQL document artifact.
|
|
1260
1278
|
* @param variables - Query variables.
|
|
1261
1279
|
* @param listener - Callback function to invoke on cache changes.
|
|
1262
|
-
* @returns Object containing initial data, stale status, unsubscribe function
|
|
1280
|
+
* @returns Object containing initial data, stale status, and unsubscribe function.
|
|
1263
1281
|
*/
|
|
1264
1282
|
subscribeQuery(artifact, variables, listener) {
|
|
1283
|
+
const id = this.#nextId++;
|
|
1284
|
+
const vars = variables;
|
|
1265
1285
|
let stale = false;
|
|
1266
|
-
const
|
|
1267
|
-
const
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
});
|
|
1275
|
-
if (this.#stale.has(storageKey) || this.#stale.has(makeDependencyKey(storageKey, fieldKey))) stale = true;
|
|
1276
|
-
}, { trackFragmentDeps: false });
|
|
1277
|
-
const entryTree = buildEntryTree(tuples);
|
|
1286
|
+
const traceResult = traceSelections(artifact.selections, this.#storage, this.#storage[RootFieldKey], vars, RootFieldKey, [], id);
|
|
1287
|
+
for (const { depKey } of traceResult.cursors) {
|
|
1288
|
+
const { storageKey, fieldKey } = parseDependencyKey(depKey);
|
|
1289
|
+
if (this.#stale.has(storageKey) || this.#stale.has(makeDependencyKey(storageKey, fieldKey))) {
|
|
1290
|
+
stale = true;
|
|
1291
|
+
break;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1278
1294
|
const subscription = {
|
|
1295
|
+
id,
|
|
1296
|
+
kind: "query",
|
|
1297
|
+
artifact,
|
|
1298
|
+
variables: vars,
|
|
1279
1299
|
listener,
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1300
|
+
data: traceResult.complete ? traceResult.data : null,
|
|
1301
|
+
stale,
|
|
1302
|
+
cursors: new Set(traceResult.cursors.map((c) => c.entry))
|
|
1283
1303
|
};
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
let entrySet = this.#subscriptions.get(depKey);
|
|
1291
|
-
if (!entrySet) {
|
|
1292
|
-
entrySet = /* @__PURE__ */ new Set();
|
|
1293
|
-
this.#subscriptions.set(depKey, entrySet);
|
|
1294
|
-
}
|
|
1295
|
-
entrySet.add(entry);
|
|
1296
|
-
}
|
|
1304
|
+
this.#subscriptions.set(id, subscription);
|
|
1305
|
+
for (const { depKey, entry } of traceResult.cursors) this.#registry.add(depKey, entry);
|
|
1306
|
+
if (!traceResult.complete) this.#stalled.set(id, {
|
|
1307
|
+
subscription,
|
|
1308
|
+
missingDeps: traceResult.missingDeps
|
|
1309
|
+
});
|
|
1297
1310
|
const unsubscribe = () => {
|
|
1298
|
-
this.#
|
|
1311
|
+
this.#registry.removeAll(subscription.cursors);
|
|
1312
|
+
this.#subscriptions.delete(id);
|
|
1313
|
+
this.#stalled.delete(id);
|
|
1299
1314
|
};
|
|
1300
1315
|
return {
|
|
1301
|
-
data:
|
|
1316
|
+
data: subscription.data,
|
|
1302
1317
|
stale,
|
|
1303
|
-
|
|
1304
|
-
|
|
1318
|
+
subId: id,
|
|
1319
|
+
unsubscribe
|
|
1305
1320
|
};
|
|
1306
1321
|
}
|
|
1307
1322
|
/**
|
|
@@ -1313,14 +1328,13 @@ var Cache = class {
|
|
|
1313
1328
|
readFragment(artifact, fragmentRef) {
|
|
1314
1329
|
const storageKey = fragmentRef[FragmentRefKey];
|
|
1315
1330
|
const fragmentVars = getFragmentVars(fragmentRef, artifact.name);
|
|
1316
|
-
const storageView = this.#getStorageView();
|
|
1317
1331
|
let stale = false;
|
|
1318
|
-
const value =
|
|
1332
|
+
const value = this.#storage[storageKey];
|
|
1319
1333
|
if (!value) return {
|
|
1320
1334
|
data: null,
|
|
1321
1335
|
stale: false
|
|
1322
1336
|
};
|
|
1323
|
-
const { data, partial } = denormalize(artifact.selections,
|
|
1337
|
+
const { data, partial } = denormalize(artifact.selections, this.#storage, storageKey === RootFieldKey ? value : { [EntityLinkKey]: storageKey }, fragmentVars, (sk, fieldKey) => {
|
|
1324
1338
|
if (this.#stale.has(sk) || this.#stale.has(makeDependencyKey(sk, fieldKey))) stale = true;
|
|
1325
1339
|
});
|
|
1326
1340
|
if (partial) return {
|
|
@@ -1337,81 +1351,86 @@ var Cache = class {
|
|
|
1337
1351
|
* @param artifact - GraphQL fragment artifact.
|
|
1338
1352
|
* @param fragmentRef - Fragment reference containing entity key.
|
|
1339
1353
|
* @param listener - Callback function to invoke on cache changes.
|
|
1340
|
-
* @returns Object containing initial data, stale status, unsubscribe function
|
|
1354
|
+
* @returns Object containing initial data, stale status, and unsubscribe function.
|
|
1341
1355
|
*/
|
|
1342
1356
|
subscribeFragment(artifact, fragmentRef, listener) {
|
|
1343
1357
|
const storageKey = fragmentRef[FragmentRefKey];
|
|
1344
1358
|
const fragmentVars = getFragmentVars(fragmentRef, artifact.name);
|
|
1345
|
-
const
|
|
1346
|
-
const value = storageKey === RootFieldKey ?
|
|
1359
|
+
const id = this.#nextId++;
|
|
1360
|
+
const value = storageKey === RootFieldKey ? this.#storage[RootFieldKey] : this.#storage[storageKey];
|
|
1347
1361
|
if (!value) {
|
|
1348
|
-
const
|
|
1362
|
+
const subscription = {
|
|
1363
|
+
id,
|
|
1364
|
+
kind: "fragment",
|
|
1365
|
+
artifact,
|
|
1366
|
+
variables: fragmentVars,
|
|
1367
|
+
listener,
|
|
1368
|
+
entityKey: storageKey,
|
|
1369
|
+
data: null,
|
|
1370
|
+
stale: false,
|
|
1371
|
+
cursors: /* @__PURE__ */ new Set()
|
|
1372
|
+
};
|
|
1373
|
+
this.#subscriptions.set(id, subscription);
|
|
1349
1374
|
return {
|
|
1350
1375
|
data: null,
|
|
1351
1376
|
stale: false,
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
listener,
|
|
1355
|
-
selections: artifact.selections,
|
|
1356
|
-
variables: fragmentVars,
|
|
1357
|
-
entryTree
|
|
1358
|
-
}
|
|
1377
|
+
subId: id,
|
|
1378
|
+
unsubscribe: () => this.#subscriptions.delete(id)
|
|
1359
1379
|
};
|
|
1360
1380
|
}
|
|
1361
1381
|
let stale = false;
|
|
1362
|
-
const
|
|
1363
|
-
const
|
|
1364
|
-
const {
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1382
|
+
const denormalizeValue = storageKey === RootFieldKey ? value : value;
|
|
1383
|
+
const traceResult = traceSelections(artifact.selections, this.#storage, denormalizeValue, fragmentVars, storageKey, [], id);
|
|
1384
|
+
for (const { depKey } of traceResult.cursors) {
|
|
1385
|
+
const { storageKey: sk, fieldKey } = parseDependencyKey(depKey);
|
|
1386
|
+
if (this.#stale.has(sk) || this.#stale.has(makeDependencyKey(sk, fieldKey))) {
|
|
1387
|
+
stale = true;
|
|
1388
|
+
break;
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
if (!traceResult.complete) {
|
|
1392
|
+
const subscription = {
|
|
1393
|
+
id,
|
|
1394
|
+
kind: "fragment",
|
|
1395
|
+
artifact,
|
|
1396
|
+
variables: fragmentVars,
|
|
1397
|
+
listener,
|
|
1398
|
+
entityKey: storageKey,
|
|
1399
|
+
data: null,
|
|
1400
|
+
stale: false,
|
|
1401
|
+
cursors: /* @__PURE__ */ new Set()
|
|
1402
|
+
};
|
|
1403
|
+
this.#subscriptions.set(id, subscription);
|
|
1375
1404
|
return {
|
|
1376
1405
|
data: null,
|
|
1377
1406
|
stale: false,
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
listener,
|
|
1381
|
-
selections: artifact.selections,
|
|
1382
|
-
variables: fragmentVars,
|
|
1383
|
-
entryTree
|
|
1384
|
-
}
|
|
1407
|
+
subId: id,
|
|
1408
|
+
unsubscribe: () => this.#subscriptions.delete(id)
|
|
1385
1409
|
};
|
|
1386
1410
|
}
|
|
1387
|
-
const entryTree = buildEntryTree(tuples, storageKey === RootFieldKey ? void 0 : storageKey);
|
|
1388
1411
|
const subscription = {
|
|
1389
|
-
|
|
1390
|
-
|
|
1412
|
+
id,
|
|
1413
|
+
kind: "fragment",
|
|
1414
|
+
artifact,
|
|
1391
1415
|
variables: fragmentVars,
|
|
1392
|
-
|
|
1416
|
+
listener,
|
|
1417
|
+
entityKey: storageKey,
|
|
1418
|
+
data: traceResult.data,
|
|
1419
|
+
stale,
|
|
1420
|
+
cursors: new Set(traceResult.cursors.map((c) => c.entry))
|
|
1393
1421
|
};
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
const entry = {
|
|
1397
|
-
path: tuple.path,
|
|
1398
|
-
subscription
|
|
1399
|
-
};
|
|
1400
|
-
let entrySet = this.#subscriptions.get(depKey);
|
|
1401
|
-
if (!entrySet) {
|
|
1402
|
-
entrySet = /* @__PURE__ */ new Set();
|
|
1403
|
-
this.#subscriptions.set(depKey, entrySet);
|
|
1404
|
-
}
|
|
1405
|
-
entrySet.add(entry);
|
|
1406
|
-
}
|
|
1422
|
+
this.#subscriptions.set(id, subscription);
|
|
1423
|
+
for (const { depKey, entry } of traceResult.cursors) this.#registry.add(depKey, entry);
|
|
1407
1424
|
const unsubscribe = () => {
|
|
1408
|
-
this.#
|
|
1425
|
+
this.#registry.removeAll(subscription.cursors);
|
|
1426
|
+
this.#subscriptions.delete(id);
|
|
1427
|
+
this.#stalled.delete(id);
|
|
1409
1428
|
};
|
|
1410
1429
|
return {
|
|
1411
|
-
data:
|
|
1430
|
+
data: traceResult.data,
|
|
1412
1431
|
stale,
|
|
1413
|
-
|
|
1414
|
-
|
|
1432
|
+
subId: id,
|
|
1433
|
+
unsubscribe
|
|
1415
1434
|
};
|
|
1416
1435
|
}
|
|
1417
1436
|
readFragments(artifact, fragmentRefs) {
|
|
@@ -1442,6 +1461,45 @@ var Cache = class {
|
|
|
1442
1461
|
};
|
|
1443
1462
|
}
|
|
1444
1463
|
/**
|
|
1464
|
+
* Writes an optimistic response to the cache.
|
|
1465
|
+
* @internal
|
|
1466
|
+
*/
|
|
1467
|
+
writeOptimistic(key, artifact, variables, data) {
|
|
1468
|
+
const changes = [];
|
|
1469
|
+
const optimisticChanges = /* @__PURE__ */ new Map();
|
|
1470
|
+
normalize(this.#schemaMeta, artifact.selections, this.#storage, data, variables, (storageKey, fieldKey, oldValue, newValue) => {
|
|
1471
|
+
const depKey = makeDependencyKey(storageKey, fieldKey);
|
|
1472
|
+
if (!isEqual(oldValue, newValue)) {
|
|
1473
|
+
changes.push({
|
|
1474
|
+
depKey,
|
|
1475
|
+
storageKey,
|
|
1476
|
+
fieldKey,
|
|
1477
|
+
oldValue,
|
|
1478
|
+
newValue
|
|
1479
|
+
});
|
|
1480
|
+
optimisticChanges.set(depKey, {
|
|
1481
|
+
old: oldValue,
|
|
1482
|
+
new: newValue
|
|
1483
|
+
});
|
|
1484
|
+
}
|
|
1485
|
+
});
|
|
1486
|
+
this.#optimistic.push(key, optimisticChanges);
|
|
1487
|
+
this.#notifySubscribers(changes);
|
|
1488
|
+
}
|
|
1489
|
+
/**
|
|
1490
|
+
* Removes an optimistic layer and notifies affected subscribers.
|
|
1491
|
+
* @internal
|
|
1492
|
+
*/
|
|
1493
|
+
removeOptimistic(key) {
|
|
1494
|
+
const restorations = this.#optimistic.rollback(key);
|
|
1495
|
+
for (const restoration of restorations) {
|
|
1496
|
+
const { storageKey, fieldKey, newValue } = restoration;
|
|
1497
|
+
const fields = this.#storage[storageKey];
|
|
1498
|
+
if (fields) fields[fieldKey] = newValue;
|
|
1499
|
+
}
|
|
1500
|
+
this.#notifySubscribers(restorations);
|
|
1501
|
+
}
|
|
1502
|
+
/**
|
|
1445
1503
|
* Invalidates one or more cache entries and notifies affected subscribers.
|
|
1446
1504
|
* @param targets - Cache entries to invalidate.
|
|
1447
1505
|
*/
|
|
@@ -1451,10 +1509,10 @@ var Cache = class {
|
|
|
1451
1509
|
const fieldKey = makeFieldKeyFromArgs(target.$field, target.$args);
|
|
1452
1510
|
const depKey = makeDependencyKey(RootFieldKey, fieldKey);
|
|
1453
1511
|
this.#stale.add(depKey);
|
|
1454
|
-
this.#
|
|
1512
|
+
this.#collectAffectedSubscriptions(RootFieldKey, fieldKey, affectedSubscriptions);
|
|
1455
1513
|
} else {
|
|
1456
1514
|
this.#stale.add(RootFieldKey);
|
|
1457
|
-
this.#
|
|
1515
|
+
this.#collectAffectedSubscriptions(RootFieldKey, void 0, affectedSubscriptions);
|
|
1458
1516
|
}
|
|
1459
1517
|
else {
|
|
1460
1518
|
const keyFields = this.#schemaMeta.entities[target.__typename]?.keyFields;
|
|
@@ -1464,10 +1522,10 @@ var Cache = class {
|
|
|
1464
1522
|
if ("$field" in target) {
|
|
1465
1523
|
const fieldKey = makeFieldKeyFromArgs(target.$field, target.$args);
|
|
1466
1524
|
this.#stale.add(makeDependencyKey(entityKey, fieldKey));
|
|
1467
|
-
this.#
|
|
1525
|
+
this.#collectAffectedSubscriptions(entityKey, fieldKey, affectedSubscriptions);
|
|
1468
1526
|
} else {
|
|
1469
1527
|
this.#stale.add(entityKey);
|
|
1470
|
-
this.#
|
|
1528
|
+
this.#collectAffectedSubscriptions(entityKey, void 0, affectedSubscriptions);
|
|
1471
1529
|
}
|
|
1472
1530
|
} else {
|
|
1473
1531
|
const prefix = `${target.__typename}:`;
|
|
@@ -1476,61 +1534,33 @@ var Cache = class {
|
|
|
1476
1534
|
if ("$field" in target) {
|
|
1477
1535
|
const fieldKey = makeFieldKeyFromArgs(target.$field, target.$args);
|
|
1478
1536
|
this.#stale.add(makeDependencyKey(entityKey, fieldKey));
|
|
1479
|
-
this.#
|
|
1537
|
+
this.#collectAffectedSubscriptions(entityKey, fieldKey, affectedSubscriptions);
|
|
1480
1538
|
} else {
|
|
1481
1539
|
this.#stale.add(entityKey);
|
|
1482
|
-
this.#
|
|
1540
|
+
this.#collectAffectedSubscriptions(entityKey, void 0, affectedSubscriptions);
|
|
1483
1541
|
}
|
|
1484
1542
|
}
|
|
1485
1543
|
}
|
|
1486
1544
|
}
|
|
1487
|
-
|
|
1545
|
+
const subsToNotify = [];
|
|
1546
|
+
for (const subId of affectedSubscriptions) {
|
|
1547
|
+
const sub = this.#subscriptions.get(subId);
|
|
1548
|
+
if (sub) {
|
|
1549
|
+
sub.stale = true;
|
|
1550
|
+
subsToNotify.push(sub);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
for (const sub of subsToNotify) if (sub.stale) sub.listener({ type: "stale" });
|
|
1488
1554
|
}
|
|
1489
1555
|
/**
|
|
1490
1556
|
* Checks if a subscription has stale data.
|
|
1491
1557
|
* @internal
|
|
1492
1558
|
*/
|
|
1493
|
-
isStale(
|
|
1494
|
-
|
|
1495
|
-
if (node.depKey.includes("@")) {
|
|
1496
|
-
const { storageKey } = parseDependencyKey(node.depKey);
|
|
1497
|
-
if (this.#stale.has(storageKey) || this.#stale.has(node.depKey)) return true;
|
|
1498
|
-
}
|
|
1499
|
-
for (const child of node.children.values()) if (check(child)) return true;
|
|
1500
|
-
return false;
|
|
1501
|
-
};
|
|
1502
|
-
return check(subscription.entryTree);
|
|
1503
|
-
}
|
|
1504
|
-
#hasKeyFields(target, keyFields) {
|
|
1505
|
-
return keyFields.every((f) => f in target);
|
|
1506
|
-
}
|
|
1507
|
-
#collectSubscriptions(storageKey, fieldKey, out) {
|
|
1508
|
-
if (fieldKey === void 0) {
|
|
1509
|
-
const prefix = `${storageKey}.`;
|
|
1510
|
-
for (const [depKey, entries] of this.#subscriptions) if (depKey.startsWith(prefix)) for (const entry of entries) out.add(entry.subscription);
|
|
1511
|
-
} else {
|
|
1512
|
-
const depKey = makeDependencyKey(storageKey, fieldKey);
|
|
1513
|
-
const entries = this.#subscriptions.get(depKey);
|
|
1514
|
-
if (entries) for (const entry of entries) out.add(entry.subscription);
|
|
1515
|
-
}
|
|
1516
|
-
}
|
|
1517
|
-
#removeSubscriptionFromTree(node, subscription) {
|
|
1518
|
-
const entries = this.#subscriptions.get(node.depKey);
|
|
1519
|
-
if (entries) {
|
|
1520
|
-
for (const entry of entries) if (entry.subscription === subscription) {
|
|
1521
|
-
entries.delete(entry);
|
|
1522
|
-
break;
|
|
1523
|
-
}
|
|
1524
|
-
if (entries.size === 0) this.#subscriptions.delete(node.depKey);
|
|
1525
|
-
}
|
|
1526
|
-
for (const child of node.children.values()) this.#removeSubscriptionFromTree(child, subscription);
|
|
1527
|
-
}
|
|
1528
|
-
#parseDepKey(depKey) {
|
|
1529
|
-
return parseDependencyKey(depKey);
|
|
1559
|
+
isStale(subId) {
|
|
1560
|
+
return this.#subscriptions.get(subId)?.stale ?? false;
|
|
1530
1561
|
}
|
|
1531
1562
|
/**
|
|
1532
1563
|
* Extracts a serializable snapshot of the cache storage.
|
|
1533
|
-
* Optimistic layers are excluded because they represent transient in-flight state.
|
|
1534
1564
|
*/
|
|
1535
1565
|
extract() {
|
|
1536
1566
|
return { storage: structuredClone(this.#storage) };
|
|
@@ -1544,18 +1574,100 @@ var Cache = class {
|
|
|
1544
1574
|
...this.#storage[key],
|
|
1545
1575
|
...fields
|
|
1546
1576
|
};
|
|
1547
|
-
this.#storageView = null;
|
|
1548
1577
|
}
|
|
1549
1578
|
/**
|
|
1550
1579
|
* Clears all cache data.
|
|
1551
1580
|
*/
|
|
1552
1581
|
clear() {
|
|
1553
1582
|
this.#storage = { [RootFieldKey]: {} };
|
|
1583
|
+
this.#registry.clear();
|
|
1554
1584
|
this.#subscriptions.clear();
|
|
1585
|
+
this.#stalled.clear();
|
|
1555
1586
|
this.#stale.clear();
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1587
|
+
}
|
|
1588
|
+
#notifySubscribers(changes) {
|
|
1589
|
+
if (changes.length === 0) return;
|
|
1590
|
+
const unstalledPatches = this.#checkStalled(changes);
|
|
1591
|
+
const { scalar, structural } = classifyChanges(changes);
|
|
1592
|
+
const scalarPatches = processScalarChanges(scalar, this.#registry, this.#subscriptions);
|
|
1593
|
+
const structuralPatches = processStructuralChanges(structural, this.#registry, this.#subscriptions, this.#storage, this.#stalled);
|
|
1594
|
+
const allPatches = /* @__PURE__ */ new Map();
|
|
1595
|
+
for (const [subId, patches] of unstalledPatches) allPatches.set(subId, patches);
|
|
1596
|
+
for (const [subId, patches] of scalarPatches) {
|
|
1597
|
+
if (unstalledPatches.has(subId)) continue;
|
|
1598
|
+
allPatches.set(subId, [...allPatches.get(subId) ?? [], ...patches]);
|
|
1599
|
+
}
|
|
1600
|
+
for (const [subId, patches] of structuralPatches) {
|
|
1601
|
+
if (unstalledPatches.has(subId)) continue;
|
|
1602
|
+
allPatches.set(subId, [...allPatches.get(subId) ?? [], ...patches]);
|
|
1603
|
+
}
|
|
1604
|
+
for (const [subId, patches] of allPatches) {
|
|
1605
|
+
const sub = this.#subscriptions.get(subId);
|
|
1606
|
+
if (sub && patches.length > 0) {
|
|
1607
|
+
if (!structuralPatches.has(subId) && !unstalledPatches.has(subId)) sub.data = applyPatchesImmutable(sub.data, patches);
|
|
1608
|
+
sub.listener({
|
|
1609
|
+
type: "patch",
|
|
1610
|
+
patches
|
|
1611
|
+
});
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
#checkStalled(changes) {
|
|
1616
|
+
const result = /* @__PURE__ */ new Map();
|
|
1617
|
+
const writtenDepKeys = new Set(changes.map((c) => c.depKey));
|
|
1618
|
+
for (const [subId, info] of this.#stalled) {
|
|
1619
|
+
if (![...info.missingDeps].some((dep) => writtenDepKeys.has(dep))) continue;
|
|
1620
|
+
const sub = info.subscription;
|
|
1621
|
+
const rootStorageKey = sub.entityKey ?? RootFieldKey;
|
|
1622
|
+
const rootValue = this.#storage[rootStorageKey];
|
|
1623
|
+
if (!rootValue) continue;
|
|
1624
|
+
const traceResult = traceSelections(sub.artifact.selections, this.#storage, rootValue, sub.variables, rootStorageKey, [], sub.id);
|
|
1625
|
+
if (traceResult.complete) {
|
|
1626
|
+
this.#registry.removeAll(sub.cursors);
|
|
1627
|
+
sub.cursors = new Set(traceResult.cursors.map((c) => c.entry));
|
|
1628
|
+
for (const { depKey, entry } of traceResult.cursors) this.#registry.add(depKey, entry);
|
|
1629
|
+
this.#stalled.delete(subId);
|
|
1630
|
+
const entityArrayChanges = buildEntityArrayContext(changes, traceResult.cursors);
|
|
1631
|
+
const patches = diffSnapshots(sub.data, traceResult.data, entityArrayChanges);
|
|
1632
|
+
if (patches.length > 0) {
|
|
1633
|
+
sub.data = traceResult.data;
|
|
1634
|
+
result.set(subId, patches);
|
|
1635
|
+
}
|
|
1636
|
+
} else info.missingDeps = traceResult.missingDeps;
|
|
1637
|
+
}
|
|
1638
|
+
return result;
|
|
1639
|
+
}
|
|
1640
|
+
#notifyStaleCleared(staleClearedKeys, entityStaleCleared, changes) {
|
|
1641
|
+
const changedDepKeys = new Set(changes.map((c) => c.depKey));
|
|
1642
|
+
const notifiedSubs = /* @__PURE__ */ new Set();
|
|
1643
|
+
for (const depKey of staleClearedKeys) {
|
|
1644
|
+
if (changedDepKeys.has(depKey)) continue;
|
|
1645
|
+
const entries = this.#registry.get(depKey);
|
|
1646
|
+
if (entries) for (const entry of entries) notifiedSubs.add(entry.subscriptionId);
|
|
1647
|
+
}
|
|
1648
|
+
for (const entityKey of entityStaleCleared) this.#registry.forEachByPrefix(`${entityKey}.`, (entry) => {
|
|
1649
|
+
notifiedSubs.add(entry.subscriptionId);
|
|
1650
|
+
});
|
|
1651
|
+
for (const subId of notifiedSubs) {
|
|
1652
|
+
const sub = this.#subscriptions.get(subId);
|
|
1653
|
+
if (sub?.stale) {
|
|
1654
|
+
sub.stale = false;
|
|
1655
|
+
sub.listener({ type: "stale" });
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
#hasKeyFields(target, keyFields) {
|
|
1660
|
+
return keyFields.every((f) => f in target);
|
|
1661
|
+
}
|
|
1662
|
+
#collectAffectedSubscriptions(storageKey, fieldKey, out) {
|
|
1663
|
+
if (fieldKey === void 0) this.#registry.forEachByPrefix(`${storageKey}.`, (entry) => {
|
|
1664
|
+
out.add(entry.subscriptionId);
|
|
1665
|
+
});
|
|
1666
|
+
else {
|
|
1667
|
+
const depKey = makeDependencyKey(storageKey, fieldKey);
|
|
1668
|
+
const entries = this.#registry.get(depKey);
|
|
1669
|
+
if (entries) for (const entry of entries) out.add(entry.subscriptionId);
|
|
1670
|
+
}
|
|
1559
1671
|
}
|
|
1560
1672
|
};
|
|
1561
1673
|
|
|
@@ -1588,7 +1700,6 @@ const cacheExchange = (options = {}) => {
|
|
|
1588
1700
|
},
|
|
1589
1701
|
io: (ops$) => {
|
|
1590
1702
|
const subscriptionHasData = /* @__PURE__ */ new Map();
|
|
1591
|
-
const resubscribe$ = require_make.makeSubject();
|
|
1592
1703
|
const refetch$ = require_make.makeSubject();
|
|
1593
1704
|
const fragment$ = require_make.pipe(ops$, require_make.filter((op) => op.variant === "request" && op.artifact.kind === "fragment"), require_make.mergeMap((op) => {
|
|
1594
1705
|
const fragmentRef = op.metadata?.fragment?.ref;
|
|
@@ -1599,11 +1710,10 @@ const cacheExchange = (options = {}) => {
|
|
|
1599
1710
|
if (isFragmentRefArray(fragmentRef)) {
|
|
1600
1711
|
const results = require_make.makeSubject();
|
|
1601
1712
|
const unsubscribes = [];
|
|
1602
|
-
const fragmentSubscriptions = [];
|
|
1603
1713
|
for (const [index, ref] of fragmentRef.entries()) {
|
|
1604
|
-
const
|
|
1605
|
-
if (
|
|
1606
|
-
const indexedPatches = patches.map((patch) => ({
|
|
1714
|
+
const listener = (notification) => {
|
|
1715
|
+
if (notification.type === "patch") {
|
|
1716
|
+
const indexedPatches = notification.patches.map((patch) => ({
|
|
1607
1717
|
...patch,
|
|
1608
1718
|
path: [index, ...patch.path]
|
|
1609
1719
|
}));
|
|
@@ -1613,21 +1723,17 @@ const cacheExchange = (options = {}) => {
|
|
|
1613
1723
|
errors: []
|
|
1614
1724
|
});
|
|
1615
1725
|
} else {
|
|
1616
|
-
const
|
|
1617
|
-
if (
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
errors: []
|
|
1624
|
-
});
|
|
1625
|
-
}
|
|
1726
|
+
const { data, stale } = cache.readFragments(op.artifact, fragmentRef);
|
|
1727
|
+
if (data !== null && stale) results.next({
|
|
1728
|
+
operation: op,
|
|
1729
|
+
data,
|
|
1730
|
+
metadata: { cache: { stale: true } },
|
|
1731
|
+
errors: []
|
|
1732
|
+
});
|
|
1626
1733
|
}
|
|
1627
1734
|
};
|
|
1628
|
-
const { unsubscribe
|
|
1735
|
+
const { unsubscribe } = cache.subscribeFragment(op.artifact, ref, listener);
|
|
1629
1736
|
unsubscribes.push(unsubscribe);
|
|
1630
|
-
fragmentSubscriptions.push(subscription);
|
|
1631
1737
|
}
|
|
1632
1738
|
const { data: initialData, stale: initialStale } = cache.readFragments(op.artifact, fragmentRef);
|
|
1633
1739
|
const teardown$ = require_make.pipe(ops$, require_make.filter((operation) => operation.variant === "teardown" && operation.key === op.key), require_make.tap(() => {
|
|
@@ -1647,31 +1753,25 @@ const cacheExchange = (options = {}) => {
|
|
|
1647
1753
|
errors: []
|
|
1648
1754
|
});
|
|
1649
1755
|
const results = require_make.makeSubject();
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
const patchListener = (patches) => {
|
|
1653
|
-
if (patches) results.next({
|
|
1756
|
+
const listener = (notification) => {
|
|
1757
|
+
if (notification.type === "patch") results.next({
|
|
1654
1758
|
operation: op,
|
|
1655
|
-
metadata: { cache: { patches } },
|
|
1759
|
+
metadata: { cache: { patches: notification.patches } },
|
|
1656
1760
|
errors: []
|
|
1657
1761
|
});
|
|
1658
|
-
else
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
});
|
|
1667
|
-
}
|
|
1762
|
+
else {
|
|
1763
|
+
const { data: staleData, stale: isStale } = cache.readFragment(op.artifact, fragmentRef);
|
|
1764
|
+
if (staleData !== null && isStale) results.next({
|
|
1765
|
+
operation: op,
|
|
1766
|
+
data: staleData,
|
|
1767
|
+
metadata: { cache: { stale: true } },
|
|
1768
|
+
errors: []
|
|
1769
|
+
});
|
|
1668
1770
|
}
|
|
1669
1771
|
};
|
|
1670
|
-
const { data, stale, unsubscribe
|
|
1671
|
-
currentUnsubscribe = unsubscribe;
|
|
1672
|
-
currentSubscription = subscription;
|
|
1772
|
+
const { data, stale, unsubscribe } = cache.subscribeFragment(op.artifact, fragmentRef, listener);
|
|
1673
1773
|
const teardown$ = require_make.pipe(ops$, require_make.filter((operation) => operation.variant === "teardown" && operation.key === op.key), require_make.tap(() => {
|
|
1674
|
-
|
|
1774
|
+
unsubscribe();
|
|
1675
1775
|
results.complete();
|
|
1676
1776
|
}));
|
|
1677
1777
|
return require_make.pipe(require_make.merge(data === null ? empty() : require_make.fromValue({
|
|
@@ -1687,50 +1787,34 @@ const cacheExchange = (options = {}) => {
|
|
|
1687
1787
|
const query$ = require_make.pipe(ops$, require_make.filter((op) => op.variant === "request" && op.artifact.kind === "query" && fetchPolicy !== "network-only"), require_make.share());
|
|
1688
1788
|
return require_make.merge(fragment$, require_make.pipe(query$, require_make.mergeMap((op) => {
|
|
1689
1789
|
const results = require_make.makeSubject();
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1790
|
+
const listener = (notification) => {
|
|
1791
|
+
if (notification.type === "patch") {
|
|
1792
|
+
if (!subscriptionHasData.get(op.key)) return;
|
|
1793
|
+
results.next({
|
|
1794
|
+
operation: op,
|
|
1795
|
+
metadata: { cache: { patches: notification.patches } },
|
|
1796
|
+
errors: []
|
|
1797
|
+
});
|
|
1798
|
+
} else {
|
|
1799
|
+
const { data: staleData, stale: isStale } = cache.readQuery(op.artifact, op.variables);
|
|
1800
|
+
if (isStale) {
|
|
1801
|
+
if (staleData !== null) results.next({
|
|
1699
1802
|
operation: op,
|
|
1700
|
-
|
|
1803
|
+
data: staleData,
|
|
1804
|
+
metadata: { cache: { stale: true } },
|
|
1701
1805
|
errors: []
|
|
1702
1806
|
});
|
|
1703
|
-
|
|
1704
|
-
if (cache.isStale(currentSubscription)) {
|
|
1705
|
-
const { data: staleData } = cache.readQuery(op.artifact, op.variables);
|
|
1706
|
-
if (staleData !== null) results.next({
|
|
1707
|
-
operation: op,
|
|
1708
|
-
data: staleData,
|
|
1709
|
-
metadata: { cache: { stale: true } },
|
|
1710
|
-
errors: []
|
|
1711
|
-
});
|
|
1712
|
-
refetch$.next(op);
|
|
1713
|
-
}
|
|
1807
|
+
refetch$.next(op);
|
|
1714
1808
|
}
|
|
1715
|
-
}
|
|
1716
|
-
const result = cache.subscribeQuery(op.artifact, op.variables, patchListener);
|
|
1717
|
-
currentUnsubscribe = result.unsubscribe;
|
|
1718
|
-
currentSubscription = result.subscription;
|
|
1719
|
-
return result;
|
|
1809
|
+
}
|
|
1720
1810
|
};
|
|
1721
|
-
const { data, stale } =
|
|
1811
|
+
const { data, stale, unsubscribe } = cache.subscribeQuery(op.artifact, op.variables, listener);
|
|
1722
1812
|
subscriptionHasData.set(op.key, data !== null);
|
|
1723
|
-
if (data !== null) initialized = true;
|
|
1724
1813
|
const teardown$ = require_make.pipe(ops$, require_make.filter((o) => o.variant === "teardown" && o.key === op.key), require_make.tap(() => {
|
|
1725
|
-
|
|
1814
|
+
unsubscribe();
|
|
1726
1815
|
subscriptionHasData.delete(op.key);
|
|
1727
1816
|
results.complete();
|
|
1728
1817
|
}));
|
|
1729
|
-
const resubStream$ = require_make.pipe(resubscribe$.source, require_make.filter((key) => key === op.key), require_make.mergeMap(() => {
|
|
1730
|
-
doSubscribe();
|
|
1731
|
-
initialized = true;
|
|
1732
|
-
return empty();
|
|
1733
|
-
}));
|
|
1734
1818
|
const stream$ = require_make.pipe(require_make.merge(data === null ? fetchPolicy === "cache-only" ? require_make.fromValue({
|
|
1735
1819
|
operation: op,
|
|
1736
1820
|
data: null,
|
|
@@ -1740,7 +1824,7 @@ const cacheExchange = (options = {}) => {
|
|
|
1740
1824
|
data,
|
|
1741
1825
|
...stale && { metadata: { cache: { stale: true } } },
|
|
1742
1826
|
errors: []
|
|
1743
|
-
}), results.source
|
|
1827
|
+
}), results.source), require_make.takeUntil(teardown$));
|
|
1744
1828
|
if (stale) refetch$.next(op);
|
|
1745
1829
|
return stream$;
|
|
1746
1830
|
}), require_make.filter(() => fetchPolicy === "cache-only" || fetchPolicy === "cache-and-network" || fetchPolicy === "cache-first")), require_make.pipe(require_make.merge(nonCache$, require_make.pipe(query$, require_make.filter((op) => {
|
|
@@ -1760,7 +1844,6 @@ const cacheExchange = (options = {}) => {
|
|
|
1760
1844
|
});
|
|
1761
1845
|
}
|
|
1762
1846
|
subscriptionHasData.set(result.operation.key, true);
|
|
1763
|
-
resubscribe$.next(result.operation.key);
|
|
1764
1847
|
const { data } = cache.readQuery(result.operation.artifact, result.operation.variables);
|
|
1765
1848
|
if (data !== null) return require_make.fromValue({
|
|
1766
1849
|
...result,
|
|
@@ -1777,99 +1860,6 @@ const cacheExchange = (options = {}) => {
|
|
|
1777
1860
|
};
|
|
1778
1861
|
};
|
|
1779
1862
|
|
|
1780
|
-
//#endregion
|
|
1781
|
-
//#region src/cache/patch.ts
|
|
1782
|
-
const copyNode = (node) => Array.isArray(node) ? [...node] : { ...node };
|
|
1783
|
-
const shallowCopyPath = (root, path) => {
|
|
1784
|
-
if (path.length === 0) return root;
|
|
1785
|
-
let result = copyNode(root);
|
|
1786
|
-
const top = result;
|
|
1787
|
-
for (let i = 0; i < path.length - 1; i++) {
|
|
1788
|
-
const key = path[i];
|
|
1789
|
-
result[key] = copyNode(result[key]);
|
|
1790
|
-
result = result[key];
|
|
1791
|
-
}
|
|
1792
|
-
return top;
|
|
1793
|
-
};
|
|
1794
|
-
/**
|
|
1795
|
-
* Sets a value at a nested path within an object.
|
|
1796
|
-
* @param obj - The object to modify.
|
|
1797
|
-
* @param path - The path to the target location.
|
|
1798
|
-
* @param value - The value to set.
|
|
1799
|
-
*/
|
|
1800
|
-
const setPath = (obj, path, value) => {
|
|
1801
|
-
let current = obj;
|
|
1802
|
-
for (let i = 0; i < path.length - 1; i++) current = current[path[i]];
|
|
1803
|
-
current[path.at(-1)] = value;
|
|
1804
|
-
};
|
|
1805
|
-
/**
|
|
1806
|
-
* Gets a value at a nested path within an object.
|
|
1807
|
-
* @param obj - The object to read from.
|
|
1808
|
-
* @param path - The path to the target location.
|
|
1809
|
-
* @returns The value at the path, or the object itself if path is empty.
|
|
1810
|
-
*/
|
|
1811
|
-
const getPath = (obj, path) => {
|
|
1812
|
-
let current = obj;
|
|
1813
|
-
for (const segment of path) {
|
|
1814
|
-
if (current === void 0 || current === null) return void 0;
|
|
1815
|
-
current = current[segment];
|
|
1816
|
-
}
|
|
1817
|
-
return current;
|
|
1818
|
-
};
|
|
1819
|
-
/**
|
|
1820
|
-
* Applies cache patches to data immutably, shallow-copying only along changed paths.
|
|
1821
|
-
*/
|
|
1822
|
-
const applyPatchesImmutable = (data, patches) => {
|
|
1823
|
-
if (patches.length === 0) return data;
|
|
1824
|
-
let result = data;
|
|
1825
|
-
for (const patch of patches) if (patch.type === "set") {
|
|
1826
|
-
if (patch.path.length === 0) {
|
|
1827
|
-
result = patch.value;
|
|
1828
|
-
continue;
|
|
1829
|
-
}
|
|
1830
|
-
result = shallowCopyPath(result, patch.path);
|
|
1831
|
-
let target = result;
|
|
1832
|
-
for (let i = 0; i < patch.path.length - 1; i++) target = target[patch.path[i]];
|
|
1833
|
-
target[patch.path.at(-1)] = patch.value;
|
|
1834
|
-
} else if (patch.type === "splice") {
|
|
1835
|
-
result = shallowCopyPath(result, patch.path);
|
|
1836
|
-
let target = result;
|
|
1837
|
-
for (const segment of patch.path) target = target[segment];
|
|
1838
|
-
const arr = [...target];
|
|
1839
|
-
arr.splice(patch.index, patch.deleteCount, ...patch.items);
|
|
1840
|
-
let parent = result;
|
|
1841
|
-
for (let i = 0; i < patch.path.length - 1; i++) parent = parent[patch.path[i]];
|
|
1842
|
-
parent[patch.path.at(-1)] = arr;
|
|
1843
|
-
} else if (patch.type === "swap") {
|
|
1844
|
-
result = shallowCopyPath(result, patch.path);
|
|
1845
|
-
let target = result;
|
|
1846
|
-
for (const segment of patch.path) target = target[segment];
|
|
1847
|
-
const arr = [...target];
|
|
1848
|
-
[arr[patch.i], arr[patch.j]] = [arr[patch.j], arr[patch.i]];
|
|
1849
|
-
let parent = result;
|
|
1850
|
-
for (let i = 0; i < patch.path.length - 1; i++) parent = parent[patch.path[i]];
|
|
1851
|
-
parent[patch.path.at(-1)] = arr;
|
|
1852
|
-
}
|
|
1853
|
-
return result;
|
|
1854
|
-
};
|
|
1855
|
-
/**
|
|
1856
|
-
* Applies cache patches to a mutable target object in place.
|
|
1857
|
-
* @param target - The mutable object to apply patches to.
|
|
1858
|
-
* @param patches - The patches to apply.
|
|
1859
|
-
* @returns The new root value if a root-level set patch was applied, otherwise undefined.
|
|
1860
|
-
*/
|
|
1861
|
-
const applyPatchesMutable = (target, patches) => {
|
|
1862
|
-
let root;
|
|
1863
|
-
for (const patch of patches) if (patch.type === "set") if (patch.path.length === 0) root = patch.value;
|
|
1864
|
-
else setPath(target, patch.path, patch.value);
|
|
1865
|
-
else if (patch.type === "splice") getPath(target, patch.path).splice(patch.index, patch.deleteCount, ...patch.items);
|
|
1866
|
-
else if (patch.type === "swap") {
|
|
1867
|
-
const arr = getPath(target, patch.path);
|
|
1868
|
-
[arr[patch.i], arr[patch.j]] = [arr[patch.j], arr[patch.i]];
|
|
1869
|
-
}
|
|
1870
|
-
return root;
|
|
1871
|
-
};
|
|
1872
|
-
|
|
1873
1863
|
//#endregion
|
|
1874
1864
|
//#region src/exchanges/retry.ts
|
|
1875
1865
|
const defaultShouldRetry = (error) => isExchangeError(error, "http") && error.extensions?.statusCode !== void 0 && error.extensions.statusCode >= 500;
|
|
@@ -2060,7 +2050,7 @@ const subscriptionExchange = (options) => {
|
|
|
2060
2050
|
return require_make.pipe(require_make.make((observer) => {
|
|
2061
2051
|
let unsubscribe;
|
|
2062
2052
|
let completed = false;
|
|
2063
|
-
|
|
2053
|
+
const doSubscribe = () => {
|
|
2064
2054
|
if (completed) return;
|
|
2065
2055
|
unsubscribe = client.subscribe({
|
|
2066
2056
|
operationName: op.artifact.name,
|
|
@@ -2069,16 +2059,28 @@ const subscriptionExchange = (options) => {
|
|
|
2069
2059
|
}, {
|
|
2070
2060
|
next: (result) => {
|
|
2071
2061
|
const response = result;
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2062
|
+
try {
|
|
2063
|
+
observer.next({
|
|
2064
|
+
operation: op,
|
|
2065
|
+
data: response.data,
|
|
2066
|
+
errors: response.errors?.map((err) => new GraphQLError(err.message, {
|
|
2067
|
+
path: err.path,
|
|
2068
|
+
locations: err.locations,
|
|
2069
|
+
extensions: err.extensions
|
|
2070
|
+
})),
|
|
2071
|
+
extensions: response.extensions
|
|
2072
|
+
});
|
|
2073
|
+
} catch (error) {
|
|
2074
|
+
try {
|
|
2075
|
+
observer.next({
|
|
2076
|
+
operation: op,
|
|
2077
|
+
errors: [new ExchangeError(error instanceof Error ? error.message : String(error), {
|
|
2078
|
+
exchangeName: "subscription",
|
|
2079
|
+
cause: error
|
|
2080
|
+
})]
|
|
2081
|
+
});
|
|
2082
|
+
} catch {}
|
|
2083
|
+
}
|
|
2082
2084
|
},
|
|
2083
2085
|
error: (error) => {
|
|
2084
2086
|
observer.next({
|
|
@@ -2088,11 +2090,13 @@ const subscriptionExchange = (options) => {
|
|
|
2088
2090
|
cause: error
|
|
2089
2091
|
})]
|
|
2090
2092
|
});
|
|
2091
|
-
|
|
2093
|
+
unsubscribe = void 0;
|
|
2094
|
+
Promise.resolve().then(doSubscribe);
|
|
2092
2095
|
},
|
|
2093
2096
|
complete: observer.complete
|
|
2094
2097
|
});
|
|
2095
|
-
}
|
|
2098
|
+
};
|
|
2099
|
+
Promise.resolve().then(doSubscribe);
|
|
2096
2100
|
return () => {
|
|
2097
2101
|
completed = true;
|
|
2098
2102
|
unsubscribe?.();
|