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