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