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