@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.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,201 +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
|
-
|
|
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]];
|
|
1081
1208
|
}
|
|
1082
|
-
return
|
|
1209
|
+
return root;
|
|
1083
1210
|
};
|
|
1084
1211
|
|
|
1085
1212
|
//#endregion
|
|
@@ -1091,141 +1218,39 @@ const generatePatches = (changedKeys, subscriptions, storage) => {
|
|
|
1091
1218
|
var Cache = class {
|
|
1092
1219
|
#schemaMeta;
|
|
1093
1220
|
#storage = { [RootFieldKey]: {} };
|
|
1221
|
+
#registry = new CursorRegistry();
|
|
1094
1222
|
#subscriptions = /* @__PURE__ */ new Map();
|
|
1223
|
+
#stalled = /* @__PURE__ */ new Map();
|
|
1224
|
+
#optimistic = new OptimisticStack();
|
|
1225
|
+
#nextId = 1;
|
|
1095
1226
|
#stale = /* @__PURE__ */ new Set();
|
|
1096
|
-
#optimisticKeys = [];
|
|
1097
|
-
#optimisticLayers = /* @__PURE__ */ new Map();
|
|
1098
|
-
#storageView = null;
|
|
1099
1227
|
constructor(schemaMetadata) {
|
|
1100
1228
|
this.#schemaMeta = schemaMetadata;
|
|
1101
1229
|
}
|
|
1102
|
-
#getStorageView() {
|
|
1103
|
-
if (this.#optimisticKeys.length === 0) return this.#storage;
|
|
1104
|
-
if (this.#storageView) return this.#storageView;
|
|
1105
|
-
const merged = { ...this.#storage };
|
|
1106
|
-
for (const storageKey of Object.keys(this.#storage)) merged[storageKey] = { ...this.#storage[storageKey] };
|
|
1107
|
-
for (const key of this.#optimisticKeys) {
|
|
1108
|
-
const layer = this.#optimisticLayers.get(key);
|
|
1109
|
-
if (!layer) continue;
|
|
1110
|
-
for (const storageKey of Object.keys(layer.storage)) merged[storageKey] = merged[storageKey] ? {
|
|
1111
|
-
...merged[storageKey],
|
|
1112
|
-
...layer.storage[storageKey]
|
|
1113
|
-
} : { ...layer.storage[storageKey] };
|
|
1114
|
-
}
|
|
1115
|
-
this.#storageView = merged;
|
|
1116
|
-
return merged;
|
|
1117
|
-
}
|
|
1118
|
-
/**
|
|
1119
|
-
* Writes an optimistic response to a separate cache layer.
|
|
1120
|
-
* The optimistic data is immediately visible in reads but does not affect the base storage.
|
|
1121
|
-
* @internal
|
|
1122
|
-
* @param key - Unique key identifying this optimistic mutation (typically the operation key).
|
|
1123
|
-
* @param artifact - GraphQL document artifact.
|
|
1124
|
-
* @param variables - Operation variables.
|
|
1125
|
-
* @param data - The optimistic response data.
|
|
1126
|
-
*/
|
|
1127
|
-
writeOptimistic(key, artifact, variables, data) {
|
|
1128
|
-
const layerStorage = { [RootFieldKey]: {} };
|
|
1129
|
-
const layerDependencies = /* @__PURE__ */ new Set();
|
|
1130
|
-
normalize(this.#schemaMeta, artifact.selections, layerStorage, data, variables, (storageKey, fieldKey) => {
|
|
1131
|
-
layerDependencies.add(makeDependencyKey(storageKey, fieldKey));
|
|
1132
|
-
});
|
|
1133
|
-
const oldValues = /* @__PURE__ */ new Map();
|
|
1134
|
-
const currentView = this.#getStorageView();
|
|
1135
|
-
for (const depKey of layerDependencies) {
|
|
1136
|
-
const { storageKey: sk, fieldKey: fk } = this.#parseDepKey(depKey);
|
|
1137
|
-
oldValues.set(depKey, currentView[sk]?.[fk]);
|
|
1138
|
-
}
|
|
1139
|
-
this.#optimisticKeys.push(key);
|
|
1140
|
-
this.#optimisticLayers.set(key, {
|
|
1141
|
-
storage: layerStorage,
|
|
1142
|
-
dependencies: layerDependencies
|
|
1143
|
-
});
|
|
1144
|
-
this.#storageView = null;
|
|
1145
|
-
const newView = this.#getStorageView();
|
|
1146
|
-
const changedKeys = /* @__PURE__ */ new Map();
|
|
1147
|
-
for (const depKey of layerDependencies) {
|
|
1148
|
-
const { storageKey: sk, fieldKey: fk } = this.#parseDepKey(depKey);
|
|
1149
|
-
const newVal = newView[sk]?.[fk];
|
|
1150
|
-
const oldVal = oldValues.get(depKey);
|
|
1151
|
-
if (oldVal !== newVal) changedKeys.set(depKey, {
|
|
1152
|
-
oldValue: oldVal,
|
|
1153
|
-
newValue: newVal
|
|
1154
|
-
});
|
|
1155
|
-
}
|
|
1156
|
-
const patchesBySubscription = generatePatches(changedKeys, this.#subscriptions, newView);
|
|
1157
|
-
for (const [subscription, patches] of patchesBySubscription) subscription.listener(patches);
|
|
1158
|
-
}
|
|
1159
|
-
/**
|
|
1160
|
-
* Removes an optimistic layer and notifies affected subscribers.
|
|
1161
|
-
* @internal
|
|
1162
|
-
* @param key - The key of the optimistic layer to remove.
|
|
1163
|
-
*/
|
|
1164
|
-
removeOptimistic(key) {
|
|
1165
|
-
const layer = this.#optimisticLayers.get(key);
|
|
1166
|
-
if (!layer) return;
|
|
1167
|
-
const currentView = this.#getStorageView();
|
|
1168
|
-
const oldValues = /* @__PURE__ */ new Map();
|
|
1169
|
-
for (const depKey of layer.dependencies) {
|
|
1170
|
-
const { storageKey: sk, fieldKey: fk } = this.#parseDepKey(depKey);
|
|
1171
|
-
oldValues.set(depKey, currentView[sk]?.[fk]);
|
|
1172
|
-
}
|
|
1173
|
-
this.#optimisticLayers.delete(key);
|
|
1174
|
-
this.#optimisticKeys = this.#optimisticKeys.filter((k) => k !== key);
|
|
1175
|
-
this.#storageView = null;
|
|
1176
|
-
const newView = this.#getStorageView();
|
|
1177
|
-
const changedKeys = /* @__PURE__ */ new Map();
|
|
1178
|
-
for (const depKey of layer.dependencies) {
|
|
1179
|
-
const { storageKey: sk, fieldKey: fk } = this.#parseDepKey(depKey);
|
|
1180
|
-
const newVal = newView[sk]?.[fk];
|
|
1181
|
-
const oldVal = oldValues.get(depKey);
|
|
1182
|
-
if (oldVal !== newVal) changedKeys.set(depKey, {
|
|
1183
|
-
oldValue: oldVal,
|
|
1184
|
-
newValue: newVal
|
|
1185
|
-
});
|
|
1186
|
-
}
|
|
1187
|
-
const patchesBySubscription = generatePatches(changedKeys, this.#subscriptions, newView);
|
|
1188
|
-
for (const [subscription, patches] of patchesBySubscription) subscription.listener(patches);
|
|
1189
|
-
}
|
|
1190
1230
|
/**
|
|
1191
1231
|
* Writes a query result to the cache, normalizing entities.
|
|
1192
|
-
* In addition to field-level stale clearing, this also clears entity-level stale entries
|
|
1193
|
-
* (e.g., `"User:1"`) when any field of that entity is written, because {@link invalidate}
|
|
1194
|
-
* supports entity-level invalidation without specifying a field.
|
|
1195
1232
|
* @param artifact - GraphQL document artifact.
|
|
1196
1233
|
* @param variables - Query variables.
|
|
1197
1234
|
* @param data - Query result data.
|
|
1198
1235
|
*/
|
|
1199
1236
|
writeQuery(artifact, variables, data) {
|
|
1200
|
-
const
|
|
1237
|
+
const changes = [];
|
|
1201
1238
|
const staleClearedKeys = /* @__PURE__ */ new Set();
|
|
1202
1239
|
const entityStaleCleared = /* @__PURE__ */ new Set();
|
|
1203
1240
|
normalize(this.#schemaMeta, artifact.selections, this.#storage, data, variables, (storageKey, fieldKey, oldValue, newValue) => {
|
|
1204
1241
|
const depKey = makeDependencyKey(storageKey, fieldKey);
|
|
1205
1242
|
if (this.#stale.delete(depKey)) staleClearedKeys.add(depKey);
|
|
1206
1243
|
if (!entityStaleCleared.has(storageKey) && this.#stale.delete(storageKey)) entityStaleCleared.add(storageKey);
|
|
1207
|
-
if (oldValue
|
|
1244
|
+
if (!isEqual(oldValue, newValue)) changes.push({
|
|
1245
|
+
depKey,
|
|
1246
|
+
storageKey,
|
|
1247
|
+
fieldKey,
|
|
1208
1248
|
oldValue,
|
|
1209
1249
|
newValue
|
|
1210
1250
|
});
|
|
1211
1251
|
});
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
const staleOnlySubscriptions = /* @__PURE__ */ new Set();
|
|
1215
|
-
for (const depKey of staleClearedKeys) {
|
|
1216
|
-
if (changedKeys.has(depKey)) continue;
|
|
1217
|
-
const entries = this.#subscriptions.get(depKey);
|
|
1218
|
-
if (entries) {
|
|
1219
|
-
for (const entry of entries) if (!patchesBySubscription.has(entry.subscription)) staleOnlySubscriptions.add(entry.subscription);
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
for (const entityKey of entityStaleCleared) {
|
|
1223
|
-
const prefix = `${entityKey}.`;
|
|
1224
|
-
for (const [depKey, entries] of this.#subscriptions) if (depKey.startsWith(prefix)) {
|
|
1225
|
-
for (const entry of entries) if (!patchesBySubscription.has(entry.subscription)) staleOnlySubscriptions.add(entry.subscription);
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
|
-
for (const subscription of staleOnlySubscriptions) subscription.listener(null);
|
|
1252
|
+
this.#notifySubscribers(changes);
|
|
1253
|
+
this.#notifyStaleCleared(staleClearedKeys, entityStaleCleared, changes);
|
|
1229
1254
|
}
|
|
1230
1255
|
/**
|
|
1231
1256
|
* Reads a query result from the cache, denormalizing entities if available.
|
|
@@ -1235,8 +1260,7 @@ var Cache = class {
|
|
|
1235
1260
|
*/
|
|
1236
1261
|
readQuery(artifact, variables) {
|
|
1237
1262
|
let stale = false;
|
|
1238
|
-
const
|
|
1239
|
-
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) => {
|
|
1240
1264
|
if (this.#stale.has(storageKey) || this.#stale.has(makeDependencyKey(storageKey, fieldKey))) stale = true;
|
|
1241
1265
|
});
|
|
1242
1266
|
if (partial) return {
|
|
@@ -1253,49 +1277,46 @@ var Cache = class {
|
|
|
1253
1277
|
* @param artifact - GraphQL document artifact.
|
|
1254
1278
|
* @param variables - Query variables.
|
|
1255
1279
|
* @param listener - Callback function to invoke on cache changes.
|
|
1256
|
-
* @returns Object containing initial data, stale status, unsubscribe function
|
|
1280
|
+
* @returns Object containing initial data, stale status, and unsubscribe function.
|
|
1257
1281
|
*/
|
|
1258
1282
|
subscribeQuery(artifact, variables, listener) {
|
|
1283
|
+
const id = this.#nextId++;
|
|
1284
|
+
const vars = variables;
|
|
1259
1285
|
let stale = false;
|
|
1260
|
-
const
|
|
1261
|
-
const
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
});
|
|
1269
|
-
if (this.#stale.has(storageKey) || this.#stale.has(makeDependencyKey(storageKey, fieldKey))) stale = true;
|
|
1270
|
-
}, { trackFragmentDeps: false });
|
|
1271
|
-
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
|
+
}
|
|
1272
1294
|
const subscription = {
|
|
1295
|
+
id,
|
|
1296
|
+
kind: "query",
|
|
1297
|
+
artifact,
|
|
1298
|
+
variables: vars,
|
|
1273
1299
|
listener,
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1300
|
+
data: traceResult.complete ? traceResult.data : null,
|
|
1301
|
+
stale,
|
|
1302
|
+
cursors: new Set(traceResult.cursors.map((c) => c.entry))
|
|
1277
1303
|
};
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
let entrySet = this.#subscriptions.get(depKey);
|
|
1285
|
-
if (!entrySet) {
|
|
1286
|
-
entrySet = /* @__PURE__ */ new Set();
|
|
1287
|
-
this.#subscriptions.set(depKey, entrySet);
|
|
1288
|
-
}
|
|
1289
|
-
entrySet.add(entry);
|
|
1290
|
-
}
|
|
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
|
+
});
|
|
1291
1310
|
const unsubscribe = () => {
|
|
1292
|
-
this.#
|
|
1311
|
+
this.#registry.removeAll(subscription.cursors);
|
|
1312
|
+
this.#subscriptions.delete(id);
|
|
1313
|
+
this.#stalled.delete(id);
|
|
1293
1314
|
};
|
|
1294
1315
|
return {
|
|
1295
|
-
data:
|
|
1316
|
+
data: subscription.data,
|
|
1296
1317
|
stale,
|
|
1297
|
-
|
|
1298
|
-
|
|
1318
|
+
subId: id,
|
|
1319
|
+
unsubscribe
|
|
1299
1320
|
};
|
|
1300
1321
|
}
|
|
1301
1322
|
/**
|
|
@@ -1307,14 +1328,13 @@ var Cache = class {
|
|
|
1307
1328
|
readFragment(artifact, fragmentRef) {
|
|
1308
1329
|
const storageKey = fragmentRef[FragmentRefKey];
|
|
1309
1330
|
const fragmentVars = getFragmentVars(fragmentRef, artifact.name);
|
|
1310
|
-
const storageView = this.#getStorageView();
|
|
1311
1331
|
let stale = false;
|
|
1312
|
-
const value =
|
|
1332
|
+
const value = this.#storage[storageKey];
|
|
1313
1333
|
if (!value) return {
|
|
1314
1334
|
data: null,
|
|
1315
1335
|
stale: false
|
|
1316
1336
|
};
|
|
1317
|
-
const { data, partial } = denormalize(artifact.selections,
|
|
1337
|
+
const { data, partial } = denormalize(artifact.selections, this.#storage, storageKey === RootFieldKey ? value : { [EntityLinkKey]: storageKey }, fragmentVars, (sk, fieldKey) => {
|
|
1318
1338
|
if (this.#stale.has(sk) || this.#stale.has(makeDependencyKey(sk, fieldKey))) stale = true;
|
|
1319
1339
|
});
|
|
1320
1340
|
if (partial) return {
|
|
@@ -1331,81 +1351,86 @@ var Cache = class {
|
|
|
1331
1351
|
* @param artifact - GraphQL fragment artifact.
|
|
1332
1352
|
* @param fragmentRef - Fragment reference containing entity key.
|
|
1333
1353
|
* @param listener - Callback function to invoke on cache changes.
|
|
1334
|
-
* @returns Object containing initial data, stale status, unsubscribe function
|
|
1354
|
+
* @returns Object containing initial data, stale status, and unsubscribe function.
|
|
1335
1355
|
*/
|
|
1336
1356
|
subscribeFragment(artifact, fragmentRef, listener) {
|
|
1337
1357
|
const storageKey = fragmentRef[FragmentRefKey];
|
|
1338
1358
|
const fragmentVars = getFragmentVars(fragmentRef, artifact.name);
|
|
1339
|
-
const
|
|
1340
|
-
const value = storageKey === RootFieldKey ?
|
|
1359
|
+
const id = this.#nextId++;
|
|
1360
|
+
const value = storageKey === RootFieldKey ? this.#storage[RootFieldKey] : this.#storage[storageKey];
|
|
1341
1361
|
if (!value) {
|
|
1342
|
-
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);
|
|
1343
1374
|
return {
|
|
1344
1375
|
data: null,
|
|
1345
1376
|
stale: false,
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
listener,
|
|
1349
|
-
selections: artifact.selections,
|
|
1350
|
-
variables: fragmentVars,
|
|
1351
|
-
entryTree
|
|
1352
|
-
}
|
|
1377
|
+
subId: id,
|
|
1378
|
+
unsubscribe: () => this.#subscriptions.delete(id)
|
|
1353
1379
|
};
|
|
1354
1380
|
}
|
|
1355
1381
|
let stale = false;
|
|
1356
|
-
const
|
|
1357
|
-
const
|
|
1358
|
-
const {
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
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);
|
|
1369
1404
|
return {
|
|
1370
1405
|
data: null,
|
|
1371
1406
|
stale: false,
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
listener,
|
|
1375
|
-
selections: artifact.selections,
|
|
1376
|
-
variables: fragmentVars,
|
|
1377
|
-
entryTree
|
|
1378
|
-
}
|
|
1407
|
+
subId: id,
|
|
1408
|
+
unsubscribe: () => this.#subscriptions.delete(id)
|
|
1379
1409
|
};
|
|
1380
1410
|
}
|
|
1381
|
-
const entryTree = buildEntryTree(tuples, storageKey === RootFieldKey ? void 0 : storageKey);
|
|
1382
1411
|
const subscription = {
|
|
1383
|
-
|
|
1384
|
-
|
|
1412
|
+
id,
|
|
1413
|
+
kind: "fragment",
|
|
1414
|
+
artifact,
|
|
1385
1415
|
variables: fragmentVars,
|
|
1386
|
-
|
|
1416
|
+
listener,
|
|
1417
|
+
entityKey: storageKey,
|
|
1418
|
+
data: traceResult.data,
|
|
1419
|
+
stale,
|
|
1420
|
+
cursors: new Set(traceResult.cursors.map((c) => c.entry))
|
|
1387
1421
|
};
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
const entry = {
|
|
1391
|
-
path: tuple.path,
|
|
1392
|
-
subscription
|
|
1393
|
-
};
|
|
1394
|
-
let entrySet = this.#subscriptions.get(depKey);
|
|
1395
|
-
if (!entrySet) {
|
|
1396
|
-
entrySet = /* @__PURE__ */ new Set();
|
|
1397
|
-
this.#subscriptions.set(depKey, entrySet);
|
|
1398
|
-
}
|
|
1399
|
-
entrySet.add(entry);
|
|
1400
|
-
}
|
|
1422
|
+
this.#subscriptions.set(id, subscription);
|
|
1423
|
+
for (const { depKey, entry } of traceResult.cursors) this.#registry.add(depKey, entry);
|
|
1401
1424
|
const unsubscribe = () => {
|
|
1402
|
-
this.#
|
|
1425
|
+
this.#registry.removeAll(subscription.cursors);
|
|
1426
|
+
this.#subscriptions.delete(id);
|
|
1427
|
+
this.#stalled.delete(id);
|
|
1403
1428
|
};
|
|
1404
1429
|
return {
|
|
1405
|
-
data:
|
|
1430
|
+
data: traceResult.data,
|
|
1406
1431
|
stale,
|
|
1407
|
-
|
|
1408
|
-
|
|
1432
|
+
subId: id,
|
|
1433
|
+
unsubscribe
|
|
1409
1434
|
};
|
|
1410
1435
|
}
|
|
1411
1436
|
readFragments(artifact, fragmentRefs) {
|
|
@@ -1436,6 +1461,45 @@ var Cache = class {
|
|
|
1436
1461
|
};
|
|
1437
1462
|
}
|
|
1438
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
|
+
/**
|
|
1439
1503
|
* Invalidates one or more cache entries and notifies affected subscribers.
|
|
1440
1504
|
* @param targets - Cache entries to invalidate.
|
|
1441
1505
|
*/
|
|
@@ -1445,10 +1509,10 @@ var Cache = class {
|
|
|
1445
1509
|
const fieldKey = makeFieldKeyFromArgs(target.$field, target.$args);
|
|
1446
1510
|
const depKey = makeDependencyKey(RootFieldKey, fieldKey);
|
|
1447
1511
|
this.#stale.add(depKey);
|
|
1448
|
-
this.#
|
|
1512
|
+
this.#collectAffectedSubscriptions(RootFieldKey, fieldKey, affectedSubscriptions);
|
|
1449
1513
|
} else {
|
|
1450
1514
|
this.#stale.add(RootFieldKey);
|
|
1451
|
-
this.#
|
|
1515
|
+
this.#collectAffectedSubscriptions(RootFieldKey, void 0, affectedSubscriptions);
|
|
1452
1516
|
}
|
|
1453
1517
|
else {
|
|
1454
1518
|
const keyFields = this.#schemaMeta.entities[target.__typename]?.keyFields;
|
|
@@ -1458,10 +1522,10 @@ var Cache = class {
|
|
|
1458
1522
|
if ("$field" in target) {
|
|
1459
1523
|
const fieldKey = makeFieldKeyFromArgs(target.$field, target.$args);
|
|
1460
1524
|
this.#stale.add(makeDependencyKey(entityKey, fieldKey));
|
|
1461
|
-
this.#
|
|
1525
|
+
this.#collectAffectedSubscriptions(entityKey, fieldKey, affectedSubscriptions);
|
|
1462
1526
|
} else {
|
|
1463
1527
|
this.#stale.add(entityKey);
|
|
1464
|
-
this.#
|
|
1528
|
+
this.#collectAffectedSubscriptions(entityKey, void 0, affectedSubscriptions);
|
|
1465
1529
|
}
|
|
1466
1530
|
} else {
|
|
1467
1531
|
const prefix = `${target.__typename}:`;
|
|
@@ -1470,61 +1534,33 @@ var Cache = class {
|
|
|
1470
1534
|
if ("$field" in target) {
|
|
1471
1535
|
const fieldKey = makeFieldKeyFromArgs(target.$field, target.$args);
|
|
1472
1536
|
this.#stale.add(makeDependencyKey(entityKey, fieldKey));
|
|
1473
|
-
this.#
|
|
1537
|
+
this.#collectAffectedSubscriptions(entityKey, fieldKey, affectedSubscriptions);
|
|
1474
1538
|
} else {
|
|
1475
1539
|
this.#stale.add(entityKey);
|
|
1476
|
-
this.#
|
|
1540
|
+
this.#collectAffectedSubscriptions(entityKey, void 0, affectedSubscriptions);
|
|
1477
1541
|
}
|
|
1478
1542
|
}
|
|
1479
1543
|
}
|
|
1480
1544
|
}
|
|
1481
|
-
|
|
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" });
|
|
1482
1554
|
}
|
|
1483
1555
|
/**
|
|
1484
1556
|
* Checks if a subscription has stale data.
|
|
1485
1557
|
* @internal
|
|
1486
1558
|
*/
|
|
1487
|
-
isStale(
|
|
1488
|
-
|
|
1489
|
-
if (node.depKey.includes("@")) {
|
|
1490
|
-
const { storageKey } = parseDependencyKey(node.depKey);
|
|
1491
|
-
if (this.#stale.has(storageKey) || this.#stale.has(node.depKey)) return true;
|
|
1492
|
-
}
|
|
1493
|
-
for (const child of node.children.values()) if (check(child)) return true;
|
|
1494
|
-
return false;
|
|
1495
|
-
};
|
|
1496
|
-
return check(subscription.entryTree);
|
|
1497
|
-
}
|
|
1498
|
-
#hasKeyFields(target, keyFields) {
|
|
1499
|
-
return keyFields.every((f) => f in target);
|
|
1500
|
-
}
|
|
1501
|
-
#collectSubscriptions(storageKey, fieldKey, out) {
|
|
1502
|
-
if (fieldKey === void 0) {
|
|
1503
|
-
const prefix = `${storageKey}.`;
|
|
1504
|
-
for (const [depKey, entries] of this.#subscriptions) if (depKey.startsWith(prefix)) for (const entry of entries) out.add(entry.subscription);
|
|
1505
|
-
} else {
|
|
1506
|
-
const depKey = makeDependencyKey(storageKey, fieldKey);
|
|
1507
|
-
const entries = this.#subscriptions.get(depKey);
|
|
1508
|
-
if (entries) for (const entry of entries) out.add(entry.subscription);
|
|
1509
|
-
}
|
|
1510
|
-
}
|
|
1511
|
-
#removeSubscriptionFromTree(node, subscription) {
|
|
1512
|
-
const entries = this.#subscriptions.get(node.depKey);
|
|
1513
|
-
if (entries) {
|
|
1514
|
-
for (const entry of entries) if (entry.subscription === subscription) {
|
|
1515
|
-
entries.delete(entry);
|
|
1516
|
-
break;
|
|
1517
|
-
}
|
|
1518
|
-
if (entries.size === 0) this.#subscriptions.delete(node.depKey);
|
|
1519
|
-
}
|
|
1520
|
-
for (const child of node.children.values()) this.#removeSubscriptionFromTree(child, subscription);
|
|
1521
|
-
}
|
|
1522
|
-
#parseDepKey(depKey) {
|
|
1523
|
-
return parseDependencyKey(depKey);
|
|
1559
|
+
isStale(subId) {
|
|
1560
|
+
return this.#subscriptions.get(subId)?.stale ?? false;
|
|
1524
1561
|
}
|
|
1525
1562
|
/**
|
|
1526
1563
|
* Extracts a serializable snapshot of the cache storage.
|
|
1527
|
-
* Optimistic layers are excluded because they represent transient in-flight state.
|
|
1528
1564
|
*/
|
|
1529
1565
|
extract() {
|
|
1530
1566
|
return { storage: structuredClone(this.#storage) };
|
|
@@ -1538,18 +1574,100 @@ var Cache = class {
|
|
|
1538
1574
|
...this.#storage[key],
|
|
1539
1575
|
...fields
|
|
1540
1576
|
};
|
|
1541
|
-
this.#storageView = null;
|
|
1542
1577
|
}
|
|
1543
1578
|
/**
|
|
1544
1579
|
* Clears all cache data.
|
|
1545
1580
|
*/
|
|
1546
1581
|
clear() {
|
|
1547
1582
|
this.#storage = { [RootFieldKey]: {} };
|
|
1583
|
+
this.#registry.clear();
|
|
1548
1584
|
this.#subscriptions.clear();
|
|
1585
|
+
this.#stalled.clear();
|
|
1549
1586
|
this.#stale.clear();
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
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
|
+
}
|
|
1553
1671
|
}
|
|
1554
1672
|
};
|
|
1555
1673
|
|
|
@@ -1582,7 +1700,6 @@ const cacheExchange = (options = {}) => {
|
|
|
1582
1700
|
},
|
|
1583
1701
|
io: (ops$) => {
|
|
1584
1702
|
const subscriptionHasData = /* @__PURE__ */ new Map();
|
|
1585
|
-
const resubscribe$ = require_make.makeSubject();
|
|
1586
1703
|
const refetch$ = require_make.makeSubject();
|
|
1587
1704
|
const fragment$ = require_make.pipe(ops$, require_make.filter((op) => op.variant === "request" && op.artifact.kind === "fragment"), require_make.mergeMap((op) => {
|
|
1588
1705
|
const fragmentRef = op.metadata?.fragment?.ref;
|
|
@@ -1593,11 +1710,10 @@ const cacheExchange = (options = {}) => {
|
|
|
1593
1710
|
if (isFragmentRefArray(fragmentRef)) {
|
|
1594
1711
|
const results = require_make.makeSubject();
|
|
1595
1712
|
const unsubscribes = [];
|
|
1596
|
-
const fragmentSubscriptions = [];
|
|
1597
1713
|
for (const [index, ref] of fragmentRef.entries()) {
|
|
1598
|
-
const
|
|
1599
|
-
if (
|
|
1600
|
-
const indexedPatches = patches.map((patch) => ({
|
|
1714
|
+
const listener = (notification) => {
|
|
1715
|
+
if (notification.type === "patch") {
|
|
1716
|
+
const indexedPatches = notification.patches.map((patch) => ({
|
|
1601
1717
|
...patch,
|
|
1602
1718
|
path: [index, ...patch.path]
|
|
1603
1719
|
}));
|
|
@@ -1607,21 +1723,17 @@ const cacheExchange = (options = {}) => {
|
|
|
1607
1723
|
errors: []
|
|
1608
1724
|
});
|
|
1609
1725
|
} else {
|
|
1610
|
-
const
|
|
1611
|
-
if (
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
errors: []
|
|
1618
|
-
});
|
|
1619
|
-
}
|
|
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
|
+
});
|
|
1620
1733
|
}
|
|
1621
1734
|
};
|
|
1622
|
-
const { unsubscribe
|
|
1735
|
+
const { unsubscribe } = cache.subscribeFragment(op.artifact, ref, listener);
|
|
1623
1736
|
unsubscribes.push(unsubscribe);
|
|
1624
|
-
fragmentSubscriptions.push(subscription);
|
|
1625
1737
|
}
|
|
1626
1738
|
const { data: initialData, stale: initialStale } = cache.readFragments(op.artifact, fragmentRef);
|
|
1627
1739
|
const teardown$ = require_make.pipe(ops$, require_make.filter((operation) => operation.variant === "teardown" && operation.key === op.key), require_make.tap(() => {
|
|
@@ -1641,31 +1753,25 @@ const cacheExchange = (options = {}) => {
|
|
|
1641
1753
|
errors: []
|
|
1642
1754
|
});
|
|
1643
1755
|
const results = require_make.makeSubject();
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
const patchListener = (patches) => {
|
|
1647
|
-
if (patches) results.next({
|
|
1756
|
+
const listener = (notification) => {
|
|
1757
|
+
if (notification.type === "patch") results.next({
|
|
1648
1758
|
operation: op,
|
|
1649
|
-
metadata: { cache: { patches } },
|
|
1759
|
+
metadata: { cache: { patches: notification.patches } },
|
|
1650
1760
|
errors: []
|
|
1651
1761
|
});
|
|
1652
|
-
else
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
});
|
|
1661
|
-
}
|
|
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
|
+
});
|
|
1662
1770
|
}
|
|
1663
1771
|
};
|
|
1664
|
-
const { data, stale, unsubscribe
|
|
1665
|
-
currentUnsubscribe = unsubscribe;
|
|
1666
|
-
currentSubscription = subscription;
|
|
1772
|
+
const { data, stale, unsubscribe } = cache.subscribeFragment(op.artifact, fragmentRef, listener);
|
|
1667
1773
|
const teardown$ = require_make.pipe(ops$, require_make.filter((operation) => operation.variant === "teardown" && operation.key === op.key), require_make.tap(() => {
|
|
1668
|
-
|
|
1774
|
+
unsubscribe();
|
|
1669
1775
|
results.complete();
|
|
1670
1776
|
}));
|
|
1671
1777
|
return require_make.pipe(require_make.merge(data === null ? empty() : require_make.fromValue({
|
|
@@ -1681,50 +1787,34 @@ const cacheExchange = (options = {}) => {
|
|
|
1681
1787
|
const query$ = require_make.pipe(ops$, require_make.filter((op) => op.variant === "request" && op.artifact.kind === "query" && fetchPolicy !== "network-only"), require_make.share());
|
|
1682
1788
|
return require_make.merge(fragment$, require_make.pipe(query$, require_make.mergeMap((op) => {
|
|
1683
1789
|
const results = require_make.makeSubject();
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
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({
|
|
1693
1802
|
operation: op,
|
|
1694
|
-
|
|
1803
|
+
data: staleData,
|
|
1804
|
+
metadata: { cache: { stale: true } },
|
|
1695
1805
|
errors: []
|
|
1696
1806
|
});
|
|
1697
|
-
|
|
1698
|
-
if (cache.isStale(currentSubscription)) {
|
|
1699
|
-
const { data: staleData } = cache.readQuery(op.artifact, op.variables);
|
|
1700
|
-
if (staleData !== null) results.next({
|
|
1701
|
-
operation: op,
|
|
1702
|
-
data: staleData,
|
|
1703
|
-
metadata: { cache: { stale: true } },
|
|
1704
|
-
errors: []
|
|
1705
|
-
});
|
|
1706
|
-
refetch$.next(op);
|
|
1707
|
-
}
|
|
1807
|
+
refetch$.next(op);
|
|
1708
1808
|
}
|
|
1709
|
-
}
|
|
1710
|
-
const result = cache.subscribeQuery(op.artifact, op.variables, patchListener);
|
|
1711
|
-
currentUnsubscribe = result.unsubscribe;
|
|
1712
|
-
currentSubscription = result.subscription;
|
|
1713
|
-
return result;
|
|
1809
|
+
}
|
|
1714
1810
|
};
|
|
1715
|
-
const { data, stale } =
|
|
1811
|
+
const { data, stale, unsubscribe } = cache.subscribeQuery(op.artifact, op.variables, listener);
|
|
1716
1812
|
subscriptionHasData.set(op.key, data !== null);
|
|
1717
|
-
if (data !== null) initialized = true;
|
|
1718
1813
|
const teardown$ = require_make.pipe(ops$, require_make.filter((o) => o.variant === "teardown" && o.key === op.key), require_make.tap(() => {
|
|
1719
|
-
|
|
1814
|
+
unsubscribe();
|
|
1720
1815
|
subscriptionHasData.delete(op.key);
|
|
1721
1816
|
results.complete();
|
|
1722
1817
|
}));
|
|
1723
|
-
const resubStream$ = require_make.pipe(resubscribe$.source, require_make.filter((key) => key === op.key), require_make.mergeMap(() => {
|
|
1724
|
-
doSubscribe();
|
|
1725
|
-
initialized = true;
|
|
1726
|
-
return empty();
|
|
1727
|
-
}));
|
|
1728
1818
|
const stream$ = require_make.pipe(require_make.merge(data === null ? fetchPolicy === "cache-only" ? require_make.fromValue({
|
|
1729
1819
|
operation: op,
|
|
1730
1820
|
data: null,
|
|
@@ -1734,7 +1824,7 @@ const cacheExchange = (options = {}) => {
|
|
|
1734
1824
|
data,
|
|
1735
1825
|
...stale && { metadata: { cache: { stale: true } } },
|
|
1736
1826
|
errors: []
|
|
1737
|
-
}), results.source
|
|
1827
|
+
}), results.source), require_make.takeUntil(teardown$));
|
|
1738
1828
|
if (stale) refetch$.next(op);
|
|
1739
1829
|
return stream$;
|
|
1740
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) => {
|
|
@@ -1754,7 +1844,6 @@ const cacheExchange = (options = {}) => {
|
|
|
1754
1844
|
});
|
|
1755
1845
|
}
|
|
1756
1846
|
subscriptionHasData.set(result.operation.key, true);
|
|
1757
|
-
resubscribe$.next(result.operation.key);
|
|
1758
1847
|
const { data } = cache.readQuery(result.operation.artifact, result.operation.variables);
|
|
1759
1848
|
if (data !== null) return require_make.fromValue({
|
|
1760
1849
|
...result,
|
|
@@ -1771,99 +1860,6 @@ const cacheExchange = (options = {}) => {
|
|
|
1771
1860
|
};
|
|
1772
1861
|
};
|
|
1773
1862
|
|
|
1774
|
-
//#endregion
|
|
1775
|
-
//#region src/cache/patch.ts
|
|
1776
|
-
const copyNode = (node) => Array.isArray(node) ? [...node] : { ...node };
|
|
1777
|
-
const shallowCopyPath = (root, path) => {
|
|
1778
|
-
if (path.length === 0) return root;
|
|
1779
|
-
let result = copyNode(root);
|
|
1780
|
-
const top = result;
|
|
1781
|
-
for (let i = 0; i < path.length - 1; i++) {
|
|
1782
|
-
const key = path[i];
|
|
1783
|
-
result[key] = copyNode(result[key]);
|
|
1784
|
-
result = result[key];
|
|
1785
|
-
}
|
|
1786
|
-
return top;
|
|
1787
|
-
};
|
|
1788
|
-
/**
|
|
1789
|
-
* Sets a value at a nested path within an object.
|
|
1790
|
-
* @param obj - The object to modify.
|
|
1791
|
-
* @param path - The path to the target location.
|
|
1792
|
-
* @param value - The value to set.
|
|
1793
|
-
*/
|
|
1794
|
-
const setPath = (obj, path, value) => {
|
|
1795
|
-
let current = obj;
|
|
1796
|
-
for (let i = 0; i < path.length - 1; i++) current = current[path[i]];
|
|
1797
|
-
current[path.at(-1)] = value;
|
|
1798
|
-
};
|
|
1799
|
-
/**
|
|
1800
|
-
* Gets a value at a nested path within an object.
|
|
1801
|
-
* @param obj - The object to read from.
|
|
1802
|
-
* @param path - The path to the target location.
|
|
1803
|
-
* @returns The value at the path, or the object itself if path is empty.
|
|
1804
|
-
*/
|
|
1805
|
-
const getPath = (obj, path) => {
|
|
1806
|
-
let current = obj;
|
|
1807
|
-
for (const segment of path) {
|
|
1808
|
-
if (current === void 0 || current === null) return void 0;
|
|
1809
|
-
current = current[segment];
|
|
1810
|
-
}
|
|
1811
|
-
return current;
|
|
1812
|
-
};
|
|
1813
|
-
/**
|
|
1814
|
-
* Applies cache patches to data immutably, shallow-copying only along changed paths.
|
|
1815
|
-
*/
|
|
1816
|
-
const applyPatchesImmutable = (data, patches) => {
|
|
1817
|
-
if (patches.length === 0) return data;
|
|
1818
|
-
let result = data;
|
|
1819
|
-
for (const patch of patches) if (patch.type === "set") {
|
|
1820
|
-
if (patch.path.length === 0) {
|
|
1821
|
-
result = patch.value;
|
|
1822
|
-
continue;
|
|
1823
|
-
}
|
|
1824
|
-
result = shallowCopyPath(result, patch.path);
|
|
1825
|
-
let target = result;
|
|
1826
|
-
for (let i = 0; i < patch.path.length - 1; i++) target = target[patch.path[i]];
|
|
1827
|
-
target[patch.path.at(-1)] = patch.value;
|
|
1828
|
-
} else if (patch.type === "splice") {
|
|
1829
|
-
result = shallowCopyPath(result, patch.path);
|
|
1830
|
-
let target = result;
|
|
1831
|
-
for (const segment of patch.path) target = target[segment];
|
|
1832
|
-
const arr = [...target];
|
|
1833
|
-
arr.splice(patch.index, patch.deleteCount, ...patch.items);
|
|
1834
|
-
let parent = result;
|
|
1835
|
-
for (let i = 0; i < patch.path.length - 1; i++) parent = parent[patch.path[i]];
|
|
1836
|
-
parent[patch.path.at(-1)] = arr;
|
|
1837
|
-
} else if (patch.type === "swap") {
|
|
1838
|
-
result = shallowCopyPath(result, patch.path);
|
|
1839
|
-
let target = result;
|
|
1840
|
-
for (const segment of patch.path) target = target[segment];
|
|
1841
|
-
const arr = [...target];
|
|
1842
|
-
[arr[patch.i], arr[patch.j]] = [arr[patch.j], arr[patch.i]];
|
|
1843
|
-
let parent = result;
|
|
1844
|
-
for (let i = 0; i < patch.path.length - 1; i++) parent = parent[patch.path[i]];
|
|
1845
|
-
parent[patch.path.at(-1)] = arr;
|
|
1846
|
-
}
|
|
1847
|
-
return result;
|
|
1848
|
-
};
|
|
1849
|
-
/**
|
|
1850
|
-
* Applies cache patches to a mutable target object in place.
|
|
1851
|
-
* @param target - The mutable object to apply patches to.
|
|
1852
|
-
* @param patches - The patches to apply.
|
|
1853
|
-
* @returns The new root value if a root-level set patch was applied, otherwise undefined.
|
|
1854
|
-
*/
|
|
1855
|
-
const applyPatchesMutable = (target, patches) => {
|
|
1856
|
-
let root;
|
|
1857
|
-
for (const patch of patches) if (patch.type === "set") if (patch.path.length === 0) root = patch.value;
|
|
1858
|
-
else setPath(target, patch.path, patch.value);
|
|
1859
|
-
else if (patch.type === "splice") getPath(target, patch.path).splice(patch.index, patch.deleteCount, ...patch.items);
|
|
1860
|
-
else if (patch.type === "swap") {
|
|
1861
|
-
const arr = getPath(target, patch.path);
|
|
1862
|
-
[arr[patch.i], arr[patch.j]] = [arr[patch.j], arr[patch.i]];
|
|
1863
|
-
}
|
|
1864
|
-
return root;
|
|
1865
|
-
};
|
|
1866
|
-
|
|
1867
1863
|
//#endregion
|
|
1868
1864
|
//#region src/exchanges/retry.ts
|
|
1869
1865
|
const defaultShouldRetry = (error) => isExchangeError(error, "http") && error.extensions?.statusCode !== void 0 && error.extensions.statusCode >= 500;
|
|
@@ -2054,7 +2050,7 @@ const subscriptionExchange = (options) => {
|
|
|
2054
2050
|
return require_make.pipe(require_make.make((observer) => {
|
|
2055
2051
|
let unsubscribe;
|
|
2056
2052
|
let completed = false;
|
|
2057
|
-
|
|
2053
|
+
const doSubscribe = () => {
|
|
2058
2054
|
if (completed) return;
|
|
2059
2055
|
unsubscribe = client.subscribe({
|
|
2060
2056
|
operationName: op.artifact.name,
|
|
@@ -2063,16 +2059,28 @@ const subscriptionExchange = (options) => {
|
|
|
2063
2059
|
}, {
|
|
2064
2060
|
next: (result) => {
|
|
2065
2061
|
const response = result;
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
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
|
+
}
|
|
2076
2084
|
},
|
|
2077
2085
|
error: (error) => {
|
|
2078
2086
|
observer.next({
|
|
@@ -2082,11 +2090,13 @@ const subscriptionExchange = (options) => {
|
|
|
2082
2090
|
cause: error
|
|
2083
2091
|
})]
|
|
2084
2092
|
});
|
|
2085
|
-
|
|
2093
|
+
unsubscribe = void 0;
|
|
2094
|
+
Promise.resolve().then(doSubscribe);
|
|
2086
2095
|
},
|
|
2087
2096
|
complete: observer.complete
|
|
2088
2097
|
});
|
|
2089
|
-
}
|
|
2098
|
+
};
|
|
2099
|
+
Promise.resolve().then(doSubscribe);
|
|
2090
2100
|
return () => {
|
|
2091
2101
|
completed = true;
|
|
2092
2102
|
unsubscribe?.();
|