@mearie/core 0.3.0 → 0.5.0
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 +160 -53
- package/dist/index.d.cts +55 -44
- package/dist/index.d.mts +55 -44
- package/dist/index.mjs +160 -53
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -318,6 +318,12 @@ const RootFieldKey = "__root";
|
|
|
318
318
|
* @internal
|
|
319
319
|
*/
|
|
320
320
|
const FragmentRefKey = "__fragmentRef";
|
|
321
|
+
/**
|
|
322
|
+
* Special key used to carry merged variable context (fragment args + operation variables)
|
|
323
|
+
* on fragment references. Used by readFragment to resolve variable-dependent field keys.
|
|
324
|
+
* @internal
|
|
325
|
+
*/
|
|
326
|
+
const FragmentVarsKey = "__fragmentVars";
|
|
321
327
|
|
|
322
328
|
//#endregion
|
|
323
329
|
//#region src/cache/utils.ts
|
|
@@ -391,6 +397,14 @@ const isFragmentRef = (value) => {
|
|
|
391
397
|
return typeof value === "object" && value !== null && FragmentRefKey in value;
|
|
392
398
|
};
|
|
393
399
|
/**
|
|
400
|
+
* Extracts the merged variable context for a specific fragment from a fragment reference.
|
|
401
|
+
* Returns the merged variables (fragment args + operation variables) if present, or an empty object.
|
|
402
|
+
* @internal
|
|
403
|
+
*/
|
|
404
|
+
const getFragmentVars = (fragmentRef, fragmentName) => {
|
|
405
|
+
return fragmentRef[FragmentVarsKey]?.[fragmentName] ?? {};
|
|
406
|
+
};
|
|
407
|
+
/**
|
|
394
408
|
* Type guard to check if a value is an array of fragment references.
|
|
395
409
|
* @internal
|
|
396
410
|
* @param value - Value to check.
|
|
@@ -501,33 +515,20 @@ const mergeFields = (target, source) => {
|
|
|
501
515
|
const makeFieldKeyFromArgs = (field, args) => {
|
|
502
516
|
return `${field}@${args && Object.keys(args).length > 0 ? stringify(args) : "{}"}`;
|
|
503
517
|
};
|
|
504
|
-
/**
|
|
505
|
-
* Converts an EntityId to an EntityKey.
|
|
506
|
-
* @internal
|
|
507
|
-
* @param typename - The GraphQL typename of the entity.
|
|
508
|
-
* @param id - The entity identifier (string, number, or composite key record).
|
|
509
|
-
* @param keyFields - Optional ordered list of key field names for composite keys.
|
|
510
|
-
* @returns An EntityKey.
|
|
511
|
-
*/
|
|
512
|
-
const resolveEntityKey = (typename, id, keyFields) => {
|
|
513
|
-
if (typeof id === "string" || typeof id === "number") return makeEntityKey(typename, [id]);
|
|
514
|
-
return makeEntityKey(typename, keyFields ? keyFields.map((f) => id[f]) : Object.values(id));
|
|
515
|
-
};
|
|
516
518
|
|
|
517
519
|
//#endregion
|
|
518
520
|
//#region src/cache/normalize.ts
|
|
519
|
-
const SKIP = Symbol();
|
|
520
521
|
const normalize = (schemaMeta, selections, storage, data, variables, accessor) => {
|
|
521
522
|
const normalizeField = (storageKey, selections, value) => {
|
|
522
523
|
if (isNullish(value)) return value;
|
|
523
524
|
if (Array.isArray(value)) return value.map((item) => normalizeField(storageKey, selections, item));
|
|
524
525
|
const data = value;
|
|
525
526
|
const typename = data.__typename;
|
|
526
|
-
|
|
527
|
+
let entityMeta = schemaMeta.entities[typename];
|
|
527
528
|
if (entityMeta) {
|
|
528
529
|
const keys = entityMeta.keyFields.map((field) => data[field]);
|
|
529
|
-
if (
|
|
530
|
-
|
|
530
|
+
if (keys.every((k) => k !== void 0 && k !== null)) storageKey = makeEntityKey(typename, keys);
|
|
531
|
+
else entityMeta = void 0;
|
|
531
532
|
}
|
|
532
533
|
const fields = {};
|
|
533
534
|
for (const selection of selections) if (selection.kind === "Field") {
|
|
@@ -535,13 +536,11 @@ const normalize = (schemaMeta, selections, storage, data, variables, accessor) =
|
|
|
535
536
|
const fieldValue = data[selection.alias ?? selection.name];
|
|
536
537
|
const oldValue = storageKey === null ? void 0 : storage[storageKey]?.[fieldKey];
|
|
537
538
|
if (storageKey !== null && (!selection.selections || isNullish(oldValue) || isNullish(fieldValue))) accessor?.(storageKey, fieldKey, oldValue, fieldValue);
|
|
538
|
-
|
|
539
|
-
if (normalized === SKIP) continue;
|
|
540
|
-
fields[fieldKey] = normalized;
|
|
539
|
+
fields[fieldKey] = selection.selections ? normalizeField(null, selection.selections, fieldValue) : fieldValue;
|
|
541
540
|
if (storageKey !== null && selection.selections && !isNullish(oldValue) && !isNullish(fieldValue) && !isEntityLink(fields[fieldKey]) && !isEqual(oldValue, fields[fieldKey])) accessor?.(storageKey, fieldKey, oldValue, fields[fieldKey]);
|
|
542
541
|
} else if (selection.kind === "FragmentSpread" || selection.kind === "InlineFragment" && selection.on === typename) {
|
|
543
542
|
const inner = normalizeField(storageKey, selection.selections, value);
|
|
544
|
-
if (
|
|
543
|
+
if (!isEntityLink(inner)) mergeFields(fields, inner);
|
|
545
544
|
}
|
|
546
545
|
if (entityMeta && storageKey !== null) {
|
|
547
546
|
const existing = storage[storageKey];
|
|
@@ -596,6 +595,17 @@ const denormalize = (selections, storage, value, variables, accessor) => {
|
|
|
596
595
|
else fields[name] = value;
|
|
597
596
|
} else if (selection.kind === "FragmentSpread") if (storageKey !== null && storageKey !== RootFieldKey) {
|
|
598
597
|
fields[FragmentRefKey] = storageKey;
|
|
598
|
+
if (selection.args) {
|
|
599
|
+
const resolvedArgs = resolveArguments(selection.args, variables);
|
|
600
|
+
const mergedVars = {
|
|
601
|
+
...variables,
|
|
602
|
+
...resolvedArgs
|
|
603
|
+
};
|
|
604
|
+
fields[FragmentVarsKey] = {
|
|
605
|
+
...fields[FragmentVarsKey],
|
|
606
|
+
[selection.name]: mergedVars
|
|
607
|
+
};
|
|
608
|
+
}
|
|
599
609
|
if (accessor) denormalize(selection.selections, storage, { [EntityLinkKey]: storageKey }, variables, accessor);
|
|
600
610
|
} else mergeFields(fields, denormalizeField(storageKey, selection.selections, value));
|
|
601
611
|
else if (selection.kind === "InlineFragment" && selection.on === data[typenameFieldKey]) mergeFields(fields, denormalizeField(storageKey, selection.selections, value));
|
|
@@ -619,9 +629,74 @@ var Cache = class {
|
|
|
619
629
|
#subscriptions = /* @__PURE__ */ new Map();
|
|
620
630
|
#memo = /* @__PURE__ */ new Map();
|
|
621
631
|
#stale = /* @__PURE__ */ new Set();
|
|
632
|
+
#optimisticKeys = [];
|
|
633
|
+
#optimisticLayers = /* @__PURE__ */ new Map();
|
|
634
|
+
#storageView = null;
|
|
622
635
|
constructor(schemaMetadata) {
|
|
623
636
|
this.#schemaMeta = schemaMetadata;
|
|
624
637
|
}
|
|
638
|
+
#getStorageView() {
|
|
639
|
+
if (this.#optimisticKeys.length === 0) return this.#storage;
|
|
640
|
+
if (this.#storageView) return this.#storageView;
|
|
641
|
+
const merged = { ...this.#storage };
|
|
642
|
+
for (const storageKey of Object.keys(this.#storage)) merged[storageKey] = { ...this.#storage[storageKey] };
|
|
643
|
+
for (const key of this.#optimisticKeys) {
|
|
644
|
+
const layer = this.#optimisticLayers.get(key);
|
|
645
|
+
if (!layer) continue;
|
|
646
|
+
for (const storageKey of Object.keys(layer.storage)) merged[storageKey] = merged[storageKey] ? {
|
|
647
|
+
...merged[storageKey],
|
|
648
|
+
...layer.storage[storageKey]
|
|
649
|
+
} : { ...layer.storage[storageKey] };
|
|
650
|
+
}
|
|
651
|
+
this.#storageView = merged;
|
|
652
|
+
return merged;
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Writes an optimistic response to a separate cache layer.
|
|
656
|
+
* The optimistic data is immediately visible in reads but does not affect the base storage.
|
|
657
|
+
* @internal
|
|
658
|
+
* @param key - Unique key identifying this optimistic mutation (typically the operation key).
|
|
659
|
+
* @param artifact - GraphQL document artifact.
|
|
660
|
+
* @param variables - Operation variables.
|
|
661
|
+
* @param data - The optimistic response data.
|
|
662
|
+
*/
|
|
663
|
+
writeOptimistic(key, artifact, variables, data) {
|
|
664
|
+
const layerStorage = { [RootFieldKey]: {} };
|
|
665
|
+
const dependencies = /* @__PURE__ */ new Set();
|
|
666
|
+
normalize(this.#schemaMeta, artifact.selections, layerStorage, data, variables, (storageKey, fieldKey) => {
|
|
667
|
+
dependencies.add(makeDependencyKey(storageKey, fieldKey));
|
|
668
|
+
});
|
|
669
|
+
this.#optimisticKeys.push(key);
|
|
670
|
+
this.#optimisticLayers.set(key, {
|
|
671
|
+
storage: layerStorage,
|
|
672
|
+
dependencies
|
|
673
|
+
});
|
|
674
|
+
this.#storageView = null;
|
|
675
|
+
const subscriptions = /* @__PURE__ */ new Set();
|
|
676
|
+
for (const depKey of dependencies) {
|
|
677
|
+
const ss = this.#subscriptions.get(depKey);
|
|
678
|
+
if (ss) for (const s of ss) subscriptions.add(s);
|
|
679
|
+
}
|
|
680
|
+
for (const subscription of subscriptions) subscription.listener();
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Removes an optimistic layer and notifies affected subscribers.
|
|
684
|
+
* @internal
|
|
685
|
+
* @param key - The key of the optimistic layer to remove.
|
|
686
|
+
*/
|
|
687
|
+
removeOptimistic(key) {
|
|
688
|
+
const layer = this.#optimisticLayers.get(key);
|
|
689
|
+
if (!layer) return;
|
|
690
|
+
this.#optimisticLayers.delete(key);
|
|
691
|
+
this.#optimisticKeys = this.#optimisticKeys.filter((k) => k !== key);
|
|
692
|
+
this.#storageView = null;
|
|
693
|
+
const subscriptions = /* @__PURE__ */ new Set();
|
|
694
|
+
for (const depKey of layer.dependencies) {
|
|
695
|
+
const ss = this.#subscriptions.get(depKey);
|
|
696
|
+
if (ss) for (const s of ss) subscriptions.add(s);
|
|
697
|
+
}
|
|
698
|
+
for (const subscription of subscriptions) subscription.listener();
|
|
699
|
+
}
|
|
625
700
|
/**
|
|
626
701
|
* Writes a query result to the cache, normalizing entities.
|
|
627
702
|
* @param artifact - GraphQL document artifact.
|
|
@@ -654,7 +729,8 @@ var Cache = class {
|
|
|
654
729
|
*/
|
|
655
730
|
readQuery(artifact, variables) {
|
|
656
731
|
let stale = false;
|
|
657
|
-
const
|
|
732
|
+
const storage = this.#getStorageView();
|
|
733
|
+
const { data, partial } = denormalize(artifact.selections, storage, storage[RootFieldKey], variables, (storageKey, fieldKey) => {
|
|
658
734
|
if (this.#stale.has(storageKey) || this.#stale.has(makeDependencyKey(storageKey, fieldKey))) stale = true;
|
|
659
735
|
});
|
|
660
736
|
if (partial) return {
|
|
@@ -679,7 +755,8 @@ var Cache = class {
|
|
|
679
755
|
*/
|
|
680
756
|
subscribeQuery(artifact, variables, listener) {
|
|
681
757
|
const dependencies = /* @__PURE__ */ new Set();
|
|
682
|
-
|
|
758
|
+
const storageView = this.#getStorageView();
|
|
759
|
+
denormalize(artifact.selections, storageView, storageView[RootFieldKey], variables, (storageKey, fieldKey) => {
|
|
683
760
|
const dependencyKey = makeDependencyKey(storageKey, fieldKey);
|
|
684
761
|
dependencies.add(dependencyKey);
|
|
685
762
|
});
|
|
@@ -694,19 +771,22 @@ var Cache = class {
|
|
|
694
771
|
*/
|
|
695
772
|
readFragment(artifact, fragmentRef) {
|
|
696
773
|
const entityKey = fragmentRef[FragmentRefKey];
|
|
697
|
-
|
|
774
|
+
const fragmentVars = getFragmentVars(fragmentRef, artifact.name);
|
|
775
|
+
const storageView = this.#getStorageView();
|
|
776
|
+
if (!storageView[entityKey]) return {
|
|
698
777
|
data: null,
|
|
699
778
|
stale: false
|
|
700
779
|
};
|
|
701
780
|
let stale = false;
|
|
702
|
-
const { data, partial } = denormalize(artifact.selections,
|
|
781
|
+
const { data, partial } = denormalize(artifact.selections, storageView, { [EntityLinkKey]: entityKey }, fragmentVars, (storageKey, fieldKey) => {
|
|
703
782
|
if (this.#stale.has(storageKey) || this.#stale.has(makeDependencyKey(storageKey, fieldKey))) stale = true;
|
|
704
783
|
});
|
|
705
784
|
if (partial) return {
|
|
706
785
|
data: null,
|
|
707
786
|
stale: false
|
|
708
787
|
};
|
|
709
|
-
const
|
|
788
|
+
const argsId = Object.keys(fragmentVars).length > 0 ? entityKey + stringify(fragmentVars) : entityKey;
|
|
789
|
+
const key = makeMemoKey("fragment", artifact.name, argsId);
|
|
710
790
|
const prev = this.#memo.get(key);
|
|
711
791
|
const result = prev === void 0 ? data : replaceEqualDeep(prev, data);
|
|
712
792
|
this.#memo.set(key, result);
|
|
@@ -717,8 +797,10 @@ var Cache = class {
|
|
|
717
797
|
}
|
|
718
798
|
subscribeFragment(artifact, fragmentRef, listener) {
|
|
719
799
|
const entityKey = fragmentRef[FragmentRefKey];
|
|
800
|
+
const fragmentVars = getFragmentVars(fragmentRef, artifact.name);
|
|
720
801
|
const dependencies = /* @__PURE__ */ new Set();
|
|
721
|
-
|
|
802
|
+
const storageView = this.#getStorageView();
|
|
803
|
+
denormalize(artifact.selections, storageView, { [EntityLinkKey]: entityKey }, fragmentVars, (storageKey, fieldKey) => {
|
|
722
804
|
const dependencyKey = makeDependencyKey(storageKey, fieldKey);
|
|
723
805
|
dependencies.add(dependencyKey);
|
|
724
806
|
});
|
|
@@ -748,9 +830,11 @@ var Cache = class {
|
|
|
748
830
|
}
|
|
749
831
|
subscribeFragments(artifact, fragmentRefs, listener) {
|
|
750
832
|
const dependencies = /* @__PURE__ */ new Set();
|
|
833
|
+
const storageView = this.#getStorageView();
|
|
751
834
|
for (const ref of fragmentRefs) {
|
|
752
835
|
const entityKey = ref[FragmentRefKey];
|
|
753
|
-
|
|
836
|
+
const fragmentVars = getFragmentVars(ref, artifact.name);
|
|
837
|
+
denormalize(artifact.selections, storageView, { [EntityLinkKey]: entityKey }, fragmentVars, (storageKey, fieldKey) => {
|
|
754
838
|
dependencies.add(makeDependencyKey(storageKey, fieldKey));
|
|
755
839
|
});
|
|
756
840
|
}
|
|
@@ -762,8 +846,8 @@ var Cache = class {
|
|
|
762
846
|
*/
|
|
763
847
|
invalidate(...targets) {
|
|
764
848
|
const subscriptions = /* @__PURE__ */ new Set();
|
|
765
|
-
for (const target of targets) if (target.__typename === "Query") if ("field" in target) {
|
|
766
|
-
const fieldKey = makeFieldKeyFromArgs(target
|
|
849
|
+
for (const target of targets) if (target.__typename === "Query") if ("$field" in target) {
|
|
850
|
+
const fieldKey = makeFieldKeyFromArgs(target.$field, target.$args);
|
|
767
851
|
const depKey = makeDependencyKey(RootFieldKey, fieldKey);
|
|
768
852
|
this.#stale.add(depKey);
|
|
769
853
|
this.#collectSubscriptions(RootFieldKey, fieldKey, subscriptions);
|
|
@@ -771,32 +855,39 @@ var Cache = class {
|
|
|
771
855
|
this.#stale.add(RootFieldKey);
|
|
772
856
|
this.#collectSubscriptions(RootFieldKey, void 0, subscriptions);
|
|
773
857
|
}
|
|
774
|
-
else
|
|
775
|
-
const
|
|
776
|
-
if (
|
|
777
|
-
const
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
this.#stale.add(entityKey);
|
|
782
|
-
this.#collectSubscriptions(entityKey, void 0, subscriptions);
|
|
783
|
-
}
|
|
784
|
-
} else {
|
|
785
|
-
const prefix = `${target.__typename}:`;
|
|
786
|
-
for (const key of Object.keys(this.#storage)) if (key.startsWith(prefix)) {
|
|
787
|
-
const entityKey = key;
|
|
788
|
-
if ("field" in target) {
|
|
789
|
-
const fieldKey = makeFieldKeyFromArgs(target.field, target.args);
|
|
858
|
+
else {
|
|
859
|
+
const keyFields = this.#schemaMeta.entities[target.__typename]?.keyFields;
|
|
860
|
+
if (keyFields && this.#hasKeyFields(target, keyFields)) {
|
|
861
|
+
const keyValues = keyFields.map((f) => target[f]);
|
|
862
|
+
const entityKey = makeEntityKey(target.__typename, keyValues);
|
|
863
|
+
if ("$field" in target) {
|
|
864
|
+
const fieldKey = makeFieldKeyFromArgs(target.$field, target.$args);
|
|
790
865
|
this.#stale.add(makeDependencyKey(entityKey, fieldKey));
|
|
791
866
|
this.#collectSubscriptions(entityKey, fieldKey, subscriptions);
|
|
792
867
|
} else {
|
|
793
868
|
this.#stale.add(entityKey);
|
|
794
869
|
this.#collectSubscriptions(entityKey, void 0, subscriptions);
|
|
795
870
|
}
|
|
871
|
+
} else {
|
|
872
|
+
const prefix = `${target.__typename}:`;
|
|
873
|
+
for (const key of Object.keys(this.#storage)) if (key.startsWith(prefix)) {
|
|
874
|
+
const entityKey = key;
|
|
875
|
+
if ("$field" in target) {
|
|
876
|
+
const fieldKey = makeFieldKeyFromArgs(target.$field, target.$args);
|
|
877
|
+
this.#stale.add(makeDependencyKey(entityKey, fieldKey));
|
|
878
|
+
this.#collectSubscriptions(entityKey, fieldKey, subscriptions);
|
|
879
|
+
} else {
|
|
880
|
+
this.#stale.add(entityKey);
|
|
881
|
+
this.#collectSubscriptions(entityKey, void 0, subscriptions);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
796
884
|
}
|
|
797
885
|
}
|
|
798
886
|
for (const subscription of subscriptions) subscription.listener();
|
|
799
887
|
}
|
|
888
|
+
#hasKeyFields(target, keyFields) {
|
|
889
|
+
return keyFields.every((f) => f in target);
|
|
890
|
+
}
|
|
800
891
|
#collectSubscriptions(storageKey, fieldKey, out) {
|
|
801
892
|
if (fieldKey === void 0) {
|
|
802
893
|
const prefix = `${storageKey}.`;
|
|
@@ -824,6 +915,7 @@ var Cache = class {
|
|
|
824
915
|
}
|
|
825
916
|
/**
|
|
826
917
|
* Extracts a serializable snapshot of the cache storage and structural sharing state.
|
|
918
|
+
* Optimistic layers are excluded because they represent transient in-flight state.
|
|
827
919
|
*/
|
|
828
920
|
extract() {
|
|
829
921
|
return {
|
|
@@ -841,6 +933,7 @@ var Cache = class {
|
|
|
841
933
|
...fields
|
|
842
934
|
};
|
|
843
935
|
for (const [key, value] of Object.entries(memo)) this.#memo.set(key, value);
|
|
936
|
+
this.#storageView = null;
|
|
844
937
|
}
|
|
845
938
|
/**
|
|
846
939
|
* Clears all cache data.
|
|
@@ -850,6 +943,9 @@ var Cache = class {
|
|
|
850
943
|
this.#subscriptions.clear();
|
|
851
944
|
this.#memo.clear();
|
|
852
945
|
this.#stale.clear();
|
|
946
|
+
this.#optimisticKeys = [];
|
|
947
|
+
this.#optimisticLayers.clear();
|
|
948
|
+
this.#storageView = null;
|
|
853
949
|
}
|
|
854
950
|
};
|
|
855
951
|
|
|
@@ -882,10 +978,10 @@ const cacheExchange = (options = {}) => {
|
|
|
882
978
|
},
|
|
883
979
|
io: (ops$) => {
|
|
884
980
|
const fragment$ = require_make.pipe(ops$, require_make.filter((op) => op.variant === "request" && op.artifact.kind === "fragment"), require_make.mergeMap((op) => {
|
|
885
|
-
const fragmentRef = op.metadata?.
|
|
981
|
+
const fragmentRef = op.metadata?.fragment?.ref;
|
|
886
982
|
if (!fragmentRef) return require_make.fromValue({
|
|
887
983
|
operation: op,
|
|
888
|
-
errors: [new ExchangeError("Fragment operation missing
|
|
984
|
+
errors: [new ExchangeError("Fragment operation missing fragment.ref in metadata. This usually happens when the wrong fragment reference was passed.", { exchangeName: "cache" })]
|
|
889
985
|
});
|
|
890
986
|
if (isFragmentRefArray(fragmentRef)) {
|
|
891
987
|
const trigger = require_make.makeSubject();
|
|
@@ -917,7 +1013,9 @@ const cacheExchange = (options = {}) => {
|
|
|
917
1013
|
errors: []
|
|
918
1014
|
})));
|
|
919
1015
|
}));
|
|
920
|
-
const nonCache$ = require_make.pipe(ops$, require_make.filter((op) => op.variant === "request" && (op.artifact.kind === "mutation" || op.artifact.kind === "subscription" || op.artifact.kind === "query" && fetchPolicy === "network-only")))
|
|
1016
|
+
const nonCache$ = require_make.pipe(ops$, require_make.filter((op) => op.variant === "request" && (op.artifact.kind === "mutation" || op.artifact.kind === "subscription" || op.artifact.kind === "query" && fetchPolicy === "network-only")), require_make.tap((op) => {
|
|
1017
|
+
if (op.artifact.kind === "mutation" && op.metadata?.cache?.optimisticResponse) cache.writeOptimistic(op.key, op.artifact, op.variables, op.metadata.cache.optimisticResponse);
|
|
1018
|
+
}));
|
|
921
1019
|
const query$ = require_make.pipe(ops$, require_make.filter((op) => op.variant === "request" && op.artifact.kind === "query" && fetchPolicy !== "network-only"), require_make.share());
|
|
922
1020
|
const refetch$ = require_make.makeSubject();
|
|
923
1021
|
return require_make.merge(fragment$, require_make.pipe(query$, require_make.mergeMap((op) => {
|
|
@@ -960,9 +1058,18 @@ const cacheExchange = (options = {}) => {
|
|
|
960
1058
|
}), require_make.filter(() => fetchPolicy === "cache-only" || fetchPolicy === "cache-and-network" || fetchPolicy === "cache-first")), require_make.pipe(require_make.merge(nonCache$, require_make.pipe(query$, require_make.filter((op) => {
|
|
961
1059
|
const { data } = cache.readQuery(op.artifact, op.variables);
|
|
962
1060
|
return fetchPolicy === "cache-and-network" || data === null;
|
|
963
|
-
})), require_make.pipe(ops$, require_make.filter((op) => op.variant === "teardown")), refetch$.source), forward, require_make.
|
|
1061
|
+
})), require_make.pipe(ops$, require_make.filter((op) => op.variant === "teardown")), refetch$.source), forward, require_make.mergeMap((result) => {
|
|
1062
|
+
if (result.operation.variant === "request" && result.operation.artifact.kind === "mutation" && result.operation.metadata?.cache?.optimisticResponse) cache.removeOptimistic(result.operation.key);
|
|
964
1063
|
if (result.operation.variant === "request" && result.data) cache.writeQuery(result.operation.artifact, result.operation.variables, result.data);
|
|
965
|
-
|
|
1064
|
+
if (result.operation.variant !== "request" || result.operation.artifact.kind !== "query" || fetchPolicy === "network-only" || !!(result.errors && result.errors.length > 0)) return require_make.fromValue(result);
|
|
1065
|
+
const { data } = cache.readQuery(result.operation.artifact, result.operation.variables);
|
|
1066
|
+
if (data !== null) return empty();
|
|
1067
|
+
return require_make.fromValue({
|
|
1068
|
+
operation: result.operation,
|
|
1069
|
+
data: void 0,
|
|
1070
|
+
errors: [new ExchangeError("Cache failed to denormalize the network response. This is likely a bug in the cache normalizer.", { exchangeName: "cache" })]
|
|
1071
|
+
});
|
|
1072
|
+
})));
|
|
966
1073
|
}
|
|
967
1074
|
};
|
|
968
1075
|
};
|
|
@@ -1014,10 +1121,10 @@ const fragmentExchange = () => {
|
|
|
1014
1121
|
name: "fragment",
|
|
1015
1122
|
io: (ops$) => {
|
|
1016
1123
|
return require_make.merge(require_make.pipe(ops$, require_make.filter((op) => op.variant === "request" && op.artifact.kind === "fragment"), require_make.map((op) => {
|
|
1017
|
-
const fragmentRef = op.metadata.
|
|
1124
|
+
const fragmentRef = op.metadata.fragment?.ref;
|
|
1018
1125
|
if (!fragmentRef) return {
|
|
1019
1126
|
operation: op,
|
|
1020
|
-
errors: [new ExchangeError("Fragment operation missing
|
|
1127
|
+
errors: [new ExchangeError("Fragment operation missing fragment.ref in metadata. This usually happens when the wrong fragment reference was passed.", { exchangeName: "fragment" })]
|
|
1021
1128
|
};
|
|
1022
1129
|
return {
|
|
1023
1130
|
operation: op,
|
|
@@ -1403,7 +1510,7 @@ var Client = class {
|
|
|
1403
1510
|
key: this.createOperationKey(),
|
|
1404
1511
|
metadata: {
|
|
1405
1512
|
...options?.metadata,
|
|
1406
|
-
fragmentRef
|
|
1513
|
+
fragment: { ref: fragmentRef }
|
|
1407
1514
|
},
|
|
1408
1515
|
artifact,
|
|
1409
1516
|
variables: {}
|
package/dist/index.d.cts
CHANGED
|
@@ -68,8 +68,8 @@ type QueryOptions<T extends Artifact$1<'query'> = Artifact$1<'query'>> = {
|
|
|
68
68
|
initialData?: DataOf$1<T>;
|
|
69
69
|
metadata?: OperationMetadata;
|
|
70
70
|
};
|
|
71
|
-
type MutationOptions = {
|
|
72
|
-
metadata?: OperationMetadata
|
|
71
|
+
type MutationOptions<T extends Artifact$1<'mutation'> = Artifact$1<'mutation'>> = {
|
|
72
|
+
metadata?: OperationMetadata<T>;
|
|
73
73
|
};
|
|
74
74
|
type SubscriptionOptions = {
|
|
75
75
|
metadata?: OperationMetadata;
|
|
@@ -93,31 +93,31 @@ declare class Client<TMeta extends SchemaMeta$1 = SchemaMeta$1> {
|
|
|
93
93
|
get schema(): TMeta;
|
|
94
94
|
get scalars(): ScalarsConfig<TMeta> | undefined;
|
|
95
95
|
private createOperationKey;
|
|
96
|
-
createOperation(artifact: Artifact$1, variables?: unknown, metadata?:
|
|
96
|
+
createOperation(artifact: Artifact$1, variables?: unknown, metadata?: Record<string, unknown>): Operation;
|
|
97
97
|
executeOperation(operation: Operation): Source<OperationResult>;
|
|
98
98
|
executeQuery<T extends Artifact$1<'query'>>(artifact: T, ...[variables, options]: VariablesOf$1<T> extends undefined ? [undefined?, QueryOptions<T>?] : [VariablesOf$1<T>, QueryOptions<T>?]): Source<OperationResult>;
|
|
99
|
-
executeMutation<T extends Artifact$1<'mutation'>>(artifact: T, ...[variables, options]: VariablesOf$1<T> extends undefined ? [undefined?, MutationOptions
|
|
99
|
+
executeMutation<T extends Artifact$1<'mutation'>>(artifact: T, ...[variables, options]: VariablesOf$1<T> extends undefined ? [undefined?, MutationOptions<T>?] : [VariablesOf$1<T>, MutationOptions<T>?]): Source<OperationResult>;
|
|
100
100
|
executeSubscription<T extends Artifact$1<'subscription'>>(artifact: T, ...[variables, options]: VariablesOf$1<T> extends undefined ? [undefined?, SubscriptionOptions?] : [VariablesOf$1<T>, SubscriptionOptions?]): Source<OperationResult>;
|
|
101
101
|
executeFragment<T extends Artifact$1<'fragment'>>(artifact: T, fragmentRef: FragmentRefs$1<T['name']> | FragmentRefs$1<T['name']>[], options?: FragmentOptions): Source<OperationResult>;
|
|
102
102
|
query<T extends Artifact$1<'query'>>(artifact: T, ...[variables, options]: VariablesOf$1<T> extends undefined ? [undefined?, QueryOptions<T>?] : [VariablesOf$1<T>, QueryOptions<T>?]): Promise<DataOf$1<T>>;
|
|
103
|
-
mutation<T extends Artifact$1<'mutation'>>(artifact: T, ...[variables, options]: VariablesOf$1<T> extends undefined ? [undefined?, MutationOptions
|
|
103
|
+
mutation<T extends Artifact$1<'mutation'>>(artifact: T, ...[variables, options]: VariablesOf$1<T> extends undefined ? [undefined?, MutationOptions<T>?] : [VariablesOf$1<T>, MutationOptions<T>?]): Promise<DataOf$1<T>>;
|
|
104
104
|
/**
|
|
105
105
|
* Returns the extension registered by a named exchange.
|
|
106
106
|
* @param name - The exchange name.
|
|
107
107
|
* @returns The extension object provided by the exchange.
|
|
108
108
|
*/
|
|
109
|
-
extension<TName extends keyof ExchangeExtensionMap
|
|
109
|
+
extension<TName extends keyof ExchangeExtensionMap<TMeta>>(name: TName): ExchangeExtensionMap<TMeta>[TName];
|
|
110
110
|
extension(name: string): unknown;
|
|
111
|
-
maybeExtension<TName extends keyof ExchangeExtensionMap
|
|
111
|
+
maybeExtension<TName extends keyof ExchangeExtensionMap<TMeta>>(name: TName): ExchangeExtensionMap<TMeta>[TName] | undefined;
|
|
112
112
|
maybeExtension(name: string): unknown;
|
|
113
113
|
dispose(): void;
|
|
114
114
|
}
|
|
115
115
|
declare const createClient: <T extends SchemaMeta$1>(config: ClientOptions<T>) => Client<T>;
|
|
116
116
|
//#endregion
|
|
117
117
|
//#region src/exchange.d.ts
|
|
118
|
-
interface OperationMetadataMap {}
|
|
118
|
+
interface OperationMetadataMap<T extends Artifact$1 = Artifact$1> {}
|
|
119
119
|
interface OperationResultMetadataMap {}
|
|
120
|
-
type OperationMetadata = { [K in keyof OperationMetadataMap]?: OperationMetadataMap[K] } & Record<string, unknown>;
|
|
120
|
+
type OperationMetadata<T extends Artifact$1 = Artifact$1> = { [K in keyof OperationMetadataMap<T>]?: OperationMetadataMap<T>[K] } & Record<string, unknown>;
|
|
121
121
|
type BaseOperation = {
|
|
122
122
|
key: string;
|
|
123
123
|
metadata: OperationMetadataMap & Record<string, unknown>;
|
|
@@ -143,14 +143,14 @@ type ExchangeInput<TMeta extends SchemaMeta$1 = SchemaMeta$1> = {
|
|
|
143
143
|
client: Client<TMeta>;
|
|
144
144
|
};
|
|
145
145
|
type ExchangeIO = (operations: Source<Operation>) => Source<OperationResult>;
|
|
146
|
-
interface ExchangeExtensionMap {}
|
|
147
|
-
type ExchangeResult<TName extends keyof ExchangeExtensionMap | (string & {}) = string> = {
|
|
146
|
+
interface ExchangeExtensionMap<TMeta extends SchemaMeta$1 = SchemaMeta$1> {}
|
|
147
|
+
type ExchangeResult<TName extends keyof ExchangeExtensionMap | (string & {}) = string, TMeta extends SchemaMeta$1 = SchemaMeta$1> = {
|
|
148
148
|
name: TName;
|
|
149
149
|
io: ExchangeIO;
|
|
150
|
-
} & (TName extends keyof ExchangeExtensionMap ? {
|
|
151
|
-
extension: ExchangeExtensionMap[TName];
|
|
150
|
+
} & (TName extends keyof ExchangeExtensionMap<TMeta> ? {
|
|
151
|
+
extension: ExchangeExtensionMap<TMeta>[TName];
|
|
152
152
|
} : {});
|
|
153
|
-
type Exchange<TName extends keyof ExchangeExtensionMap | (string & {}) = string> = <TMeta extends SchemaMeta$1 = SchemaMeta$1>(input: ExchangeInput<TMeta>) => ExchangeResult<TName>;
|
|
153
|
+
type Exchange<TName extends keyof ExchangeExtensionMap | (string & {}) = string> = <TMeta extends SchemaMeta$1 = SchemaMeta$1>(input: ExchangeInput<TMeta>) => ExchangeResult<TName, TMeta>;
|
|
154
154
|
//#endregion
|
|
155
155
|
//#region src/exchanges/http.d.ts
|
|
156
156
|
declare module '@mearie/core' {
|
|
@@ -195,34 +195,38 @@ declare module '@mearie/core' {
|
|
|
195
195
|
declare const dedupExchange: () => Exchange;
|
|
196
196
|
//#endregion
|
|
197
197
|
//#region src/cache/types.d.ts
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
} | {
|
|
209
|
-
__typename:
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
198
|
+
type EntityTypes<TMeta extends SchemaMeta$1> = NonNullable<TMeta[' $entityTypes']>;
|
|
199
|
+
type QueryFields<TMeta extends SchemaMeta$1> = NonNullable<TMeta[' $queryFields']>;
|
|
200
|
+
type KeyFieldsOf<E> = E extends {
|
|
201
|
+
keyFields: infer KF;
|
|
202
|
+
} ? KF : Record<string, unknown>;
|
|
203
|
+
type FieldsOf<E> = E extends {
|
|
204
|
+
fields: infer F extends string;
|
|
205
|
+
} ? F : string;
|
|
206
|
+
type EntityInvalidateTarget<Entities> = { [K in keyof Entities & string]: {
|
|
207
|
+
__typename: K;
|
|
208
|
+
} | ({
|
|
209
|
+
__typename: K;
|
|
210
|
+
} & KeyFieldsOf<Entities[K]>) | {
|
|
211
|
+
__typename: K;
|
|
212
|
+
$field: FieldsOf<Entities[K]>;
|
|
213
|
+
$args?: Record<string, unknown>;
|
|
214
|
+
} | ({
|
|
215
|
+
__typename: K;
|
|
216
|
+
$field: FieldsOf<Entities[K]>;
|
|
217
|
+
$args?: Record<string, unknown>;
|
|
218
|
+
} & KeyFieldsOf<Entities[K]>) }[keyof Entities & string];
|
|
219
|
+
type QueryInvalidateTarget<QF extends string> = {
|
|
214
220
|
__typename: 'Query';
|
|
215
221
|
} | {
|
|
216
222
|
__typename: 'Query';
|
|
217
|
-
field:
|
|
218
|
-
args?: Record<string, unknown>;
|
|
219
|
-
} | {
|
|
220
|
-
__typename: string;
|
|
221
|
-
} | {
|
|
222
|
-
__typename: string;
|
|
223
|
-
field: string;
|
|
224
|
-
args?: Record<string, unknown>;
|
|
223
|
+
$field: QF;
|
|
224
|
+
$args?: Record<string, unknown>;
|
|
225
225
|
};
|
|
226
|
+
/**
|
|
227
|
+
* Target specification for cache invalidation operations.
|
|
228
|
+
*/
|
|
229
|
+
type InvalidateTarget<TMeta extends SchemaMeta$1 = SchemaMeta$1> = EntityInvalidateTarget<EntityTypes<TMeta>> | QueryInvalidateTarget<QueryFields<TMeta>>;
|
|
226
230
|
/**
|
|
227
231
|
* Opaque type representing a serializable cache snapshot.
|
|
228
232
|
*/
|
|
@@ -232,17 +236,22 @@ type CacheSnapshot = {
|
|
|
232
236
|
/**
|
|
233
237
|
* Operations available for programmatic cache manipulation.
|
|
234
238
|
*/
|
|
235
|
-
type CacheOperations = {
|
|
239
|
+
type CacheOperations<TMeta extends SchemaMeta$1 = SchemaMeta$1> = {
|
|
236
240
|
extract(): CacheSnapshot;
|
|
237
241
|
hydrate(data: CacheSnapshot): void;
|
|
238
|
-
invalidate(...targets: InvalidateTarget[]): void;
|
|
242
|
+
invalidate(...targets: InvalidateTarget<TMeta>[]): void;
|
|
239
243
|
clear(): void;
|
|
240
244
|
};
|
|
241
245
|
//#endregion
|
|
242
246
|
//#region src/exchanges/cache.d.ts
|
|
243
247
|
declare module '@mearie/core' {
|
|
244
|
-
interface ExchangeExtensionMap {
|
|
245
|
-
cache: CacheOperations
|
|
248
|
+
interface ExchangeExtensionMap<TMeta extends SchemaMeta$1> {
|
|
249
|
+
cache: CacheOperations<TMeta>;
|
|
250
|
+
}
|
|
251
|
+
interface OperationMetadataMap<T extends Artifact$1> {
|
|
252
|
+
cache?: {
|
|
253
|
+
optimisticResponse?: T extends Artifact$1<'mutation'> ? DataOf$1<T> : never;
|
|
254
|
+
};
|
|
246
255
|
}
|
|
247
256
|
interface OperationResultMetadataMap {
|
|
248
257
|
cache?: {
|
|
@@ -274,7 +283,9 @@ declare const retryExchange: (options?: RetryOptions) => Exchange;
|
|
|
274
283
|
//#region src/exchanges/fragment.d.ts
|
|
275
284
|
declare module '@mearie/core' {
|
|
276
285
|
interface OperationMetadataMap {
|
|
277
|
-
|
|
286
|
+
fragment?: {
|
|
287
|
+
ref?: unknown;
|
|
288
|
+
};
|
|
278
289
|
}
|
|
279
290
|
}
|
|
280
291
|
declare const fragmentExchange: () => Exchange;
|
|
@@ -351,4 +362,4 @@ declare class RequiredFieldError extends Error {
|
|
|
351
362
|
*/
|
|
352
363
|
declare const stringify: (value: unknown) => string;
|
|
353
364
|
//#endregion
|
|
354
|
-
export { AggregatedError, type Artifact, type ArtifactKind, type CacheOptions, type CacheSnapshot, Client, type ClientOptions, type DataOf, type Exchange, ExchangeError, type ExchangeErrorExtensionsMap, type ExchangeExtensionMap, type ExchangeIO, type ExchangeResult, type FragmentOptions, type FragmentRefs, GraphQLError, type HttpOptions, type MutationOptions, type Operation, type OperationError, type OperationMetadata, type OperationMetadataMap, type OperationResult, type OperationResultMetadataMap, type QueryOptions, type RequiredAction, RequiredFieldError, type RetryOptions, type SchemaMeta, type SubscriptionClient, type SubscriptionExchangeOptions, type SubscriptionOptions, type VariablesOf, cacheExchange, createClient, dedupExchange, fragmentExchange, httpExchange, isAggregatedError, isExchangeError, isGraphQLError, requiredExchange, retryExchange, stringify, subscriptionExchange };
|
|
365
|
+
export { AggregatedError, type Artifact, type ArtifactKind, type CacheOperations, type CacheOptions, type CacheSnapshot, Client, type ClientOptions, type DataOf, type Exchange, ExchangeError, type ExchangeErrorExtensionsMap, type ExchangeExtensionMap, type ExchangeIO, type ExchangeResult, type FragmentOptions, type FragmentRefs, GraphQLError, type HttpOptions, type InvalidateTarget, type MutationOptions, type Operation, type OperationError, type OperationMetadata, type OperationMetadataMap, type OperationResult, type OperationResultMetadataMap, type QueryOptions, type RequiredAction, RequiredFieldError, type RetryOptions, type SchemaMeta, type SubscriptionClient, type SubscriptionExchangeOptions, type SubscriptionOptions, type VariablesOf, cacheExchange, createClient, dedupExchange, fragmentExchange, httpExchange, isAggregatedError, isExchangeError, isGraphQLError, requiredExchange, retryExchange, stringify, subscriptionExchange };
|
package/dist/index.d.mts
CHANGED
|
@@ -68,8 +68,8 @@ type QueryOptions<T extends Artifact$1<'query'> = Artifact$1<'query'>> = {
|
|
|
68
68
|
initialData?: DataOf$1<T>;
|
|
69
69
|
metadata?: OperationMetadata;
|
|
70
70
|
};
|
|
71
|
-
type MutationOptions = {
|
|
72
|
-
metadata?: OperationMetadata
|
|
71
|
+
type MutationOptions<T extends Artifact$1<'mutation'> = Artifact$1<'mutation'>> = {
|
|
72
|
+
metadata?: OperationMetadata<T>;
|
|
73
73
|
};
|
|
74
74
|
type SubscriptionOptions = {
|
|
75
75
|
metadata?: OperationMetadata;
|
|
@@ -93,31 +93,31 @@ declare class Client<TMeta extends SchemaMeta$1 = SchemaMeta$1> {
|
|
|
93
93
|
get schema(): TMeta;
|
|
94
94
|
get scalars(): ScalarsConfig<TMeta> | undefined;
|
|
95
95
|
private createOperationKey;
|
|
96
|
-
createOperation(artifact: Artifact$1, variables?: unknown, metadata?:
|
|
96
|
+
createOperation(artifact: Artifact$1, variables?: unknown, metadata?: Record<string, unknown>): Operation;
|
|
97
97
|
executeOperation(operation: Operation): Source<OperationResult>;
|
|
98
98
|
executeQuery<T extends Artifact$1<'query'>>(artifact: T, ...[variables, options]: VariablesOf$1<T> extends undefined ? [undefined?, QueryOptions<T>?] : [VariablesOf$1<T>, QueryOptions<T>?]): Source<OperationResult>;
|
|
99
|
-
executeMutation<T extends Artifact$1<'mutation'>>(artifact: T, ...[variables, options]: VariablesOf$1<T> extends undefined ? [undefined?, MutationOptions
|
|
99
|
+
executeMutation<T extends Artifact$1<'mutation'>>(artifact: T, ...[variables, options]: VariablesOf$1<T> extends undefined ? [undefined?, MutationOptions<T>?] : [VariablesOf$1<T>, MutationOptions<T>?]): Source<OperationResult>;
|
|
100
100
|
executeSubscription<T extends Artifact$1<'subscription'>>(artifact: T, ...[variables, options]: VariablesOf$1<T> extends undefined ? [undefined?, SubscriptionOptions?] : [VariablesOf$1<T>, SubscriptionOptions?]): Source<OperationResult>;
|
|
101
101
|
executeFragment<T extends Artifact$1<'fragment'>>(artifact: T, fragmentRef: FragmentRefs$1<T['name']> | FragmentRefs$1<T['name']>[], options?: FragmentOptions): Source<OperationResult>;
|
|
102
102
|
query<T extends Artifact$1<'query'>>(artifact: T, ...[variables, options]: VariablesOf$1<T> extends undefined ? [undefined?, QueryOptions<T>?] : [VariablesOf$1<T>, QueryOptions<T>?]): Promise<DataOf$1<T>>;
|
|
103
|
-
mutation<T extends Artifact$1<'mutation'>>(artifact: T, ...[variables, options]: VariablesOf$1<T> extends undefined ? [undefined?, MutationOptions
|
|
103
|
+
mutation<T extends Artifact$1<'mutation'>>(artifact: T, ...[variables, options]: VariablesOf$1<T> extends undefined ? [undefined?, MutationOptions<T>?] : [VariablesOf$1<T>, MutationOptions<T>?]): Promise<DataOf$1<T>>;
|
|
104
104
|
/**
|
|
105
105
|
* Returns the extension registered by a named exchange.
|
|
106
106
|
* @param name - The exchange name.
|
|
107
107
|
* @returns The extension object provided by the exchange.
|
|
108
108
|
*/
|
|
109
|
-
extension<TName extends keyof ExchangeExtensionMap
|
|
109
|
+
extension<TName extends keyof ExchangeExtensionMap<TMeta>>(name: TName): ExchangeExtensionMap<TMeta>[TName];
|
|
110
110
|
extension(name: string): unknown;
|
|
111
|
-
maybeExtension<TName extends keyof ExchangeExtensionMap
|
|
111
|
+
maybeExtension<TName extends keyof ExchangeExtensionMap<TMeta>>(name: TName): ExchangeExtensionMap<TMeta>[TName] | undefined;
|
|
112
112
|
maybeExtension(name: string): unknown;
|
|
113
113
|
dispose(): void;
|
|
114
114
|
}
|
|
115
115
|
declare const createClient: <T extends SchemaMeta$1>(config: ClientOptions<T>) => Client<T>;
|
|
116
116
|
//#endregion
|
|
117
117
|
//#region src/exchange.d.ts
|
|
118
|
-
interface OperationMetadataMap {}
|
|
118
|
+
interface OperationMetadataMap<T extends Artifact$1 = Artifact$1> {}
|
|
119
119
|
interface OperationResultMetadataMap {}
|
|
120
|
-
type OperationMetadata = { [K in keyof OperationMetadataMap]?: OperationMetadataMap[K] } & Record<string, unknown>;
|
|
120
|
+
type OperationMetadata<T extends Artifact$1 = Artifact$1> = { [K in keyof OperationMetadataMap<T>]?: OperationMetadataMap<T>[K] } & Record<string, unknown>;
|
|
121
121
|
type BaseOperation = {
|
|
122
122
|
key: string;
|
|
123
123
|
metadata: OperationMetadataMap & Record<string, unknown>;
|
|
@@ -143,14 +143,14 @@ type ExchangeInput<TMeta extends SchemaMeta$1 = SchemaMeta$1> = {
|
|
|
143
143
|
client: Client<TMeta>;
|
|
144
144
|
};
|
|
145
145
|
type ExchangeIO = (operations: Source<Operation>) => Source<OperationResult>;
|
|
146
|
-
interface ExchangeExtensionMap {}
|
|
147
|
-
type ExchangeResult<TName extends keyof ExchangeExtensionMap | (string & {}) = string> = {
|
|
146
|
+
interface ExchangeExtensionMap<TMeta extends SchemaMeta$1 = SchemaMeta$1> {}
|
|
147
|
+
type ExchangeResult<TName extends keyof ExchangeExtensionMap | (string & {}) = string, TMeta extends SchemaMeta$1 = SchemaMeta$1> = {
|
|
148
148
|
name: TName;
|
|
149
149
|
io: ExchangeIO;
|
|
150
|
-
} & (TName extends keyof ExchangeExtensionMap ? {
|
|
151
|
-
extension: ExchangeExtensionMap[TName];
|
|
150
|
+
} & (TName extends keyof ExchangeExtensionMap<TMeta> ? {
|
|
151
|
+
extension: ExchangeExtensionMap<TMeta>[TName];
|
|
152
152
|
} : {});
|
|
153
|
-
type Exchange<TName extends keyof ExchangeExtensionMap | (string & {}) = string> = <TMeta extends SchemaMeta$1 = SchemaMeta$1>(input: ExchangeInput<TMeta>) => ExchangeResult<TName>;
|
|
153
|
+
type Exchange<TName extends keyof ExchangeExtensionMap | (string & {}) = string> = <TMeta extends SchemaMeta$1 = SchemaMeta$1>(input: ExchangeInput<TMeta>) => ExchangeResult<TName, TMeta>;
|
|
154
154
|
//#endregion
|
|
155
155
|
//#region src/exchanges/http.d.ts
|
|
156
156
|
declare module '@mearie/core' {
|
|
@@ -195,34 +195,38 @@ declare module '@mearie/core' {
|
|
|
195
195
|
declare const dedupExchange: () => Exchange;
|
|
196
196
|
//#endregion
|
|
197
197
|
//#region src/cache/types.d.ts
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
} | {
|
|
209
|
-
__typename:
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
198
|
+
type EntityTypes<TMeta extends SchemaMeta$1> = NonNullable<TMeta[' $entityTypes']>;
|
|
199
|
+
type QueryFields<TMeta extends SchemaMeta$1> = NonNullable<TMeta[' $queryFields']>;
|
|
200
|
+
type KeyFieldsOf<E> = E extends {
|
|
201
|
+
keyFields: infer KF;
|
|
202
|
+
} ? KF : Record<string, unknown>;
|
|
203
|
+
type FieldsOf<E> = E extends {
|
|
204
|
+
fields: infer F extends string;
|
|
205
|
+
} ? F : string;
|
|
206
|
+
type EntityInvalidateTarget<Entities> = { [K in keyof Entities & string]: {
|
|
207
|
+
__typename: K;
|
|
208
|
+
} | ({
|
|
209
|
+
__typename: K;
|
|
210
|
+
} & KeyFieldsOf<Entities[K]>) | {
|
|
211
|
+
__typename: K;
|
|
212
|
+
$field: FieldsOf<Entities[K]>;
|
|
213
|
+
$args?: Record<string, unknown>;
|
|
214
|
+
} | ({
|
|
215
|
+
__typename: K;
|
|
216
|
+
$field: FieldsOf<Entities[K]>;
|
|
217
|
+
$args?: Record<string, unknown>;
|
|
218
|
+
} & KeyFieldsOf<Entities[K]>) }[keyof Entities & string];
|
|
219
|
+
type QueryInvalidateTarget<QF extends string> = {
|
|
214
220
|
__typename: 'Query';
|
|
215
221
|
} | {
|
|
216
222
|
__typename: 'Query';
|
|
217
|
-
field:
|
|
218
|
-
args?: Record<string, unknown>;
|
|
219
|
-
} | {
|
|
220
|
-
__typename: string;
|
|
221
|
-
} | {
|
|
222
|
-
__typename: string;
|
|
223
|
-
field: string;
|
|
224
|
-
args?: Record<string, unknown>;
|
|
223
|
+
$field: QF;
|
|
224
|
+
$args?: Record<string, unknown>;
|
|
225
225
|
};
|
|
226
|
+
/**
|
|
227
|
+
* Target specification for cache invalidation operations.
|
|
228
|
+
*/
|
|
229
|
+
type InvalidateTarget<TMeta extends SchemaMeta$1 = SchemaMeta$1> = EntityInvalidateTarget<EntityTypes<TMeta>> | QueryInvalidateTarget<QueryFields<TMeta>>;
|
|
226
230
|
/**
|
|
227
231
|
* Opaque type representing a serializable cache snapshot.
|
|
228
232
|
*/
|
|
@@ -232,17 +236,22 @@ type CacheSnapshot = {
|
|
|
232
236
|
/**
|
|
233
237
|
* Operations available for programmatic cache manipulation.
|
|
234
238
|
*/
|
|
235
|
-
type CacheOperations = {
|
|
239
|
+
type CacheOperations<TMeta extends SchemaMeta$1 = SchemaMeta$1> = {
|
|
236
240
|
extract(): CacheSnapshot;
|
|
237
241
|
hydrate(data: CacheSnapshot): void;
|
|
238
|
-
invalidate(...targets: InvalidateTarget[]): void;
|
|
242
|
+
invalidate(...targets: InvalidateTarget<TMeta>[]): void;
|
|
239
243
|
clear(): void;
|
|
240
244
|
};
|
|
241
245
|
//#endregion
|
|
242
246
|
//#region src/exchanges/cache.d.ts
|
|
243
247
|
declare module '@mearie/core' {
|
|
244
|
-
interface ExchangeExtensionMap {
|
|
245
|
-
cache: CacheOperations
|
|
248
|
+
interface ExchangeExtensionMap<TMeta extends SchemaMeta$1> {
|
|
249
|
+
cache: CacheOperations<TMeta>;
|
|
250
|
+
}
|
|
251
|
+
interface OperationMetadataMap<T extends Artifact$1> {
|
|
252
|
+
cache?: {
|
|
253
|
+
optimisticResponse?: T extends Artifact$1<'mutation'> ? DataOf$1<T> : never;
|
|
254
|
+
};
|
|
246
255
|
}
|
|
247
256
|
interface OperationResultMetadataMap {
|
|
248
257
|
cache?: {
|
|
@@ -274,7 +283,9 @@ declare const retryExchange: (options?: RetryOptions) => Exchange;
|
|
|
274
283
|
//#region src/exchanges/fragment.d.ts
|
|
275
284
|
declare module '@mearie/core' {
|
|
276
285
|
interface OperationMetadataMap {
|
|
277
|
-
|
|
286
|
+
fragment?: {
|
|
287
|
+
ref?: unknown;
|
|
288
|
+
};
|
|
278
289
|
}
|
|
279
290
|
}
|
|
280
291
|
declare const fragmentExchange: () => Exchange;
|
|
@@ -351,4 +362,4 @@ declare class RequiredFieldError extends Error {
|
|
|
351
362
|
*/
|
|
352
363
|
declare const stringify: (value: unknown) => string;
|
|
353
364
|
//#endregion
|
|
354
|
-
export { AggregatedError, type Artifact, type ArtifactKind, type CacheOptions, type CacheSnapshot, Client, type ClientOptions, type DataOf, type Exchange, ExchangeError, type ExchangeErrorExtensionsMap, type ExchangeExtensionMap, type ExchangeIO, type ExchangeResult, type FragmentOptions, type FragmentRefs, GraphQLError, type HttpOptions, type MutationOptions, type Operation, type OperationError, type OperationMetadata, type OperationMetadataMap, type OperationResult, type OperationResultMetadataMap, type QueryOptions, type RequiredAction, RequiredFieldError, type RetryOptions, type SchemaMeta, type SubscriptionClient, type SubscriptionExchangeOptions, type SubscriptionOptions, type VariablesOf, cacheExchange, createClient, dedupExchange, fragmentExchange, httpExchange, isAggregatedError, isExchangeError, isGraphQLError, requiredExchange, retryExchange, stringify, subscriptionExchange };
|
|
365
|
+
export { AggregatedError, type Artifact, type ArtifactKind, type CacheOperations, type CacheOptions, type CacheSnapshot, Client, type ClientOptions, type DataOf, type Exchange, ExchangeError, type ExchangeErrorExtensionsMap, type ExchangeExtensionMap, type ExchangeIO, type ExchangeResult, type FragmentOptions, type FragmentRefs, GraphQLError, type HttpOptions, type InvalidateTarget, type MutationOptions, type Operation, type OperationError, type OperationMetadata, type OperationMetadataMap, type OperationResult, type OperationResultMetadataMap, type QueryOptions, type RequiredAction, RequiredFieldError, type RetryOptions, type SchemaMeta, type SubscriptionClient, type SubscriptionExchangeOptions, type SubscriptionOptions, type VariablesOf, cacheExchange, createClient, dedupExchange, fragmentExchange, httpExchange, isAggregatedError, isExchangeError, isGraphQLError, requiredExchange, retryExchange, stringify, subscriptionExchange };
|
package/dist/index.mjs
CHANGED
|
@@ -317,6 +317,12 @@ const RootFieldKey = "__root";
|
|
|
317
317
|
* @internal
|
|
318
318
|
*/
|
|
319
319
|
const FragmentRefKey = "__fragmentRef";
|
|
320
|
+
/**
|
|
321
|
+
* Special key used to carry merged variable context (fragment args + operation variables)
|
|
322
|
+
* on fragment references. Used by readFragment to resolve variable-dependent field keys.
|
|
323
|
+
* @internal
|
|
324
|
+
*/
|
|
325
|
+
const FragmentVarsKey = "__fragmentVars";
|
|
320
326
|
|
|
321
327
|
//#endregion
|
|
322
328
|
//#region src/cache/utils.ts
|
|
@@ -390,6 +396,14 @@ const isFragmentRef = (value) => {
|
|
|
390
396
|
return typeof value === "object" && value !== null && FragmentRefKey in value;
|
|
391
397
|
};
|
|
392
398
|
/**
|
|
399
|
+
* Extracts the merged variable context for a specific fragment from a fragment reference.
|
|
400
|
+
* Returns the merged variables (fragment args + operation variables) if present, or an empty object.
|
|
401
|
+
* @internal
|
|
402
|
+
*/
|
|
403
|
+
const getFragmentVars = (fragmentRef, fragmentName) => {
|
|
404
|
+
return fragmentRef[FragmentVarsKey]?.[fragmentName] ?? {};
|
|
405
|
+
};
|
|
406
|
+
/**
|
|
393
407
|
* Type guard to check if a value is an array of fragment references.
|
|
394
408
|
* @internal
|
|
395
409
|
* @param value - Value to check.
|
|
@@ -500,33 +514,20 @@ const mergeFields = (target, source) => {
|
|
|
500
514
|
const makeFieldKeyFromArgs = (field, args) => {
|
|
501
515
|
return `${field}@${args && Object.keys(args).length > 0 ? stringify(args) : "{}"}`;
|
|
502
516
|
};
|
|
503
|
-
/**
|
|
504
|
-
* Converts an EntityId to an EntityKey.
|
|
505
|
-
* @internal
|
|
506
|
-
* @param typename - The GraphQL typename of the entity.
|
|
507
|
-
* @param id - The entity identifier (string, number, or composite key record).
|
|
508
|
-
* @param keyFields - Optional ordered list of key field names for composite keys.
|
|
509
|
-
* @returns An EntityKey.
|
|
510
|
-
*/
|
|
511
|
-
const resolveEntityKey = (typename, id, keyFields) => {
|
|
512
|
-
if (typeof id === "string" || typeof id === "number") return makeEntityKey(typename, [id]);
|
|
513
|
-
return makeEntityKey(typename, keyFields ? keyFields.map((f) => id[f]) : Object.values(id));
|
|
514
|
-
};
|
|
515
517
|
|
|
516
518
|
//#endregion
|
|
517
519
|
//#region src/cache/normalize.ts
|
|
518
|
-
const SKIP = Symbol();
|
|
519
520
|
const normalize = (schemaMeta, selections, storage, data, variables, accessor) => {
|
|
520
521
|
const normalizeField = (storageKey, selections, value) => {
|
|
521
522
|
if (isNullish(value)) return value;
|
|
522
523
|
if (Array.isArray(value)) return value.map((item) => normalizeField(storageKey, selections, item));
|
|
523
524
|
const data = value;
|
|
524
525
|
const typename = data.__typename;
|
|
525
|
-
|
|
526
|
+
let entityMeta = schemaMeta.entities[typename];
|
|
526
527
|
if (entityMeta) {
|
|
527
528
|
const keys = entityMeta.keyFields.map((field) => data[field]);
|
|
528
|
-
if (
|
|
529
|
-
|
|
529
|
+
if (keys.every((k) => k !== void 0 && k !== null)) storageKey = makeEntityKey(typename, keys);
|
|
530
|
+
else entityMeta = void 0;
|
|
530
531
|
}
|
|
531
532
|
const fields = {};
|
|
532
533
|
for (const selection of selections) if (selection.kind === "Field") {
|
|
@@ -534,13 +535,11 @@ const normalize = (schemaMeta, selections, storage, data, variables, accessor) =
|
|
|
534
535
|
const fieldValue = data[selection.alias ?? selection.name];
|
|
535
536
|
const oldValue = storageKey === null ? void 0 : storage[storageKey]?.[fieldKey];
|
|
536
537
|
if (storageKey !== null && (!selection.selections || isNullish(oldValue) || isNullish(fieldValue))) accessor?.(storageKey, fieldKey, oldValue, fieldValue);
|
|
537
|
-
|
|
538
|
-
if (normalized === SKIP) continue;
|
|
539
|
-
fields[fieldKey] = normalized;
|
|
538
|
+
fields[fieldKey] = selection.selections ? normalizeField(null, selection.selections, fieldValue) : fieldValue;
|
|
540
539
|
if (storageKey !== null && selection.selections && !isNullish(oldValue) && !isNullish(fieldValue) && !isEntityLink(fields[fieldKey]) && !isEqual(oldValue, fields[fieldKey])) accessor?.(storageKey, fieldKey, oldValue, fields[fieldKey]);
|
|
541
540
|
} else if (selection.kind === "FragmentSpread" || selection.kind === "InlineFragment" && selection.on === typename) {
|
|
542
541
|
const inner = normalizeField(storageKey, selection.selections, value);
|
|
543
|
-
if (
|
|
542
|
+
if (!isEntityLink(inner)) mergeFields(fields, inner);
|
|
544
543
|
}
|
|
545
544
|
if (entityMeta && storageKey !== null) {
|
|
546
545
|
const existing = storage[storageKey];
|
|
@@ -595,6 +594,17 @@ const denormalize = (selections, storage, value, variables, accessor) => {
|
|
|
595
594
|
else fields[name] = value;
|
|
596
595
|
} else if (selection.kind === "FragmentSpread") if (storageKey !== null && storageKey !== RootFieldKey) {
|
|
597
596
|
fields[FragmentRefKey] = storageKey;
|
|
597
|
+
if (selection.args) {
|
|
598
|
+
const resolvedArgs = resolveArguments(selection.args, variables);
|
|
599
|
+
const mergedVars = {
|
|
600
|
+
...variables,
|
|
601
|
+
...resolvedArgs
|
|
602
|
+
};
|
|
603
|
+
fields[FragmentVarsKey] = {
|
|
604
|
+
...fields[FragmentVarsKey],
|
|
605
|
+
[selection.name]: mergedVars
|
|
606
|
+
};
|
|
607
|
+
}
|
|
598
608
|
if (accessor) denormalize(selection.selections, storage, { [EntityLinkKey]: storageKey }, variables, accessor);
|
|
599
609
|
} else mergeFields(fields, denormalizeField(storageKey, selection.selections, value));
|
|
600
610
|
else if (selection.kind === "InlineFragment" && selection.on === data[typenameFieldKey]) mergeFields(fields, denormalizeField(storageKey, selection.selections, value));
|
|
@@ -618,9 +628,74 @@ var Cache = class {
|
|
|
618
628
|
#subscriptions = /* @__PURE__ */ new Map();
|
|
619
629
|
#memo = /* @__PURE__ */ new Map();
|
|
620
630
|
#stale = /* @__PURE__ */ new Set();
|
|
631
|
+
#optimisticKeys = [];
|
|
632
|
+
#optimisticLayers = /* @__PURE__ */ new Map();
|
|
633
|
+
#storageView = null;
|
|
621
634
|
constructor(schemaMetadata) {
|
|
622
635
|
this.#schemaMeta = schemaMetadata;
|
|
623
636
|
}
|
|
637
|
+
#getStorageView() {
|
|
638
|
+
if (this.#optimisticKeys.length === 0) return this.#storage;
|
|
639
|
+
if (this.#storageView) return this.#storageView;
|
|
640
|
+
const merged = { ...this.#storage };
|
|
641
|
+
for (const storageKey of Object.keys(this.#storage)) merged[storageKey] = { ...this.#storage[storageKey] };
|
|
642
|
+
for (const key of this.#optimisticKeys) {
|
|
643
|
+
const layer = this.#optimisticLayers.get(key);
|
|
644
|
+
if (!layer) continue;
|
|
645
|
+
for (const storageKey of Object.keys(layer.storage)) merged[storageKey] = merged[storageKey] ? {
|
|
646
|
+
...merged[storageKey],
|
|
647
|
+
...layer.storage[storageKey]
|
|
648
|
+
} : { ...layer.storage[storageKey] };
|
|
649
|
+
}
|
|
650
|
+
this.#storageView = merged;
|
|
651
|
+
return merged;
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Writes an optimistic response to a separate cache layer.
|
|
655
|
+
* The optimistic data is immediately visible in reads but does not affect the base storage.
|
|
656
|
+
* @internal
|
|
657
|
+
* @param key - Unique key identifying this optimistic mutation (typically the operation key).
|
|
658
|
+
* @param artifact - GraphQL document artifact.
|
|
659
|
+
* @param variables - Operation variables.
|
|
660
|
+
* @param data - The optimistic response data.
|
|
661
|
+
*/
|
|
662
|
+
writeOptimistic(key, artifact, variables, data) {
|
|
663
|
+
const layerStorage = { [RootFieldKey]: {} };
|
|
664
|
+
const dependencies = /* @__PURE__ */ new Set();
|
|
665
|
+
normalize(this.#schemaMeta, artifact.selections, layerStorage, data, variables, (storageKey, fieldKey) => {
|
|
666
|
+
dependencies.add(makeDependencyKey(storageKey, fieldKey));
|
|
667
|
+
});
|
|
668
|
+
this.#optimisticKeys.push(key);
|
|
669
|
+
this.#optimisticLayers.set(key, {
|
|
670
|
+
storage: layerStorage,
|
|
671
|
+
dependencies
|
|
672
|
+
});
|
|
673
|
+
this.#storageView = null;
|
|
674
|
+
const subscriptions = /* @__PURE__ */ new Set();
|
|
675
|
+
for (const depKey of dependencies) {
|
|
676
|
+
const ss = this.#subscriptions.get(depKey);
|
|
677
|
+
if (ss) for (const s of ss) subscriptions.add(s);
|
|
678
|
+
}
|
|
679
|
+
for (const subscription of subscriptions) subscription.listener();
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Removes an optimistic layer and notifies affected subscribers.
|
|
683
|
+
* @internal
|
|
684
|
+
* @param key - The key of the optimistic layer to remove.
|
|
685
|
+
*/
|
|
686
|
+
removeOptimistic(key) {
|
|
687
|
+
const layer = this.#optimisticLayers.get(key);
|
|
688
|
+
if (!layer) return;
|
|
689
|
+
this.#optimisticLayers.delete(key);
|
|
690
|
+
this.#optimisticKeys = this.#optimisticKeys.filter((k) => k !== key);
|
|
691
|
+
this.#storageView = null;
|
|
692
|
+
const subscriptions = /* @__PURE__ */ new Set();
|
|
693
|
+
for (const depKey of layer.dependencies) {
|
|
694
|
+
const ss = this.#subscriptions.get(depKey);
|
|
695
|
+
if (ss) for (const s of ss) subscriptions.add(s);
|
|
696
|
+
}
|
|
697
|
+
for (const subscription of subscriptions) subscription.listener();
|
|
698
|
+
}
|
|
624
699
|
/**
|
|
625
700
|
* Writes a query result to the cache, normalizing entities.
|
|
626
701
|
* @param artifact - GraphQL document artifact.
|
|
@@ -653,7 +728,8 @@ var Cache = class {
|
|
|
653
728
|
*/
|
|
654
729
|
readQuery(artifact, variables) {
|
|
655
730
|
let stale = false;
|
|
656
|
-
const
|
|
731
|
+
const storage = this.#getStorageView();
|
|
732
|
+
const { data, partial } = denormalize(artifact.selections, storage, storage[RootFieldKey], variables, (storageKey, fieldKey) => {
|
|
657
733
|
if (this.#stale.has(storageKey) || this.#stale.has(makeDependencyKey(storageKey, fieldKey))) stale = true;
|
|
658
734
|
});
|
|
659
735
|
if (partial) return {
|
|
@@ -678,7 +754,8 @@ var Cache = class {
|
|
|
678
754
|
*/
|
|
679
755
|
subscribeQuery(artifact, variables, listener) {
|
|
680
756
|
const dependencies = /* @__PURE__ */ new Set();
|
|
681
|
-
|
|
757
|
+
const storageView = this.#getStorageView();
|
|
758
|
+
denormalize(artifact.selections, storageView, storageView[RootFieldKey], variables, (storageKey, fieldKey) => {
|
|
682
759
|
const dependencyKey = makeDependencyKey(storageKey, fieldKey);
|
|
683
760
|
dependencies.add(dependencyKey);
|
|
684
761
|
});
|
|
@@ -693,19 +770,22 @@ var Cache = class {
|
|
|
693
770
|
*/
|
|
694
771
|
readFragment(artifact, fragmentRef) {
|
|
695
772
|
const entityKey = fragmentRef[FragmentRefKey];
|
|
696
|
-
|
|
773
|
+
const fragmentVars = getFragmentVars(fragmentRef, artifact.name);
|
|
774
|
+
const storageView = this.#getStorageView();
|
|
775
|
+
if (!storageView[entityKey]) return {
|
|
697
776
|
data: null,
|
|
698
777
|
stale: false
|
|
699
778
|
};
|
|
700
779
|
let stale = false;
|
|
701
|
-
const { data, partial } = denormalize(artifact.selections,
|
|
780
|
+
const { data, partial } = denormalize(artifact.selections, storageView, { [EntityLinkKey]: entityKey }, fragmentVars, (storageKey, fieldKey) => {
|
|
702
781
|
if (this.#stale.has(storageKey) || this.#stale.has(makeDependencyKey(storageKey, fieldKey))) stale = true;
|
|
703
782
|
});
|
|
704
783
|
if (partial) return {
|
|
705
784
|
data: null,
|
|
706
785
|
stale: false
|
|
707
786
|
};
|
|
708
|
-
const
|
|
787
|
+
const argsId = Object.keys(fragmentVars).length > 0 ? entityKey + stringify(fragmentVars) : entityKey;
|
|
788
|
+
const key = makeMemoKey("fragment", artifact.name, argsId);
|
|
709
789
|
const prev = this.#memo.get(key);
|
|
710
790
|
const result = prev === void 0 ? data : replaceEqualDeep(prev, data);
|
|
711
791
|
this.#memo.set(key, result);
|
|
@@ -716,8 +796,10 @@ var Cache = class {
|
|
|
716
796
|
}
|
|
717
797
|
subscribeFragment(artifact, fragmentRef, listener) {
|
|
718
798
|
const entityKey = fragmentRef[FragmentRefKey];
|
|
799
|
+
const fragmentVars = getFragmentVars(fragmentRef, artifact.name);
|
|
719
800
|
const dependencies = /* @__PURE__ */ new Set();
|
|
720
|
-
|
|
801
|
+
const storageView = this.#getStorageView();
|
|
802
|
+
denormalize(artifact.selections, storageView, { [EntityLinkKey]: entityKey }, fragmentVars, (storageKey, fieldKey) => {
|
|
721
803
|
const dependencyKey = makeDependencyKey(storageKey, fieldKey);
|
|
722
804
|
dependencies.add(dependencyKey);
|
|
723
805
|
});
|
|
@@ -747,9 +829,11 @@ var Cache = class {
|
|
|
747
829
|
}
|
|
748
830
|
subscribeFragments(artifact, fragmentRefs, listener) {
|
|
749
831
|
const dependencies = /* @__PURE__ */ new Set();
|
|
832
|
+
const storageView = this.#getStorageView();
|
|
750
833
|
for (const ref of fragmentRefs) {
|
|
751
834
|
const entityKey = ref[FragmentRefKey];
|
|
752
|
-
|
|
835
|
+
const fragmentVars = getFragmentVars(ref, artifact.name);
|
|
836
|
+
denormalize(artifact.selections, storageView, { [EntityLinkKey]: entityKey }, fragmentVars, (storageKey, fieldKey) => {
|
|
753
837
|
dependencies.add(makeDependencyKey(storageKey, fieldKey));
|
|
754
838
|
});
|
|
755
839
|
}
|
|
@@ -761,8 +845,8 @@ var Cache = class {
|
|
|
761
845
|
*/
|
|
762
846
|
invalidate(...targets) {
|
|
763
847
|
const subscriptions = /* @__PURE__ */ new Set();
|
|
764
|
-
for (const target of targets) if (target.__typename === "Query") if ("field" in target) {
|
|
765
|
-
const fieldKey = makeFieldKeyFromArgs(target
|
|
848
|
+
for (const target of targets) if (target.__typename === "Query") if ("$field" in target) {
|
|
849
|
+
const fieldKey = makeFieldKeyFromArgs(target.$field, target.$args);
|
|
766
850
|
const depKey = makeDependencyKey(RootFieldKey, fieldKey);
|
|
767
851
|
this.#stale.add(depKey);
|
|
768
852
|
this.#collectSubscriptions(RootFieldKey, fieldKey, subscriptions);
|
|
@@ -770,32 +854,39 @@ var Cache = class {
|
|
|
770
854
|
this.#stale.add(RootFieldKey);
|
|
771
855
|
this.#collectSubscriptions(RootFieldKey, void 0, subscriptions);
|
|
772
856
|
}
|
|
773
|
-
else
|
|
774
|
-
const
|
|
775
|
-
if (
|
|
776
|
-
const
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
this.#stale.add(entityKey);
|
|
781
|
-
this.#collectSubscriptions(entityKey, void 0, subscriptions);
|
|
782
|
-
}
|
|
783
|
-
} else {
|
|
784
|
-
const prefix = `${target.__typename}:`;
|
|
785
|
-
for (const key of Object.keys(this.#storage)) if (key.startsWith(prefix)) {
|
|
786
|
-
const entityKey = key;
|
|
787
|
-
if ("field" in target) {
|
|
788
|
-
const fieldKey = makeFieldKeyFromArgs(target.field, target.args);
|
|
857
|
+
else {
|
|
858
|
+
const keyFields = this.#schemaMeta.entities[target.__typename]?.keyFields;
|
|
859
|
+
if (keyFields && this.#hasKeyFields(target, keyFields)) {
|
|
860
|
+
const keyValues = keyFields.map((f) => target[f]);
|
|
861
|
+
const entityKey = makeEntityKey(target.__typename, keyValues);
|
|
862
|
+
if ("$field" in target) {
|
|
863
|
+
const fieldKey = makeFieldKeyFromArgs(target.$field, target.$args);
|
|
789
864
|
this.#stale.add(makeDependencyKey(entityKey, fieldKey));
|
|
790
865
|
this.#collectSubscriptions(entityKey, fieldKey, subscriptions);
|
|
791
866
|
} else {
|
|
792
867
|
this.#stale.add(entityKey);
|
|
793
868
|
this.#collectSubscriptions(entityKey, void 0, subscriptions);
|
|
794
869
|
}
|
|
870
|
+
} else {
|
|
871
|
+
const prefix = `${target.__typename}:`;
|
|
872
|
+
for (const key of Object.keys(this.#storage)) if (key.startsWith(prefix)) {
|
|
873
|
+
const entityKey = key;
|
|
874
|
+
if ("$field" in target) {
|
|
875
|
+
const fieldKey = makeFieldKeyFromArgs(target.$field, target.$args);
|
|
876
|
+
this.#stale.add(makeDependencyKey(entityKey, fieldKey));
|
|
877
|
+
this.#collectSubscriptions(entityKey, fieldKey, subscriptions);
|
|
878
|
+
} else {
|
|
879
|
+
this.#stale.add(entityKey);
|
|
880
|
+
this.#collectSubscriptions(entityKey, void 0, subscriptions);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
795
883
|
}
|
|
796
884
|
}
|
|
797
885
|
for (const subscription of subscriptions) subscription.listener();
|
|
798
886
|
}
|
|
887
|
+
#hasKeyFields(target, keyFields) {
|
|
888
|
+
return keyFields.every((f) => f in target);
|
|
889
|
+
}
|
|
799
890
|
#collectSubscriptions(storageKey, fieldKey, out) {
|
|
800
891
|
if (fieldKey === void 0) {
|
|
801
892
|
const prefix = `${storageKey}.`;
|
|
@@ -823,6 +914,7 @@ var Cache = class {
|
|
|
823
914
|
}
|
|
824
915
|
/**
|
|
825
916
|
* Extracts a serializable snapshot of the cache storage and structural sharing state.
|
|
917
|
+
* Optimistic layers are excluded because they represent transient in-flight state.
|
|
826
918
|
*/
|
|
827
919
|
extract() {
|
|
828
920
|
return {
|
|
@@ -840,6 +932,7 @@ var Cache = class {
|
|
|
840
932
|
...fields
|
|
841
933
|
};
|
|
842
934
|
for (const [key, value] of Object.entries(memo)) this.#memo.set(key, value);
|
|
935
|
+
this.#storageView = null;
|
|
843
936
|
}
|
|
844
937
|
/**
|
|
845
938
|
* Clears all cache data.
|
|
@@ -849,6 +942,9 @@ var Cache = class {
|
|
|
849
942
|
this.#subscriptions.clear();
|
|
850
943
|
this.#memo.clear();
|
|
851
944
|
this.#stale.clear();
|
|
945
|
+
this.#optimisticKeys = [];
|
|
946
|
+
this.#optimisticLayers.clear();
|
|
947
|
+
this.#storageView = null;
|
|
852
948
|
}
|
|
853
949
|
};
|
|
854
950
|
|
|
@@ -881,10 +977,10 @@ const cacheExchange = (options = {}) => {
|
|
|
881
977
|
},
|
|
882
978
|
io: (ops$) => {
|
|
883
979
|
const fragment$ = pipe(ops$, filter((op) => op.variant === "request" && op.artifact.kind === "fragment"), mergeMap((op) => {
|
|
884
|
-
const fragmentRef = op.metadata?.
|
|
980
|
+
const fragmentRef = op.metadata?.fragment?.ref;
|
|
885
981
|
if (!fragmentRef) return fromValue({
|
|
886
982
|
operation: op,
|
|
887
|
-
errors: [new ExchangeError("Fragment operation missing
|
|
983
|
+
errors: [new ExchangeError("Fragment operation missing fragment.ref in metadata. This usually happens when the wrong fragment reference was passed.", { exchangeName: "cache" })]
|
|
888
984
|
});
|
|
889
985
|
if (isFragmentRefArray(fragmentRef)) {
|
|
890
986
|
const trigger = makeSubject();
|
|
@@ -916,7 +1012,9 @@ const cacheExchange = (options = {}) => {
|
|
|
916
1012
|
errors: []
|
|
917
1013
|
})));
|
|
918
1014
|
}));
|
|
919
|
-
const nonCache$ = pipe(ops$, filter((op) => op.variant === "request" && (op.artifact.kind === "mutation" || op.artifact.kind === "subscription" || op.artifact.kind === "query" && fetchPolicy === "network-only")))
|
|
1015
|
+
const nonCache$ = pipe(ops$, filter((op) => op.variant === "request" && (op.artifact.kind === "mutation" || op.artifact.kind === "subscription" || op.artifact.kind === "query" && fetchPolicy === "network-only")), tap((op) => {
|
|
1016
|
+
if (op.artifact.kind === "mutation" && op.metadata?.cache?.optimisticResponse) cache.writeOptimistic(op.key, op.artifact, op.variables, op.metadata.cache.optimisticResponse);
|
|
1017
|
+
}));
|
|
920
1018
|
const query$ = pipe(ops$, filter((op) => op.variant === "request" && op.artifact.kind === "query" && fetchPolicy !== "network-only"), share());
|
|
921
1019
|
const refetch$ = makeSubject();
|
|
922
1020
|
return merge(fragment$, pipe(query$, mergeMap((op) => {
|
|
@@ -959,9 +1057,18 @@ const cacheExchange = (options = {}) => {
|
|
|
959
1057
|
}), filter(() => fetchPolicy === "cache-only" || fetchPolicy === "cache-and-network" || fetchPolicy === "cache-first")), pipe(merge(nonCache$, pipe(query$, filter((op) => {
|
|
960
1058
|
const { data } = cache.readQuery(op.artifact, op.variables);
|
|
961
1059
|
return fetchPolicy === "cache-and-network" || data === null;
|
|
962
|
-
})), pipe(ops$, filter((op) => op.variant === "teardown")), refetch$.source), forward,
|
|
1060
|
+
})), pipe(ops$, filter((op) => op.variant === "teardown")), refetch$.source), forward, mergeMap((result) => {
|
|
1061
|
+
if (result.operation.variant === "request" && result.operation.artifact.kind === "mutation" && result.operation.metadata?.cache?.optimisticResponse) cache.removeOptimistic(result.operation.key);
|
|
963
1062
|
if (result.operation.variant === "request" && result.data) cache.writeQuery(result.operation.artifact, result.operation.variables, result.data);
|
|
964
|
-
|
|
1063
|
+
if (result.operation.variant !== "request" || result.operation.artifact.kind !== "query" || fetchPolicy === "network-only" || !!(result.errors && result.errors.length > 0)) return fromValue(result);
|
|
1064
|
+
const { data } = cache.readQuery(result.operation.artifact, result.operation.variables);
|
|
1065
|
+
if (data !== null) return empty();
|
|
1066
|
+
return fromValue({
|
|
1067
|
+
operation: result.operation,
|
|
1068
|
+
data: void 0,
|
|
1069
|
+
errors: [new ExchangeError("Cache failed to denormalize the network response. This is likely a bug in the cache normalizer.", { exchangeName: "cache" })]
|
|
1070
|
+
});
|
|
1071
|
+
})));
|
|
965
1072
|
}
|
|
966
1073
|
};
|
|
967
1074
|
};
|
|
@@ -1013,10 +1120,10 @@ const fragmentExchange = () => {
|
|
|
1013
1120
|
name: "fragment",
|
|
1014
1121
|
io: (ops$) => {
|
|
1015
1122
|
return merge(pipe(ops$, filter((op) => op.variant === "request" && op.artifact.kind === "fragment"), map((op) => {
|
|
1016
|
-
const fragmentRef = op.metadata.
|
|
1123
|
+
const fragmentRef = op.metadata.fragment?.ref;
|
|
1017
1124
|
if (!fragmentRef) return {
|
|
1018
1125
|
operation: op,
|
|
1019
|
-
errors: [new ExchangeError("Fragment operation missing
|
|
1126
|
+
errors: [new ExchangeError("Fragment operation missing fragment.ref in metadata. This usually happens when the wrong fragment reference was passed.", { exchangeName: "fragment" })]
|
|
1020
1127
|
};
|
|
1021
1128
|
return {
|
|
1022
1129
|
operation: op,
|
|
@@ -1402,7 +1509,7 @@ var Client = class {
|
|
|
1402
1509
|
key: this.createOperationKey(),
|
|
1403
1510
|
metadata: {
|
|
1404
1511
|
...options?.metadata,
|
|
1405
|
-
fragmentRef
|
|
1512
|
+
fragment: { ref: fragmentRef }
|
|
1406
1513
|
},
|
|
1407
1514
|
artifact,
|
|
1408
1515
|
variables: {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mearie/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Type-safe, zero-overhead GraphQL client",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"graphql",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"README.md"
|
|
63
63
|
],
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"@mearie/shared": "0.
|
|
65
|
+
"@mearie/shared": "0.4.0"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"tsdown": "^0.20.3",
|