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