@mearie/core 0.2.4 → 0.4.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 +111 -112
- package/dist/index.d.cts +47 -37
- package/dist/index.d.mts +47 -37
- package/dist/index.mjs +111 -112
- package/package.json +11 -4
package/dist/index.cjs
CHANGED
|
@@ -501,18 +501,6 @@ const mergeFields = (target, source) => {
|
|
|
501
501
|
const makeFieldKeyFromArgs = (field, args) => {
|
|
502
502
|
return `${field}@${args && Object.keys(args).length > 0 ? stringify(args) : "{}"}`;
|
|
503
503
|
};
|
|
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
504
|
|
|
517
505
|
//#endregion
|
|
518
506
|
//#region src/cache/normalize.ts
|
|
@@ -594,8 +582,10 @@ const denormalize = (selections, storage, value, variables, accessor) => {
|
|
|
594
582
|
const value = selection.selections ? denormalizeField(null, selection.selections, fieldValue) : fieldValue;
|
|
595
583
|
if (name in fields) mergeFields(fields, { [name]: value });
|
|
596
584
|
else fields[name] = value;
|
|
597
|
-
} else if (selection.kind === "FragmentSpread") if (storageKey !== null && storageKey !== RootFieldKey)
|
|
598
|
-
|
|
585
|
+
} else if (selection.kind === "FragmentSpread") if (storageKey !== null && storageKey !== RootFieldKey) {
|
|
586
|
+
fields[FragmentRefKey] = storageKey;
|
|
587
|
+
if (accessor) denormalize(selection.selections, storage, { [EntityLinkKey]: storageKey }, variables, accessor);
|
|
588
|
+
} else mergeFields(fields, denormalizeField(storageKey, selection.selections, value));
|
|
599
589
|
else if (selection.kind === "InlineFragment" && selection.on === data[typenameFieldKey]) mergeFields(fields, denormalizeField(storageKey, selection.selections, value));
|
|
600
590
|
return fields;
|
|
601
591
|
};
|
|
@@ -616,6 +606,7 @@ var Cache = class {
|
|
|
616
606
|
#storage = { [RootFieldKey]: {} };
|
|
617
607
|
#subscriptions = /* @__PURE__ */ new Map();
|
|
618
608
|
#memo = /* @__PURE__ */ new Map();
|
|
609
|
+
#stale = /* @__PURE__ */ new Set();
|
|
619
610
|
constructor(schemaMetadata) {
|
|
620
611
|
this.#schemaMeta = schemaMetadata;
|
|
621
612
|
}
|
|
@@ -628,17 +619,19 @@ var Cache = class {
|
|
|
628
619
|
writeQuery(artifact, variables, data) {
|
|
629
620
|
const dependencies = /* @__PURE__ */ new Set();
|
|
630
621
|
const subscriptions = /* @__PURE__ */ new Set();
|
|
622
|
+
const entityStaleCleared = /* @__PURE__ */ new Set();
|
|
631
623
|
normalize(this.#schemaMeta, artifact.selections, this.#storage, data, variables, (storageKey, fieldKey, oldValue, newValue) => {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
624
|
+
const depKey = makeDependencyKey(storageKey, fieldKey);
|
|
625
|
+
if (this.#stale.delete(depKey)) dependencies.add(depKey);
|
|
626
|
+
if (!entityStaleCleared.has(storageKey) && this.#stale.delete(storageKey)) entityStaleCleared.add(storageKey);
|
|
627
|
+
if (oldValue !== newValue) dependencies.add(depKey);
|
|
636
628
|
});
|
|
629
|
+
for (const entityKey of entityStaleCleared) this.#collectSubscriptions(entityKey, void 0, subscriptions);
|
|
637
630
|
for (const dependency of dependencies) {
|
|
638
631
|
const ss = this.#subscriptions.get(dependency);
|
|
639
632
|
if (ss) for (const s of ss) subscriptions.add(s);
|
|
640
633
|
}
|
|
641
|
-
for (const subscription of subscriptions) subscription.listener(
|
|
634
|
+
for (const subscription of subscriptions) subscription.listener();
|
|
642
635
|
}
|
|
643
636
|
/**
|
|
644
637
|
* Reads a query result from the cache, denormalizing entities if available.
|
|
@@ -648,13 +641,22 @@ var Cache = class {
|
|
|
648
641
|
* @returns Denormalized query result or null if not found.
|
|
649
642
|
*/
|
|
650
643
|
readQuery(artifact, variables) {
|
|
651
|
-
|
|
652
|
-
|
|
644
|
+
let stale = false;
|
|
645
|
+
const { data, partial } = denormalize(artifact.selections, this.#storage, this.#storage[RootFieldKey], variables, (storageKey, fieldKey) => {
|
|
646
|
+
if (this.#stale.has(storageKey) || this.#stale.has(makeDependencyKey(storageKey, fieldKey))) stale = true;
|
|
647
|
+
});
|
|
648
|
+
if (partial) return {
|
|
649
|
+
data: null,
|
|
650
|
+
stale: false
|
|
651
|
+
};
|
|
653
652
|
const key = makeMemoKey("query", artifact.name, stringify(variables));
|
|
654
653
|
const prev = this.#memo.get(key);
|
|
655
654
|
const result = prev === void 0 ? data : replaceEqualDeep(prev, data);
|
|
656
655
|
this.#memo.set(key, result);
|
|
657
|
-
return
|
|
656
|
+
return {
|
|
657
|
+
data: result,
|
|
658
|
+
stale
|
|
659
|
+
};
|
|
658
660
|
}
|
|
659
661
|
/**
|
|
660
662
|
* Subscribes to cache invalidations for a specific query.
|
|
@@ -680,14 +682,26 @@ var Cache = class {
|
|
|
680
682
|
*/
|
|
681
683
|
readFragment(artifact, fragmentRef) {
|
|
682
684
|
const entityKey = fragmentRef[FragmentRefKey];
|
|
683
|
-
if (!this.#storage[entityKey]) return
|
|
684
|
-
|
|
685
|
-
|
|
685
|
+
if (!this.#storage[entityKey]) return {
|
|
686
|
+
data: null,
|
|
687
|
+
stale: false
|
|
688
|
+
};
|
|
689
|
+
let stale = false;
|
|
690
|
+
const { data, partial } = denormalize(artifact.selections, this.#storage, { [EntityLinkKey]: entityKey }, {}, (storageKey, fieldKey) => {
|
|
691
|
+
if (this.#stale.has(storageKey) || this.#stale.has(makeDependencyKey(storageKey, fieldKey))) stale = true;
|
|
692
|
+
});
|
|
693
|
+
if (partial) return {
|
|
694
|
+
data: null,
|
|
695
|
+
stale: false
|
|
696
|
+
};
|
|
686
697
|
const key = makeMemoKey("fragment", artifact.name, entityKey);
|
|
687
698
|
const prev = this.#memo.get(key);
|
|
688
699
|
const result = prev === void 0 ? data : replaceEqualDeep(prev, data);
|
|
689
700
|
this.#memo.set(key, result);
|
|
690
|
-
return
|
|
701
|
+
return {
|
|
702
|
+
data: result,
|
|
703
|
+
stale
|
|
704
|
+
};
|
|
691
705
|
}
|
|
692
706
|
subscribeFragment(artifact, fragmentRef, listener) {
|
|
693
707
|
const entityKey = fragmentRef[FragmentRefKey];
|
|
@@ -700,17 +714,25 @@ var Cache = class {
|
|
|
700
714
|
}
|
|
701
715
|
readFragments(artifact, fragmentRefs) {
|
|
702
716
|
const results = [];
|
|
717
|
+
let stale = false;
|
|
703
718
|
for (const ref of fragmentRefs) {
|
|
704
|
-
const
|
|
705
|
-
if (data === null) return
|
|
706
|
-
|
|
719
|
+
const result = this.readFragment(artifact, ref);
|
|
720
|
+
if (result.data === null) return {
|
|
721
|
+
data: null,
|
|
722
|
+
stale: false
|
|
723
|
+
};
|
|
724
|
+
if (result.stale) stale = true;
|
|
725
|
+
results.push(result.data);
|
|
707
726
|
}
|
|
708
727
|
const entityKeys = fragmentRefs.map((ref) => ref[FragmentRefKey]);
|
|
709
728
|
const key = makeMemoKey("fragments", artifact.name, entityKeys.join(","));
|
|
710
729
|
const prev = this.#memo.get(key);
|
|
711
730
|
const result = prev === void 0 ? results : replaceEqualDeep(prev, results);
|
|
712
731
|
this.#memo.set(key, result);
|
|
713
|
-
return
|
|
732
|
+
return {
|
|
733
|
+
data: result,
|
|
734
|
+
stale
|
|
735
|
+
};
|
|
714
736
|
}
|
|
715
737
|
subscribeFragments(artifact, fragmentRefs, listener) {
|
|
716
738
|
const dependencies = /* @__PURE__ */ new Set();
|
|
@@ -728,50 +750,47 @@ var Cache = class {
|
|
|
728
750
|
*/
|
|
729
751
|
invalidate(...targets) {
|
|
730
752
|
const subscriptions = /* @__PURE__ */ new Set();
|
|
731
|
-
for (const target of targets) if (target.__typename === "Query") if ("field" in target) {
|
|
732
|
-
const fieldKey = makeFieldKeyFromArgs(target
|
|
733
|
-
|
|
753
|
+
for (const target of targets) if (target.__typename === "Query") if ("$field" in target) {
|
|
754
|
+
const fieldKey = makeFieldKeyFromArgs(target.$field, target.$args);
|
|
755
|
+
const depKey = makeDependencyKey(RootFieldKey, fieldKey);
|
|
756
|
+
this.#stale.add(depKey);
|
|
734
757
|
this.#collectSubscriptions(RootFieldKey, fieldKey, subscriptions);
|
|
735
758
|
} else {
|
|
736
|
-
this.#
|
|
759
|
+
this.#stale.add(RootFieldKey);
|
|
737
760
|
this.#collectSubscriptions(RootFieldKey, void 0, subscriptions);
|
|
738
761
|
}
|
|
739
|
-
else
|
|
740
|
-
const
|
|
741
|
-
if (
|
|
742
|
-
const
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
this.#collectSubscriptions(entityKey, void 0, subscriptions);
|
|
748
|
-
}
|
|
749
|
-
this.#collectLinkedEntitySubscriptions((linkedEntityKey) => linkedEntityKey === entityKey, subscriptions);
|
|
750
|
-
} else {
|
|
751
|
-
const prefix = `${target.__typename}:`;
|
|
752
|
-
for (const key of Object.keys(this.#storage)) if (key.startsWith(prefix)) {
|
|
753
|
-
const entityKey = key;
|
|
754
|
-
if ("field" in target) {
|
|
755
|
-
const fieldKey = makeFieldKeyFromArgs(target.field, target.args);
|
|
756
|
-
delete this.#storage[entityKey]?.[fieldKey];
|
|
762
|
+
else {
|
|
763
|
+
const keyFields = this.#schemaMeta.entities[target.__typename]?.keyFields;
|
|
764
|
+
if (keyFields && this.#hasKeyFields(target, keyFields)) {
|
|
765
|
+
const keyValues = keyFields.map((f) => target[f]);
|
|
766
|
+
const entityKey = makeEntityKey(target.__typename, keyValues);
|
|
767
|
+
if ("$field" in target) {
|
|
768
|
+
const fieldKey = makeFieldKeyFromArgs(target.$field, target.$args);
|
|
769
|
+
this.#stale.add(makeDependencyKey(entityKey, fieldKey));
|
|
757
770
|
this.#collectSubscriptions(entityKey, fieldKey, subscriptions);
|
|
758
771
|
} else {
|
|
759
|
-
|
|
772
|
+
this.#stale.add(entityKey);
|
|
760
773
|
this.#collectSubscriptions(entityKey, void 0, subscriptions);
|
|
761
774
|
}
|
|
775
|
+
} else {
|
|
776
|
+
const prefix = `${target.__typename}:`;
|
|
777
|
+
for (const key of Object.keys(this.#storage)) if (key.startsWith(prefix)) {
|
|
778
|
+
const entityKey = key;
|
|
779
|
+
if ("$field" in target) {
|
|
780
|
+
const fieldKey = makeFieldKeyFromArgs(target.$field, target.$args);
|
|
781
|
+
this.#stale.add(makeDependencyKey(entityKey, fieldKey));
|
|
782
|
+
this.#collectSubscriptions(entityKey, fieldKey, subscriptions);
|
|
783
|
+
} else {
|
|
784
|
+
this.#stale.add(entityKey);
|
|
785
|
+
this.#collectSubscriptions(entityKey, void 0, subscriptions);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
762
788
|
}
|
|
763
|
-
this.#collectLinkedEntitySubscriptions((linkedEntityKey) => linkedEntityKey.startsWith(prefix), subscriptions);
|
|
764
789
|
}
|
|
765
|
-
for (const subscription of subscriptions) subscription.listener(
|
|
766
|
-
}
|
|
767
|
-
#collectLinkedEntitySubscriptions(matcher, out) {
|
|
768
|
-
for (const [storageKey, fields] of Object.entries(this.#storage)) for (const [fieldKey, value] of Object.entries(fields)) if (this.#containsEntityLink(value, matcher)) this.#collectSubscriptions(storageKey, fieldKey, out);
|
|
790
|
+
for (const subscription of subscriptions) subscription.listener();
|
|
769
791
|
}
|
|
770
|
-
#
|
|
771
|
-
|
|
772
|
-
if (Array.isArray(value)) return value.some((item) => this.#containsEntityLink(item, matcher));
|
|
773
|
-
if (value && typeof value === "object") return Object.values(value).some((item) => this.#containsEntityLink(item, matcher));
|
|
774
|
-
return false;
|
|
792
|
+
#hasKeyFields(target, keyFields) {
|
|
793
|
+
return keyFields.every((f) => f in target);
|
|
775
794
|
}
|
|
776
795
|
#collectSubscriptions(storageKey, fieldKey, out) {
|
|
777
796
|
if (fieldKey === void 0) {
|
|
@@ -825,6 +844,7 @@ var Cache = class {
|
|
|
825
844
|
this.#storage = { [RootFieldKey]: {} };
|
|
826
845
|
this.#subscriptions.clear();
|
|
827
846
|
this.#memo.clear();
|
|
847
|
+
this.#stale.clear();
|
|
828
848
|
}
|
|
829
849
|
};
|
|
830
850
|
|
|
@@ -864,27 +884,16 @@ const cacheExchange = (options = {}) => {
|
|
|
864
884
|
});
|
|
865
885
|
if (isFragmentRefArray(fragmentRef)) {
|
|
866
886
|
const trigger = require_make.makeSubject();
|
|
867
|
-
let hasData = false;
|
|
868
887
|
const teardown$ = require_make.pipe(ops$, require_make.filter((operation) => operation.variant === "teardown" && operation.key === op.key), require_make.tap(() => trigger.complete()));
|
|
869
888
|
return require_make.pipe(require_make.merge(require_make.fromValue(void 0), trigger.source), require_make.switchMap(() => require_make.fromSubscription(() => cache.readFragments(op.artifact, fragmentRef), () => cache.subscribeFragments(op.artifact, fragmentRef, async () => {
|
|
870
889
|
await Promise.resolve();
|
|
871
890
|
trigger.next();
|
|
872
|
-
}))), require_make.takeUntil(teardown$), require_make.
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
errors: []
|
|
879
|
-
});
|
|
880
|
-
}
|
|
881
|
-
if (hasData) return empty();
|
|
882
|
-
return require_make.fromValue({
|
|
883
|
-
operation: op,
|
|
884
|
-
data,
|
|
885
|
-
errors: []
|
|
886
|
-
});
|
|
887
|
-
}));
|
|
891
|
+
}))), require_make.takeUntil(teardown$), require_make.map(({ data, stale }) => ({
|
|
892
|
+
operation: op,
|
|
893
|
+
data,
|
|
894
|
+
...stale && { metadata: { cache: { stale: true } } },
|
|
895
|
+
errors: []
|
|
896
|
+
})));
|
|
888
897
|
}
|
|
889
898
|
if (!isFragmentRef(fragmentRef)) return require_make.fromValue({
|
|
890
899
|
operation: op,
|
|
@@ -892,27 +901,16 @@ const cacheExchange = (options = {}) => {
|
|
|
892
901
|
errors: []
|
|
893
902
|
});
|
|
894
903
|
const trigger = require_make.makeSubject();
|
|
895
|
-
let hasData = false;
|
|
896
904
|
const teardown$ = require_make.pipe(ops$, require_make.filter((operation) => operation.variant === "teardown" && operation.key === op.key), require_make.tap(() => trigger.complete()));
|
|
897
905
|
return require_make.pipe(require_make.merge(require_make.fromValue(void 0), trigger.source), require_make.switchMap(() => require_make.fromSubscription(() => cache.readFragment(op.artifact, fragmentRef), () => cache.subscribeFragment(op.artifact, fragmentRef, async () => {
|
|
898
906
|
await Promise.resolve();
|
|
899
907
|
trigger.next();
|
|
900
|
-
}))), require_make.takeUntil(teardown$), require_make.
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
errors: []
|
|
907
|
-
});
|
|
908
|
-
}
|
|
909
|
-
if (hasData) return empty();
|
|
910
|
-
return require_make.fromValue({
|
|
911
|
-
operation: op,
|
|
912
|
-
data,
|
|
913
|
-
errors: []
|
|
914
|
-
});
|
|
915
|
-
}));
|
|
908
|
+
}))), require_make.takeUntil(teardown$), require_make.map(({ data, stale }) => ({
|
|
909
|
+
operation: op,
|
|
910
|
+
data,
|
|
911
|
+
...stale && { metadata: { cache: { stale: true } } },
|
|
912
|
+
errors: []
|
|
913
|
+
})));
|
|
916
914
|
}));
|
|
917
915
|
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")));
|
|
918
916
|
const query$ = require_make.pipe(ops$, require_make.filter((op) => op.variant === "request" && op.artifact.kind === "query" && fetchPolicy !== "network-only"), require_make.share());
|
|
@@ -920,30 +918,31 @@ const cacheExchange = (options = {}) => {
|
|
|
920
918
|
return require_make.merge(fragment$, require_make.pipe(query$, require_make.mergeMap((op) => {
|
|
921
919
|
const trigger = require_make.makeSubject();
|
|
922
920
|
let hasData = false;
|
|
923
|
-
let invalidated = false;
|
|
924
921
|
const teardown$ = require_make.pipe(ops$, require_make.filter((operation) => operation.variant === "teardown" && operation.key === op.key), require_make.tap(() => trigger.complete()));
|
|
925
|
-
return require_make.pipe(require_make.merge(require_make.fromValue(void 0), trigger.source), require_make.switchMap(() => require_make.fromSubscription(() => cache.readQuery(op.artifact, op.variables), () => cache.subscribeQuery(op.artifact, op.variables, async (
|
|
926
|
-
if (event === "invalidate") invalidated = true;
|
|
922
|
+
return require_make.pipe(require_make.merge(require_make.fromValue(void 0), trigger.source), require_make.switchMap(() => require_make.fromSubscription(() => cache.readQuery(op.artifact, op.variables), () => cache.subscribeQuery(op.artifact, op.variables, async () => {
|
|
927
923
|
await Promise.resolve();
|
|
928
924
|
trigger.next();
|
|
929
|
-
}))), require_make.takeUntil(teardown$), require_make.mergeMap((data) => {
|
|
930
|
-
if (data !== null) {
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
925
|
+
}))), require_make.takeUntil(teardown$), require_make.mergeMap(({ data, stale }) => {
|
|
926
|
+
if (data !== null && !stale) {
|
|
927
|
+
hasData = true;
|
|
928
|
+
return require_make.fromValue({
|
|
929
|
+
operation: op,
|
|
930
|
+
data,
|
|
931
|
+
errors: []
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
if (data !== null && stale) {
|
|
936
935
|
hasData = true;
|
|
937
|
-
|
|
936
|
+
refetch$.next(op);
|
|
938
937
|
return require_make.fromValue({
|
|
939
938
|
operation: op,
|
|
940
939
|
data,
|
|
940
|
+
metadata: { cache: { stale: true } },
|
|
941
941
|
errors: []
|
|
942
942
|
});
|
|
943
943
|
}
|
|
944
944
|
if (hasData) {
|
|
945
|
-
|
|
946
|
-
invalidated = false;
|
|
945
|
+
refetch$.next(op);
|
|
947
946
|
return empty();
|
|
948
947
|
}
|
|
949
948
|
if (fetchPolicy === "cache-only") return require_make.fromValue({
|
|
@@ -954,8 +953,8 @@ const cacheExchange = (options = {}) => {
|
|
|
954
953
|
return empty();
|
|
955
954
|
}));
|
|
956
955
|
}), 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) => {
|
|
957
|
-
const
|
|
958
|
-
return fetchPolicy === "cache-and-network" ||
|
|
956
|
+
const { data } = cache.readQuery(op.artifact, op.variables);
|
|
957
|
+
return fetchPolicy === "cache-and-network" || data === null;
|
|
959
958
|
})), require_make.pipe(ops$, require_make.filter((op) => op.variant === "teardown")), refetch$.source), forward, require_make.tap((result) => {
|
|
960
959
|
if (result.operation.variant === "request" && result.data) cache.writeQuery(result.operation.artifact, result.operation.variables, result.data);
|
|
961
960
|
}), require_make.filter((result) => result.operation.variant !== "request" || result.operation.artifact.kind !== "query" || fetchPolicy === "network-only" || !!(result.errors && result.errors.length > 0))));
|
package/dist/index.d.cts
CHANGED
|
@@ -106,9 +106,9 @@ declare class Client<TMeta extends SchemaMeta$1 = SchemaMeta$1> {
|
|
|
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
|
}
|
|
@@ -116,6 +116,7 @@ declare const createClient: <T extends SchemaMeta$1>(config: ClientOptions<T>) =
|
|
|
116
116
|
//#endregion
|
|
117
117
|
//#region src/exchange.d.ts
|
|
118
118
|
interface OperationMetadataMap {}
|
|
119
|
+
interface OperationResultMetadataMap {}
|
|
119
120
|
type OperationMetadata = { [K in keyof OperationMetadataMap]?: OperationMetadataMap[K] } & Record<string, unknown>;
|
|
120
121
|
type BaseOperation = {
|
|
121
122
|
key: string;
|
|
@@ -135,21 +136,21 @@ type OperationResult = {
|
|
|
135
136
|
data?: unknown;
|
|
136
137
|
errors?: readonly OperationError[];
|
|
137
138
|
extensions?: Record<string, unknown>;
|
|
138
|
-
|
|
139
|
+
metadata?: OperationResultMetadataMap & Record<string, unknown>;
|
|
139
140
|
};
|
|
140
141
|
type ExchangeInput<TMeta extends SchemaMeta$1 = SchemaMeta$1> = {
|
|
141
142
|
forward: ExchangeIO;
|
|
142
143
|
client: Client<TMeta>;
|
|
143
144
|
};
|
|
144
145
|
type ExchangeIO = (operations: Source<Operation>) => Source<OperationResult>;
|
|
145
|
-
interface ExchangeExtensionMap {}
|
|
146
|
-
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> = {
|
|
147
148
|
name: TName;
|
|
148
149
|
io: ExchangeIO;
|
|
149
|
-
} & (TName extends keyof ExchangeExtensionMap ? {
|
|
150
|
-
extension: ExchangeExtensionMap[TName];
|
|
150
|
+
} & (TName extends keyof ExchangeExtensionMap<TMeta> ? {
|
|
151
|
+
extension: ExchangeExtensionMap<TMeta>[TName];
|
|
151
152
|
} : {});
|
|
152
|
-
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>;
|
|
153
154
|
//#endregion
|
|
154
155
|
//#region src/exchanges/http.d.ts
|
|
155
156
|
declare module '@mearie/core' {
|
|
@@ -194,34 +195,38 @@ declare module '@mearie/core' {
|
|
|
194
195
|
declare const dedupExchange: () => Exchange;
|
|
195
196
|
//#endregion
|
|
196
197
|
//#region src/cache/types.d.ts
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
} | {
|
|
208
|
-
__typename:
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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> = {
|
|
213
220
|
__typename: 'Query';
|
|
214
221
|
} | {
|
|
215
222
|
__typename: 'Query';
|
|
216
|
-
field:
|
|
217
|
-
args?: Record<string, unknown>;
|
|
218
|
-
} | {
|
|
219
|
-
__typename: string;
|
|
220
|
-
} | {
|
|
221
|
-
__typename: string;
|
|
222
|
-
field: string;
|
|
223
|
-
args?: Record<string, unknown>;
|
|
223
|
+
$field: QF;
|
|
224
|
+
$args?: Record<string, unknown>;
|
|
224
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>>;
|
|
225
230
|
/**
|
|
226
231
|
* Opaque type representing a serializable cache snapshot.
|
|
227
232
|
*/
|
|
@@ -231,17 +236,22 @@ type CacheSnapshot = {
|
|
|
231
236
|
/**
|
|
232
237
|
* Operations available for programmatic cache manipulation.
|
|
233
238
|
*/
|
|
234
|
-
type CacheOperations = {
|
|
239
|
+
type CacheOperations<TMeta extends SchemaMeta$1 = SchemaMeta$1> = {
|
|
235
240
|
extract(): CacheSnapshot;
|
|
236
241
|
hydrate(data: CacheSnapshot): void;
|
|
237
|
-
invalidate(...targets: InvalidateTarget[]): void;
|
|
242
|
+
invalidate(...targets: InvalidateTarget<TMeta>[]): void;
|
|
238
243
|
clear(): void;
|
|
239
244
|
};
|
|
240
245
|
//#endregion
|
|
241
246
|
//#region src/exchanges/cache.d.ts
|
|
242
247
|
declare module '@mearie/core' {
|
|
243
|
-
interface ExchangeExtensionMap {
|
|
244
|
-
cache: CacheOperations
|
|
248
|
+
interface ExchangeExtensionMap<TMeta extends SchemaMeta$1> {
|
|
249
|
+
cache: CacheOperations<TMeta>;
|
|
250
|
+
}
|
|
251
|
+
interface OperationResultMetadataMap {
|
|
252
|
+
cache?: {
|
|
253
|
+
stale: boolean;
|
|
254
|
+
};
|
|
245
255
|
}
|
|
246
256
|
}
|
|
247
257
|
type CacheOptions = {
|
|
@@ -345,4 +355,4 @@ declare class RequiredFieldError extends Error {
|
|
|
345
355
|
*/
|
|
346
356
|
declare const stringify: (value: unknown) => string;
|
|
347
357
|
//#endregion
|
|
348
|
-
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 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 };
|
|
358
|
+
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
|
@@ -106,9 +106,9 @@ declare class Client<TMeta extends SchemaMeta$1 = SchemaMeta$1> {
|
|
|
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
|
}
|
|
@@ -116,6 +116,7 @@ declare const createClient: <T extends SchemaMeta$1>(config: ClientOptions<T>) =
|
|
|
116
116
|
//#endregion
|
|
117
117
|
//#region src/exchange.d.ts
|
|
118
118
|
interface OperationMetadataMap {}
|
|
119
|
+
interface OperationResultMetadataMap {}
|
|
119
120
|
type OperationMetadata = { [K in keyof OperationMetadataMap]?: OperationMetadataMap[K] } & Record<string, unknown>;
|
|
120
121
|
type BaseOperation = {
|
|
121
122
|
key: string;
|
|
@@ -135,21 +136,21 @@ type OperationResult = {
|
|
|
135
136
|
data?: unknown;
|
|
136
137
|
errors?: readonly OperationError[];
|
|
137
138
|
extensions?: Record<string, unknown>;
|
|
138
|
-
|
|
139
|
+
metadata?: OperationResultMetadataMap & Record<string, unknown>;
|
|
139
140
|
};
|
|
140
141
|
type ExchangeInput<TMeta extends SchemaMeta$1 = SchemaMeta$1> = {
|
|
141
142
|
forward: ExchangeIO;
|
|
142
143
|
client: Client<TMeta>;
|
|
143
144
|
};
|
|
144
145
|
type ExchangeIO = (operations: Source<Operation>) => Source<OperationResult>;
|
|
145
|
-
interface ExchangeExtensionMap {}
|
|
146
|
-
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> = {
|
|
147
148
|
name: TName;
|
|
148
149
|
io: ExchangeIO;
|
|
149
|
-
} & (TName extends keyof ExchangeExtensionMap ? {
|
|
150
|
-
extension: ExchangeExtensionMap[TName];
|
|
150
|
+
} & (TName extends keyof ExchangeExtensionMap<TMeta> ? {
|
|
151
|
+
extension: ExchangeExtensionMap<TMeta>[TName];
|
|
151
152
|
} : {});
|
|
152
|
-
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>;
|
|
153
154
|
//#endregion
|
|
154
155
|
//#region src/exchanges/http.d.ts
|
|
155
156
|
declare module '@mearie/core' {
|
|
@@ -194,34 +195,38 @@ declare module '@mearie/core' {
|
|
|
194
195
|
declare const dedupExchange: () => Exchange;
|
|
195
196
|
//#endregion
|
|
196
197
|
//#region src/cache/types.d.ts
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
} | {
|
|
208
|
-
__typename:
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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> = {
|
|
213
220
|
__typename: 'Query';
|
|
214
221
|
} | {
|
|
215
222
|
__typename: 'Query';
|
|
216
|
-
field:
|
|
217
|
-
args?: Record<string, unknown>;
|
|
218
|
-
} | {
|
|
219
|
-
__typename: string;
|
|
220
|
-
} | {
|
|
221
|
-
__typename: string;
|
|
222
|
-
field: string;
|
|
223
|
-
args?: Record<string, unknown>;
|
|
223
|
+
$field: QF;
|
|
224
|
+
$args?: Record<string, unknown>;
|
|
224
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>>;
|
|
225
230
|
/**
|
|
226
231
|
* Opaque type representing a serializable cache snapshot.
|
|
227
232
|
*/
|
|
@@ -231,17 +236,22 @@ type CacheSnapshot = {
|
|
|
231
236
|
/**
|
|
232
237
|
* Operations available for programmatic cache manipulation.
|
|
233
238
|
*/
|
|
234
|
-
type CacheOperations = {
|
|
239
|
+
type CacheOperations<TMeta extends SchemaMeta$1 = SchemaMeta$1> = {
|
|
235
240
|
extract(): CacheSnapshot;
|
|
236
241
|
hydrate(data: CacheSnapshot): void;
|
|
237
|
-
invalidate(...targets: InvalidateTarget[]): void;
|
|
242
|
+
invalidate(...targets: InvalidateTarget<TMeta>[]): void;
|
|
238
243
|
clear(): void;
|
|
239
244
|
};
|
|
240
245
|
//#endregion
|
|
241
246
|
//#region src/exchanges/cache.d.ts
|
|
242
247
|
declare module '@mearie/core' {
|
|
243
|
-
interface ExchangeExtensionMap {
|
|
244
|
-
cache: CacheOperations
|
|
248
|
+
interface ExchangeExtensionMap<TMeta extends SchemaMeta$1> {
|
|
249
|
+
cache: CacheOperations<TMeta>;
|
|
250
|
+
}
|
|
251
|
+
interface OperationResultMetadataMap {
|
|
252
|
+
cache?: {
|
|
253
|
+
stale: boolean;
|
|
254
|
+
};
|
|
245
255
|
}
|
|
246
256
|
}
|
|
247
257
|
type CacheOptions = {
|
|
@@ -345,4 +355,4 @@ declare class RequiredFieldError extends Error {
|
|
|
345
355
|
*/
|
|
346
356
|
declare const stringify: (value: unknown) => string;
|
|
347
357
|
//#endregion
|
|
348
|
-
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 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 };
|
|
358
|
+
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
|
@@ -500,18 +500,6 @@ const mergeFields = (target, source) => {
|
|
|
500
500
|
const makeFieldKeyFromArgs = (field, args) => {
|
|
501
501
|
return `${field}@${args && Object.keys(args).length > 0 ? stringify(args) : "{}"}`;
|
|
502
502
|
};
|
|
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
503
|
|
|
516
504
|
//#endregion
|
|
517
505
|
//#region src/cache/normalize.ts
|
|
@@ -593,8 +581,10 @@ const denormalize = (selections, storage, value, variables, accessor) => {
|
|
|
593
581
|
const value = selection.selections ? denormalizeField(null, selection.selections, fieldValue) : fieldValue;
|
|
594
582
|
if (name in fields) mergeFields(fields, { [name]: value });
|
|
595
583
|
else fields[name] = value;
|
|
596
|
-
} else if (selection.kind === "FragmentSpread") if (storageKey !== null && storageKey !== RootFieldKey)
|
|
597
|
-
|
|
584
|
+
} else if (selection.kind === "FragmentSpread") if (storageKey !== null && storageKey !== RootFieldKey) {
|
|
585
|
+
fields[FragmentRefKey] = storageKey;
|
|
586
|
+
if (accessor) denormalize(selection.selections, storage, { [EntityLinkKey]: storageKey }, variables, accessor);
|
|
587
|
+
} else mergeFields(fields, denormalizeField(storageKey, selection.selections, value));
|
|
598
588
|
else if (selection.kind === "InlineFragment" && selection.on === data[typenameFieldKey]) mergeFields(fields, denormalizeField(storageKey, selection.selections, value));
|
|
599
589
|
return fields;
|
|
600
590
|
};
|
|
@@ -615,6 +605,7 @@ var Cache = class {
|
|
|
615
605
|
#storage = { [RootFieldKey]: {} };
|
|
616
606
|
#subscriptions = /* @__PURE__ */ new Map();
|
|
617
607
|
#memo = /* @__PURE__ */ new Map();
|
|
608
|
+
#stale = /* @__PURE__ */ new Set();
|
|
618
609
|
constructor(schemaMetadata) {
|
|
619
610
|
this.#schemaMeta = schemaMetadata;
|
|
620
611
|
}
|
|
@@ -627,17 +618,19 @@ var Cache = class {
|
|
|
627
618
|
writeQuery(artifact, variables, data) {
|
|
628
619
|
const dependencies = /* @__PURE__ */ new Set();
|
|
629
620
|
const subscriptions = /* @__PURE__ */ new Set();
|
|
621
|
+
const entityStaleCleared = /* @__PURE__ */ new Set();
|
|
630
622
|
normalize(this.#schemaMeta, artifact.selections, this.#storage, data, variables, (storageKey, fieldKey, oldValue, newValue) => {
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
623
|
+
const depKey = makeDependencyKey(storageKey, fieldKey);
|
|
624
|
+
if (this.#stale.delete(depKey)) dependencies.add(depKey);
|
|
625
|
+
if (!entityStaleCleared.has(storageKey) && this.#stale.delete(storageKey)) entityStaleCleared.add(storageKey);
|
|
626
|
+
if (oldValue !== newValue) dependencies.add(depKey);
|
|
635
627
|
});
|
|
628
|
+
for (const entityKey of entityStaleCleared) this.#collectSubscriptions(entityKey, void 0, subscriptions);
|
|
636
629
|
for (const dependency of dependencies) {
|
|
637
630
|
const ss = this.#subscriptions.get(dependency);
|
|
638
631
|
if (ss) for (const s of ss) subscriptions.add(s);
|
|
639
632
|
}
|
|
640
|
-
for (const subscription of subscriptions) subscription.listener(
|
|
633
|
+
for (const subscription of subscriptions) subscription.listener();
|
|
641
634
|
}
|
|
642
635
|
/**
|
|
643
636
|
* Reads a query result from the cache, denormalizing entities if available.
|
|
@@ -647,13 +640,22 @@ var Cache = class {
|
|
|
647
640
|
* @returns Denormalized query result or null if not found.
|
|
648
641
|
*/
|
|
649
642
|
readQuery(artifact, variables) {
|
|
650
|
-
|
|
651
|
-
|
|
643
|
+
let stale = false;
|
|
644
|
+
const { data, partial } = denormalize(artifact.selections, this.#storage, this.#storage[RootFieldKey], variables, (storageKey, fieldKey) => {
|
|
645
|
+
if (this.#stale.has(storageKey) || this.#stale.has(makeDependencyKey(storageKey, fieldKey))) stale = true;
|
|
646
|
+
});
|
|
647
|
+
if (partial) return {
|
|
648
|
+
data: null,
|
|
649
|
+
stale: false
|
|
650
|
+
};
|
|
652
651
|
const key = makeMemoKey("query", artifact.name, stringify(variables));
|
|
653
652
|
const prev = this.#memo.get(key);
|
|
654
653
|
const result = prev === void 0 ? data : replaceEqualDeep(prev, data);
|
|
655
654
|
this.#memo.set(key, result);
|
|
656
|
-
return
|
|
655
|
+
return {
|
|
656
|
+
data: result,
|
|
657
|
+
stale
|
|
658
|
+
};
|
|
657
659
|
}
|
|
658
660
|
/**
|
|
659
661
|
* Subscribes to cache invalidations for a specific query.
|
|
@@ -679,14 +681,26 @@ var Cache = class {
|
|
|
679
681
|
*/
|
|
680
682
|
readFragment(artifact, fragmentRef) {
|
|
681
683
|
const entityKey = fragmentRef[FragmentRefKey];
|
|
682
|
-
if (!this.#storage[entityKey]) return
|
|
683
|
-
|
|
684
|
-
|
|
684
|
+
if (!this.#storage[entityKey]) return {
|
|
685
|
+
data: null,
|
|
686
|
+
stale: false
|
|
687
|
+
};
|
|
688
|
+
let stale = false;
|
|
689
|
+
const { data, partial } = denormalize(artifact.selections, this.#storage, { [EntityLinkKey]: entityKey }, {}, (storageKey, fieldKey) => {
|
|
690
|
+
if (this.#stale.has(storageKey) || this.#stale.has(makeDependencyKey(storageKey, fieldKey))) stale = true;
|
|
691
|
+
});
|
|
692
|
+
if (partial) return {
|
|
693
|
+
data: null,
|
|
694
|
+
stale: false
|
|
695
|
+
};
|
|
685
696
|
const key = makeMemoKey("fragment", artifact.name, entityKey);
|
|
686
697
|
const prev = this.#memo.get(key);
|
|
687
698
|
const result = prev === void 0 ? data : replaceEqualDeep(prev, data);
|
|
688
699
|
this.#memo.set(key, result);
|
|
689
|
-
return
|
|
700
|
+
return {
|
|
701
|
+
data: result,
|
|
702
|
+
stale
|
|
703
|
+
};
|
|
690
704
|
}
|
|
691
705
|
subscribeFragment(artifact, fragmentRef, listener) {
|
|
692
706
|
const entityKey = fragmentRef[FragmentRefKey];
|
|
@@ -699,17 +713,25 @@ var Cache = class {
|
|
|
699
713
|
}
|
|
700
714
|
readFragments(artifact, fragmentRefs) {
|
|
701
715
|
const results = [];
|
|
716
|
+
let stale = false;
|
|
702
717
|
for (const ref of fragmentRefs) {
|
|
703
|
-
const
|
|
704
|
-
if (data === null) return
|
|
705
|
-
|
|
718
|
+
const result = this.readFragment(artifact, ref);
|
|
719
|
+
if (result.data === null) return {
|
|
720
|
+
data: null,
|
|
721
|
+
stale: false
|
|
722
|
+
};
|
|
723
|
+
if (result.stale) stale = true;
|
|
724
|
+
results.push(result.data);
|
|
706
725
|
}
|
|
707
726
|
const entityKeys = fragmentRefs.map((ref) => ref[FragmentRefKey]);
|
|
708
727
|
const key = makeMemoKey("fragments", artifact.name, entityKeys.join(","));
|
|
709
728
|
const prev = this.#memo.get(key);
|
|
710
729
|
const result = prev === void 0 ? results : replaceEqualDeep(prev, results);
|
|
711
730
|
this.#memo.set(key, result);
|
|
712
|
-
return
|
|
731
|
+
return {
|
|
732
|
+
data: result,
|
|
733
|
+
stale
|
|
734
|
+
};
|
|
713
735
|
}
|
|
714
736
|
subscribeFragments(artifact, fragmentRefs, listener) {
|
|
715
737
|
const dependencies = /* @__PURE__ */ new Set();
|
|
@@ -727,50 +749,47 @@ var Cache = class {
|
|
|
727
749
|
*/
|
|
728
750
|
invalidate(...targets) {
|
|
729
751
|
const subscriptions = /* @__PURE__ */ new Set();
|
|
730
|
-
for (const target of targets) if (target.__typename === "Query") if ("field" in target) {
|
|
731
|
-
const fieldKey = makeFieldKeyFromArgs(target
|
|
732
|
-
|
|
752
|
+
for (const target of targets) if (target.__typename === "Query") if ("$field" in target) {
|
|
753
|
+
const fieldKey = makeFieldKeyFromArgs(target.$field, target.$args);
|
|
754
|
+
const depKey = makeDependencyKey(RootFieldKey, fieldKey);
|
|
755
|
+
this.#stale.add(depKey);
|
|
733
756
|
this.#collectSubscriptions(RootFieldKey, fieldKey, subscriptions);
|
|
734
757
|
} else {
|
|
735
|
-
this.#
|
|
758
|
+
this.#stale.add(RootFieldKey);
|
|
736
759
|
this.#collectSubscriptions(RootFieldKey, void 0, subscriptions);
|
|
737
760
|
}
|
|
738
|
-
else
|
|
739
|
-
const
|
|
740
|
-
if (
|
|
741
|
-
const
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
this.#collectSubscriptions(entityKey, void 0, subscriptions);
|
|
747
|
-
}
|
|
748
|
-
this.#collectLinkedEntitySubscriptions((linkedEntityKey) => linkedEntityKey === entityKey, subscriptions);
|
|
749
|
-
} else {
|
|
750
|
-
const prefix = `${target.__typename}:`;
|
|
751
|
-
for (const key of Object.keys(this.#storage)) if (key.startsWith(prefix)) {
|
|
752
|
-
const entityKey = key;
|
|
753
|
-
if ("field" in target) {
|
|
754
|
-
const fieldKey = makeFieldKeyFromArgs(target.field, target.args);
|
|
755
|
-
delete this.#storage[entityKey]?.[fieldKey];
|
|
761
|
+
else {
|
|
762
|
+
const keyFields = this.#schemaMeta.entities[target.__typename]?.keyFields;
|
|
763
|
+
if (keyFields && this.#hasKeyFields(target, keyFields)) {
|
|
764
|
+
const keyValues = keyFields.map((f) => target[f]);
|
|
765
|
+
const entityKey = makeEntityKey(target.__typename, keyValues);
|
|
766
|
+
if ("$field" in target) {
|
|
767
|
+
const fieldKey = makeFieldKeyFromArgs(target.$field, target.$args);
|
|
768
|
+
this.#stale.add(makeDependencyKey(entityKey, fieldKey));
|
|
756
769
|
this.#collectSubscriptions(entityKey, fieldKey, subscriptions);
|
|
757
770
|
} else {
|
|
758
|
-
|
|
771
|
+
this.#stale.add(entityKey);
|
|
759
772
|
this.#collectSubscriptions(entityKey, void 0, subscriptions);
|
|
760
773
|
}
|
|
774
|
+
} else {
|
|
775
|
+
const prefix = `${target.__typename}:`;
|
|
776
|
+
for (const key of Object.keys(this.#storage)) if (key.startsWith(prefix)) {
|
|
777
|
+
const entityKey = key;
|
|
778
|
+
if ("$field" in target) {
|
|
779
|
+
const fieldKey = makeFieldKeyFromArgs(target.$field, target.$args);
|
|
780
|
+
this.#stale.add(makeDependencyKey(entityKey, fieldKey));
|
|
781
|
+
this.#collectSubscriptions(entityKey, fieldKey, subscriptions);
|
|
782
|
+
} else {
|
|
783
|
+
this.#stale.add(entityKey);
|
|
784
|
+
this.#collectSubscriptions(entityKey, void 0, subscriptions);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
761
787
|
}
|
|
762
|
-
this.#collectLinkedEntitySubscriptions((linkedEntityKey) => linkedEntityKey.startsWith(prefix), subscriptions);
|
|
763
788
|
}
|
|
764
|
-
for (const subscription of subscriptions) subscription.listener(
|
|
765
|
-
}
|
|
766
|
-
#collectLinkedEntitySubscriptions(matcher, out) {
|
|
767
|
-
for (const [storageKey, fields] of Object.entries(this.#storage)) for (const [fieldKey, value] of Object.entries(fields)) if (this.#containsEntityLink(value, matcher)) this.#collectSubscriptions(storageKey, fieldKey, out);
|
|
789
|
+
for (const subscription of subscriptions) subscription.listener();
|
|
768
790
|
}
|
|
769
|
-
#
|
|
770
|
-
|
|
771
|
-
if (Array.isArray(value)) return value.some((item) => this.#containsEntityLink(item, matcher));
|
|
772
|
-
if (value && typeof value === "object") return Object.values(value).some((item) => this.#containsEntityLink(item, matcher));
|
|
773
|
-
return false;
|
|
791
|
+
#hasKeyFields(target, keyFields) {
|
|
792
|
+
return keyFields.every((f) => f in target);
|
|
774
793
|
}
|
|
775
794
|
#collectSubscriptions(storageKey, fieldKey, out) {
|
|
776
795
|
if (fieldKey === void 0) {
|
|
@@ -824,6 +843,7 @@ var Cache = class {
|
|
|
824
843
|
this.#storage = { [RootFieldKey]: {} };
|
|
825
844
|
this.#subscriptions.clear();
|
|
826
845
|
this.#memo.clear();
|
|
846
|
+
this.#stale.clear();
|
|
827
847
|
}
|
|
828
848
|
};
|
|
829
849
|
|
|
@@ -863,27 +883,16 @@ const cacheExchange = (options = {}) => {
|
|
|
863
883
|
});
|
|
864
884
|
if (isFragmentRefArray(fragmentRef)) {
|
|
865
885
|
const trigger = makeSubject();
|
|
866
|
-
let hasData = false;
|
|
867
886
|
const teardown$ = pipe(ops$, filter((operation) => operation.variant === "teardown" && operation.key === op.key), tap(() => trigger.complete()));
|
|
868
887
|
return pipe(merge(fromValue(void 0), trigger.source), switchMap(() => fromSubscription(() => cache.readFragments(op.artifact, fragmentRef), () => cache.subscribeFragments(op.artifact, fragmentRef, async () => {
|
|
869
888
|
await Promise.resolve();
|
|
870
889
|
trigger.next();
|
|
871
|
-
}))), takeUntil(teardown$),
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
errors: []
|
|
878
|
-
});
|
|
879
|
-
}
|
|
880
|
-
if (hasData) return empty();
|
|
881
|
-
return fromValue({
|
|
882
|
-
operation: op,
|
|
883
|
-
data,
|
|
884
|
-
errors: []
|
|
885
|
-
});
|
|
886
|
-
}));
|
|
890
|
+
}))), takeUntil(teardown$), map(({ data, stale }) => ({
|
|
891
|
+
operation: op,
|
|
892
|
+
data,
|
|
893
|
+
...stale && { metadata: { cache: { stale: true } } },
|
|
894
|
+
errors: []
|
|
895
|
+
})));
|
|
887
896
|
}
|
|
888
897
|
if (!isFragmentRef(fragmentRef)) return fromValue({
|
|
889
898
|
operation: op,
|
|
@@ -891,27 +900,16 @@ const cacheExchange = (options = {}) => {
|
|
|
891
900
|
errors: []
|
|
892
901
|
});
|
|
893
902
|
const trigger = makeSubject();
|
|
894
|
-
let hasData = false;
|
|
895
903
|
const teardown$ = pipe(ops$, filter((operation) => operation.variant === "teardown" && operation.key === op.key), tap(() => trigger.complete()));
|
|
896
904
|
return pipe(merge(fromValue(void 0), trigger.source), switchMap(() => fromSubscription(() => cache.readFragment(op.artifact, fragmentRef), () => cache.subscribeFragment(op.artifact, fragmentRef, async () => {
|
|
897
905
|
await Promise.resolve();
|
|
898
906
|
trigger.next();
|
|
899
|
-
}))), takeUntil(teardown$),
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
errors: []
|
|
906
|
-
});
|
|
907
|
-
}
|
|
908
|
-
if (hasData) return empty();
|
|
909
|
-
return fromValue({
|
|
910
|
-
operation: op,
|
|
911
|
-
data,
|
|
912
|
-
errors: []
|
|
913
|
-
});
|
|
914
|
-
}));
|
|
907
|
+
}))), takeUntil(teardown$), map(({ data, stale }) => ({
|
|
908
|
+
operation: op,
|
|
909
|
+
data,
|
|
910
|
+
...stale && { metadata: { cache: { stale: true } } },
|
|
911
|
+
errors: []
|
|
912
|
+
})));
|
|
915
913
|
}));
|
|
916
914
|
const nonCache$ = pipe(ops$, filter((op) => op.variant === "request" && (op.artifact.kind === "mutation" || op.artifact.kind === "subscription" || op.artifact.kind === "query" && fetchPolicy === "network-only")));
|
|
917
915
|
const query$ = pipe(ops$, filter((op) => op.variant === "request" && op.artifact.kind === "query" && fetchPolicy !== "network-only"), share());
|
|
@@ -919,30 +917,31 @@ const cacheExchange = (options = {}) => {
|
|
|
919
917
|
return merge(fragment$, pipe(query$, mergeMap((op) => {
|
|
920
918
|
const trigger = makeSubject();
|
|
921
919
|
let hasData = false;
|
|
922
|
-
let invalidated = false;
|
|
923
920
|
const teardown$ = pipe(ops$, filter((operation) => operation.variant === "teardown" && operation.key === op.key), tap(() => trigger.complete()));
|
|
924
|
-
return pipe(merge(fromValue(void 0), trigger.source), switchMap(() => fromSubscription(() => cache.readQuery(op.artifact, op.variables), () => cache.subscribeQuery(op.artifact, op.variables, async (
|
|
925
|
-
if (event === "invalidate") invalidated = true;
|
|
921
|
+
return pipe(merge(fromValue(void 0), trigger.source), switchMap(() => fromSubscription(() => cache.readQuery(op.artifact, op.variables), () => cache.subscribeQuery(op.artifact, op.variables, async () => {
|
|
926
922
|
await Promise.resolve();
|
|
927
923
|
trigger.next();
|
|
928
|
-
}))), takeUntil(teardown$), mergeMap((data) => {
|
|
929
|
-
if (data !== null) {
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
924
|
+
}))), takeUntil(teardown$), mergeMap(({ data, stale }) => {
|
|
925
|
+
if (data !== null && !stale) {
|
|
926
|
+
hasData = true;
|
|
927
|
+
return fromValue({
|
|
928
|
+
operation: op,
|
|
929
|
+
data,
|
|
930
|
+
errors: []
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
if (data !== null && stale) {
|
|
935
934
|
hasData = true;
|
|
936
|
-
|
|
935
|
+
refetch$.next(op);
|
|
937
936
|
return fromValue({
|
|
938
937
|
operation: op,
|
|
939
938
|
data,
|
|
939
|
+
metadata: { cache: { stale: true } },
|
|
940
940
|
errors: []
|
|
941
941
|
});
|
|
942
942
|
}
|
|
943
943
|
if (hasData) {
|
|
944
|
-
|
|
945
|
-
invalidated = false;
|
|
944
|
+
refetch$.next(op);
|
|
946
945
|
return empty();
|
|
947
946
|
}
|
|
948
947
|
if (fetchPolicy === "cache-only") return fromValue({
|
|
@@ -953,8 +952,8 @@ const cacheExchange = (options = {}) => {
|
|
|
953
952
|
return empty();
|
|
954
953
|
}));
|
|
955
954
|
}), filter(() => fetchPolicy === "cache-only" || fetchPolicy === "cache-and-network" || fetchPolicy === "cache-first")), pipe(merge(nonCache$, pipe(query$, filter((op) => {
|
|
956
|
-
const
|
|
957
|
-
return fetchPolicy === "cache-and-network" ||
|
|
955
|
+
const { data } = cache.readQuery(op.artifact, op.variables);
|
|
956
|
+
return fetchPolicy === "cache-and-network" || data === null;
|
|
958
957
|
})), pipe(ops$, filter((op) => op.variant === "teardown")), refetch$.source), forward, tap((result) => {
|
|
959
958
|
if (result.operation.variant === "request" && result.data) cache.writeQuery(result.operation.artifact, result.operation.variables, result.data);
|
|
960
959
|
}), filter((result) => result.operation.variant !== "request" || result.operation.artifact.kind !== "query" || fetchPolicy === "network-only" || !!(result.errors && result.errors.length > 0))));
|
package/package.json
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mearie/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Type-safe, zero-overhead GraphQL client",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"graphql",
|
|
7
7
|
"graphql-client",
|
|
8
8
|
"typescript",
|
|
9
|
+
"type-safe",
|
|
9
10
|
"codegen",
|
|
10
|
-
"cache"
|
|
11
|
+
"cache",
|
|
12
|
+
"normalized-cache",
|
|
13
|
+
"react",
|
|
14
|
+
"vue",
|
|
15
|
+
"svelte",
|
|
16
|
+
"solid",
|
|
17
|
+
"vite"
|
|
11
18
|
],
|
|
12
|
-
"homepage": "https://
|
|
19
|
+
"homepage": "https://mearie.dev/",
|
|
13
20
|
"bugs": {
|
|
14
21
|
"url": "https://github.com/devunt/mearie/issues"
|
|
15
22
|
},
|
|
@@ -55,7 +62,7 @@
|
|
|
55
62
|
"README.md"
|
|
56
63
|
],
|
|
57
64
|
"dependencies": {
|
|
58
|
-
"@mearie/shared": "0.
|
|
65
|
+
"@mearie/shared": "0.3.0"
|
|
59
66
|
},
|
|
60
67
|
"devDependencies": {
|
|
61
68
|
"tsdown": "^0.20.3",
|