@mearie/core 0.4.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 +135 -23
- package/dist/index.d.cts +15 -8
- package/dist/index.d.mts +15 -8
- package/dist/index.mjs +135 -23
- 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.
|
|
@@ -504,18 +518,17 @@ const makeFieldKeyFromArgs = (field, args) => {
|
|
|
504
518
|
|
|
505
519
|
//#endregion
|
|
506
520
|
//#region src/cache/normalize.ts
|
|
507
|
-
const SKIP = Symbol();
|
|
508
521
|
const normalize = (schemaMeta, selections, storage, data, variables, accessor) => {
|
|
509
522
|
const normalizeField = (storageKey, selections, value) => {
|
|
510
523
|
if (isNullish(value)) return value;
|
|
511
524
|
if (Array.isArray(value)) return value.map((item) => normalizeField(storageKey, selections, item));
|
|
512
525
|
const data = value;
|
|
513
526
|
const typename = data.__typename;
|
|
514
|
-
|
|
527
|
+
let entityMeta = schemaMeta.entities[typename];
|
|
515
528
|
if (entityMeta) {
|
|
516
529
|
const keys = entityMeta.keyFields.map((field) => data[field]);
|
|
517
|
-
if (
|
|
518
|
-
|
|
530
|
+
if (keys.every((k) => k !== void 0 && k !== null)) storageKey = makeEntityKey(typename, keys);
|
|
531
|
+
else entityMeta = void 0;
|
|
519
532
|
}
|
|
520
533
|
const fields = {};
|
|
521
534
|
for (const selection of selections) if (selection.kind === "Field") {
|
|
@@ -523,13 +536,11 @@ const normalize = (schemaMeta, selections, storage, data, variables, accessor) =
|
|
|
523
536
|
const fieldValue = data[selection.alias ?? selection.name];
|
|
524
537
|
const oldValue = storageKey === null ? void 0 : storage[storageKey]?.[fieldKey];
|
|
525
538
|
if (storageKey !== null && (!selection.selections || isNullish(oldValue) || isNullish(fieldValue))) accessor?.(storageKey, fieldKey, oldValue, fieldValue);
|
|
526
|
-
|
|
527
|
-
if (normalized === SKIP) continue;
|
|
528
|
-
fields[fieldKey] = normalized;
|
|
539
|
+
fields[fieldKey] = selection.selections ? normalizeField(null, selection.selections, fieldValue) : fieldValue;
|
|
529
540
|
if (storageKey !== null && selection.selections && !isNullish(oldValue) && !isNullish(fieldValue) && !isEntityLink(fields[fieldKey]) && !isEqual(oldValue, fields[fieldKey])) accessor?.(storageKey, fieldKey, oldValue, fields[fieldKey]);
|
|
530
541
|
} else if (selection.kind === "FragmentSpread" || selection.kind === "InlineFragment" && selection.on === typename) {
|
|
531
542
|
const inner = normalizeField(storageKey, selection.selections, value);
|
|
532
|
-
if (
|
|
543
|
+
if (!isEntityLink(inner)) mergeFields(fields, inner);
|
|
533
544
|
}
|
|
534
545
|
if (entityMeta && storageKey !== null) {
|
|
535
546
|
const existing = storage[storageKey];
|
|
@@ -584,6 +595,17 @@ const denormalize = (selections, storage, value, variables, accessor) => {
|
|
|
584
595
|
else fields[name] = value;
|
|
585
596
|
} else if (selection.kind === "FragmentSpread") if (storageKey !== null && storageKey !== RootFieldKey) {
|
|
586
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
|
+
}
|
|
587
609
|
if (accessor) denormalize(selection.selections, storage, { [EntityLinkKey]: storageKey }, variables, accessor);
|
|
588
610
|
} else mergeFields(fields, denormalizeField(storageKey, selection.selections, value));
|
|
589
611
|
else if (selection.kind === "InlineFragment" && selection.on === data[typenameFieldKey]) mergeFields(fields, denormalizeField(storageKey, selection.selections, value));
|
|
@@ -607,9 +629,74 @@ var Cache = class {
|
|
|
607
629
|
#subscriptions = /* @__PURE__ */ new Map();
|
|
608
630
|
#memo = /* @__PURE__ */ new Map();
|
|
609
631
|
#stale = /* @__PURE__ */ new Set();
|
|
632
|
+
#optimisticKeys = [];
|
|
633
|
+
#optimisticLayers = /* @__PURE__ */ new Map();
|
|
634
|
+
#storageView = null;
|
|
610
635
|
constructor(schemaMetadata) {
|
|
611
636
|
this.#schemaMeta = schemaMetadata;
|
|
612
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
|
+
}
|
|
613
700
|
/**
|
|
614
701
|
* Writes a query result to the cache, normalizing entities.
|
|
615
702
|
* @param artifact - GraphQL document artifact.
|
|
@@ -642,7 +729,8 @@ var Cache = class {
|
|
|
642
729
|
*/
|
|
643
730
|
readQuery(artifact, variables) {
|
|
644
731
|
let stale = false;
|
|
645
|
-
const
|
|
732
|
+
const storage = this.#getStorageView();
|
|
733
|
+
const { data, partial } = denormalize(artifact.selections, storage, storage[RootFieldKey], variables, (storageKey, fieldKey) => {
|
|
646
734
|
if (this.#stale.has(storageKey) || this.#stale.has(makeDependencyKey(storageKey, fieldKey))) stale = true;
|
|
647
735
|
});
|
|
648
736
|
if (partial) return {
|
|
@@ -667,7 +755,8 @@ var Cache = class {
|
|
|
667
755
|
*/
|
|
668
756
|
subscribeQuery(artifact, variables, listener) {
|
|
669
757
|
const dependencies = /* @__PURE__ */ new Set();
|
|
670
|
-
|
|
758
|
+
const storageView = this.#getStorageView();
|
|
759
|
+
denormalize(artifact.selections, storageView, storageView[RootFieldKey], variables, (storageKey, fieldKey) => {
|
|
671
760
|
const dependencyKey = makeDependencyKey(storageKey, fieldKey);
|
|
672
761
|
dependencies.add(dependencyKey);
|
|
673
762
|
});
|
|
@@ -682,19 +771,22 @@ var Cache = class {
|
|
|
682
771
|
*/
|
|
683
772
|
readFragment(artifact, fragmentRef) {
|
|
684
773
|
const entityKey = fragmentRef[FragmentRefKey];
|
|
685
|
-
|
|
774
|
+
const fragmentVars = getFragmentVars(fragmentRef, artifact.name);
|
|
775
|
+
const storageView = this.#getStorageView();
|
|
776
|
+
if (!storageView[entityKey]) return {
|
|
686
777
|
data: null,
|
|
687
778
|
stale: false
|
|
688
779
|
};
|
|
689
780
|
let stale = false;
|
|
690
|
-
const { data, partial } = denormalize(artifact.selections,
|
|
781
|
+
const { data, partial } = denormalize(artifact.selections, storageView, { [EntityLinkKey]: entityKey }, fragmentVars, (storageKey, fieldKey) => {
|
|
691
782
|
if (this.#stale.has(storageKey) || this.#stale.has(makeDependencyKey(storageKey, fieldKey))) stale = true;
|
|
692
783
|
});
|
|
693
784
|
if (partial) return {
|
|
694
785
|
data: null,
|
|
695
786
|
stale: false
|
|
696
787
|
};
|
|
697
|
-
const
|
|
788
|
+
const argsId = Object.keys(fragmentVars).length > 0 ? entityKey + stringify(fragmentVars) : entityKey;
|
|
789
|
+
const key = makeMemoKey("fragment", artifact.name, argsId);
|
|
698
790
|
const prev = this.#memo.get(key);
|
|
699
791
|
const result = prev === void 0 ? data : replaceEqualDeep(prev, data);
|
|
700
792
|
this.#memo.set(key, result);
|
|
@@ -705,8 +797,10 @@ var Cache = class {
|
|
|
705
797
|
}
|
|
706
798
|
subscribeFragment(artifact, fragmentRef, listener) {
|
|
707
799
|
const entityKey = fragmentRef[FragmentRefKey];
|
|
800
|
+
const fragmentVars = getFragmentVars(fragmentRef, artifact.name);
|
|
708
801
|
const dependencies = /* @__PURE__ */ new Set();
|
|
709
|
-
|
|
802
|
+
const storageView = this.#getStorageView();
|
|
803
|
+
denormalize(artifact.selections, storageView, { [EntityLinkKey]: entityKey }, fragmentVars, (storageKey, fieldKey) => {
|
|
710
804
|
const dependencyKey = makeDependencyKey(storageKey, fieldKey);
|
|
711
805
|
dependencies.add(dependencyKey);
|
|
712
806
|
});
|
|
@@ -736,9 +830,11 @@ var Cache = class {
|
|
|
736
830
|
}
|
|
737
831
|
subscribeFragments(artifact, fragmentRefs, listener) {
|
|
738
832
|
const dependencies = /* @__PURE__ */ new Set();
|
|
833
|
+
const storageView = this.#getStorageView();
|
|
739
834
|
for (const ref of fragmentRefs) {
|
|
740
835
|
const entityKey = ref[FragmentRefKey];
|
|
741
|
-
|
|
836
|
+
const fragmentVars = getFragmentVars(ref, artifact.name);
|
|
837
|
+
denormalize(artifact.selections, storageView, { [EntityLinkKey]: entityKey }, fragmentVars, (storageKey, fieldKey) => {
|
|
742
838
|
dependencies.add(makeDependencyKey(storageKey, fieldKey));
|
|
743
839
|
});
|
|
744
840
|
}
|
|
@@ -819,6 +915,7 @@ var Cache = class {
|
|
|
819
915
|
}
|
|
820
916
|
/**
|
|
821
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.
|
|
822
919
|
*/
|
|
823
920
|
extract() {
|
|
824
921
|
return {
|
|
@@ -836,6 +933,7 @@ var Cache = class {
|
|
|
836
933
|
...fields
|
|
837
934
|
};
|
|
838
935
|
for (const [key, value] of Object.entries(memo)) this.#memo.set(key, value);
|
|
936
|
+
this.#storageView = null;
|
|
839
937
|
}
|
|
840
938
|
/**
|
|
841
939
|
* Clears all cache data.
|
|
@@ -845,6 +943,9 @@ var Cache = class {
|
|
|
845
943
|
this.#subscriptions.clear();
|
|
846
944
|
this.#memo.clear();
|
|
847
945
|
this.#stale.clear();
|
|
946
|
+
this.#optimisticKeys = [];
|
|
947
|
+
this.#optimisticLayers.clear();
|
|
948
|
+
this.#storageView = null;
|
|
848
949
|
}
|
|
849
950
|
};
|
|
850
951
|
|
|
@@ -877,10 +978,10 @@ const cacheExchange = (options = {}) => {
|
|
|
877
978
|
},
|
|
878
979
|
io: (ops$) => {
|
|
879
980
|
const fragment$ = require_make.pipe(ops$, require_make.filter((op) => op.variant === "request" && op.artifact.kind === "fragment"), require_make.mergeMap((op) => {
|
|
880
|
-
const fragmentRef = op.metadata?.
|
|
981
|
+
const fragmentRef = op.metadata?.fragment?.ref;
|
|
881
982
|
if (!fragmentRef) return require_make.fromValue({
|
|
882
983
|
operation: op,
|
|
883
|
-
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" })]
|
|
884
985
|
});
|
|
885
986
|
if (isFragmentRefArray(fragmentRef)) {
|
|
886
987
|
const trigger = require_make.makeSubject();
|
|
@@ -912,7 +1013,9 @@ const cacheExchange = (options = {}) => {
|
|
|
912
1013
|
errors: []
|
|
913
1014
|
})));
|
|
914
1015
|
}));
|
|
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")))
|
|
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
|
+
}));
|
|
916
1019
|
const query$ = require_make.pipe(ops$, require_make.filter((op) => op.variant === "request" && op.artifact.kind === "query" && fetchPolicy !== "network-only"), require_make.share());
|
|
917
1020
|
const refetch$ = require_make.makeSubject();
|
|
918
1021
|
return require_make.merge(fragment$, require_make.pipe(query$, require_make.mergeMap((op) => {
|
|
@@ -955,9 +1058,18 @@ const cacheExchange = (options = {}) => {
|
|
|
955
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) => {
|
|
956
1059
|
const { data } = cache.readQuery(op.artifact, op.variables);
|
|
957
1060
|
return fetchPolicy === "cache-and-network" || data === null;
|
|
958
|
-
})), 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);
|
|
959
1063
|
if (result.operation.variant === "request" && result.data) cache.writeQuery(result.operation.artifact, result.operation.variables, result.data);
|
|
960
|
-
|
|
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
|
+
})));
|
|
961
1073
|
}
|
|
962
1074
|
};
|
|
963
1075
|
};
|
|
@@ -1009,10 +1121,10 @@ const fragmentExchange = () => {
|
|
|
1009
1121
|
name: "fragment",
|
|
1010
1122
|
io: (ops$) => {
|
|
1011
1123
|
return require_make.merge(require_make.pipe(ops$, require_make.filter((op) => op.variant === "request" && op.artifact.kind === "fragment"), require_make.map((op) => {
|
|
1012
|
-
const fragmentRef = op.metadata.
|
|
1124
|
+
const fragmentRef = op.metadata.fragment?.ref;
|
|
1013
1125
|
if (!fragmentRef) return {
|
|
1014
1126
|
operation: op,
|
|
1015
|
-
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" })]
|
|
1016
1128
|
};
|
|
1017
1129
|
return {
|
|
1018
1130
|
operation: op,
|
|
@@ -1398,7 +1510,7 @@ var Client = class {
|
|
|
1398
1510
|
key: this.createOperationKey(),
|
|
1399
1511
|
metadata: {
|
|
1400
1512
|
...options?.metadata,
|
|
1401
|
-
fragmentRef
|
|
1513
|
+
fragment: { ref: fragmentRef }
|
|
1402
1514
|
},
|
|
1403
1515
|
artifact,
|
|
1404
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,14 +93,14 @@ 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.
|
|
@@ -115,9 +115,9 @@ declare class Client<TMeta extends SchemaMeta$1 = SchemaMeta$1> {
|
|
|
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>;
|
|
@@ -248,6 +248,11 @@ declare module '@mearie/core' {
|
|
|
248
248
|
interface ExchangeExtensionMap<TMeta extends SchemaMeta$1> {
|
|
249
249
|
cache: CacheOperations<TMeta>;
|
|
250
250
|
}
|
|
251
|
+
interface OperationMetadataMap<T extends Artifact$1> {
|
|
252
|
+
cache?: {
|
|
253
|
+
optimisticResponse?: T extends Artifact$1<'mutation'> ? DataOf$1<T> : never;
|
|
254
|
+
};
|
|
255
|
+
}
|
|
251
256
|
interface OperationResultMetadataMap {
|
|
252
257
|
cache?: {
|
|
253
258
|
stale: boolean;
|
|
@@ -278,7 +283,9 @@ declare const retryExchange: (options?: RetryOptions) => Exchange;
|
|
|
278
283
|
//#region src/exchanges/fragment.d.ts
|
|
279
284
|
declare module '@mearie/core' {
|
|
280
285
|
interface OperationMetadataMap {
|
|
281
|
-
|
|
286
|
+
fragment?: {
|
|
287
|
+
ref?: unknown;
|
|
288
|
+
};
|
|
282
289
|
}
|
|
283
290
|
}
|
|
284
291
|
declare const fragmentExchange: () => Exchange;
|
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,14 +93,14 @@ 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.
|
|
@@ -115,9 +115,9 @@ declare class Client<TMeta extends SchemaMeta$1 = SchemaMeta$1> {
|
|
|
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>;
|
|
@@ -248,6 +248,11 @@ declare module '@mearie/core' {
|
|
|
248
248
|
interface ExchangeExtensionMap<TMeta extends SchemaMeta$1> {
|
|
249
249
|
cache: CacheOperations<TMeta>;
|
|
250
250
|
}
|
|
251
|
+
interface OperationMetadataMap<T extends Artifact$1> {
|
|
252
|
+
cache?: {
|
|
253
|
+
optimisticResponse?: T extends Artifact$1<'mutation'> ? DataOf$1<T> : never;
|
|
254
|
+
};
|
|
255
|
+
}
|
|
251
256
|
interface OperationResultMetadataMap {
|
|
252
257
|
cache?: {
|
|
253
258
|
stale: boolean;
|
|
@@ -278,7 +283,9 @@ declare const retryExchange: (options?: RetryOptions) => Exchange;
|
|
|
278
283
|
//#region src/exchanges/fragment.d.ts
|
|
279
284
|
declare module '@mearie/core' {
|
|
280
285
|
interface OperationMetadataMap {
|
|
281
|
-
|
|
286
|
+
fragment?: {
|
|
287
|
+
ref?: unknown;
|
|
288
|
+
};
|
|
282
289
|
}
|
|
283
290
|
}
|
|
284
291
|
declare const fragmentExchange: () => Exchange;
|
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.
|
|
@@ -503,18 +517,17 @@ const makeFieldKeyFromArgs = (field, args) => {
|
|
|
503
517
|
|
|
504
518
|
//#endregion
|
|
505
519
|
//#region src/cache/normalize.ts
|
|
506
|
-
const SKIP = Symbol();
|
|
507
520
|
const normalize = (schemaMeta, selections, storage, data, variables, accessor) => {
|
|
508
521
|
const normalizeField = (storageKey, selections, value) => {
|
|
509
522
|
if (isNullish(value)) return value;
|
|
510
523
|
if (Array.isArray(value)) return value.map((item) => normalizeField(storageKey, selections, item));
|
|
511
524
|
const data = value;
|
|
512
525
|
const typename = data.__typename;
|
|
513
|
-
|
|
526
|
+
let entityMeta = schemaMeta.entities[typename];
|
|
514
527
|
if (entityMeta) {
|
|
515
528
|
const keys = entityMeta.keyFields.map((field) => data[field]);
|
|
516
|
-
if (
|
|
517
|
-
|
|
529
|
+
if (keys.every((k) => k !== void 0 && k !== null)) storageKey = makeEntityKey(typename, keys);
|
|
530
|
+
else entityMeta = void 0;
|
|
518
531
|
}
|
|
519
532
|
const fields = {};
|
|
520
533
|
for (const selection of selections) if (selection.kind === "Field") {
|
|
@@ -522,13 +535,11 @@ const normalize = (schemaMeta, selections, storage, data, variables, accessor) =
|
|
|
522
535
|
const fieldValue = data[selection.alias ?? selection.name];
|
|
523
536
|
const oldValue = storageKey === null ? void 0 : storage[storageKey]?.[fieldKey];
|
|
524
537
|
if (storageKey !== null && (!selection.selections || isNullish(oldValue) || isNullish(fieldValue))) accessor?.(storageKey, fieldKey, oldValue, fieldValue);
|
|
525
|
-
|
|
526
|
-
if (normalized === SKIP) continue;
|
|
527
|
-
fields[fieldKey] = normalized;
|
|
538
|
+
fields[fieldKey] = selection.selections ? normalizeField(null, selection.selections, fieldValue) : fieldValue;
|
|
528
539
|
if (storageKey !== null && selection.selections && !isNullish(oldValue) && !isNullish(fieldValue) && !isEntityLink(fields[fieldKey]) && !isEqual(oldValue, fields[fieldKey])) accessor?.(storageKey, fieldKey, oldValue, fields[fieldKey]);
|
|
529
540
|
} else if (selection.kind === "FragmentSpread" || selection.kind === "InlineFragment" && selection.on === typename) {
|
|
530
541
|
const inner = normalizeField(storageKey, selection.selections, value);
|
|
531
|
-
if (
|
|
542
|
+
if (!isEntityLink(inner)) mergeFields(fields, inner);
|
|
532
543
|
}
|
|
533
544
|
if (entityMeta && storageKey !== null) {
|
|
534
545
|
const existing = storage[storageKey];
|
|
@@ -583,6 +594,17 @@ const denormalize = (selections, storage, value, variables, accessor) => {
|
|
|
583
594
|
else fields[name] = value;
|
|
584
595
|
} else if (selection.kind === "FragmentSpread") if (storageKey !== null && storageKey !== RootFieldKey) {
|
|
585
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
|
+
}
|
|
586
608
|
if (accessor) denormalize(selection.selections, storage, { [EntityLinkKey]: storageKey }, variables, accessor);
|
|
587
609
|
} else mergeFields(fields, denormalizeField(storageKey, selection.selections, value));
|
|
588
610
|
else if (selection.kind === "InlineFragment" && selection.on === data[typenameFieldKey]) mergeFields(fields, denormalizeField(storageKey, selection.selections, value));
|
|
@@ -606,9 +628,74 @@ var Cache = class {
|
|
|
606
628
|
#subscriptions = /* @__PURE__ */ new Map();
|
|
607
629
|
#memo = /* @__PURE__ */ new Map();
|
|
608
630
|
#stale = /* @__PURE__ */ new Set();
|
|
631
|
+
#optimisticKeys = [];
|
|
632
|
+
#optimisticLayers = /* @__PURE__ */ new Map();
|
|
633
|
+
#storageView = null;
|
|
609
634
|
constructor(schemaMetadata) {
|
|
610
635
|
this.#schemaMeta = schemaMetadata;
|
|
611
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
|
+
}
|
|
612
699
|
/**
|
|
613
700
|
* Writes a query result to the cache, normalizing entities.
|
|
614
701
|
* @param artifact - GraphQL document artifact.
|
|
@@ -641,7 +728,8 @@ var Cache = class {
|
|
|
641
728
|
*/
|
|
642
729
|
readQuery(artifact, variables) {
|
|
643
730
|
let stale = false;
|
|
644
|
-
const
|
|
731
|
+
const storage = this.#getStorageView();
|
|
732
|
+
const { data, partial } = denormalize(artifact.selections, storage, storage[RootFieldKey], variables, (storageKey, fieldKey) => {
|
|
645
733
|
if (this.#stale.has(storageKey) || this.#stale.has(makeDependencyKey(storageKey, fieldKey))) stale = true;
|
|
646
734
|
});
|
|
647
735
|
if (partial) return {
|
|
@@ -666,7 +754,8 @@ var Cache = class {
|
|
|
666
754
|
*/
|
|
667
755
|
subscribeQuery(artifact, variables, listener) {
|
|
668
756
|
const dependencies = /* @__PURE__ */ new Set();
|
|
669
|
-
|
|
757
|
+
const storageView = this.#getStorageView();
|
|
758
|
+
denormalize(artifact.selections, storageView, storageView[RootFieldKey], variables, (storageKey, fieldKey) => {
|
|
670
759
|
const dependencyKey = makeDependencyKey(storageKey, fieldKey);
|
|
671
760
|
dependencies.add(dependencyKey);
|
|
672
761
|
});
|
|
@@ -681,19 +770,22 @@ var Cache = class {
|
|
|
681
770
|
*/
|
|
682
771
|
readFragment(artifact, fragmentRef) {
|
|
683
772
|
const entityKey = fragmentRef[FragmentRefKey];
|
|
684
|
-
|
|
773
|
+
const fragmentVars = getFragmentVars(fragmentRef, artifact.name);
|
|
774
|
+
const storageView = this.#getStorageView();
|
|
775
|
+
if (!storageView[entityKey]) return {
|
|
685
776
|
data: null,
|
|
686
777
|
stale: false
|
|
687
778
|
};
|
|
688
779
|
let stale = false;
|
|
689
|
-
const { data, partial } = denormalize(artifact.selections,
|
|
780
|
+
const { data, partial } = denormalize(artifact.selections, storageView, { [EntityLinkKey]: entityKey }, fragmentVars, (storageKey, fieldKey) => {
|
|
690
781
|
if (this.#stale.has(storageKey) || this.#stale.has(makeDependencyKey(storageKey, fieldKey))) stale = true;
|
|
691
782
|
});
|
|
692
783
|
if (partial) return {
|
|
693
784
|
data: null,
|
|
694
785
|
stale: false
|
|
695
786
|
};
|
|
696
|
-
const
|
|
787
|
+
const argsId = Object.keys(fragmentVars).length > 0 ? entityKey + stringify(fragmentVars) : entityKey;
|
|
788
|
+
const key = makeMemoKey("fragment", artifact.name, argsId);
|
|
697
789
|
const prev = this.#memo.get(key);
|
|
698
790
|
const result = prev === void 0 ? data : replaceEqualDeep(prev, data);
|
|
699
791
|
this.#memo.set(key, result);
|
|
@@ -704,8 +796,10 @@ var Cache = class {
|
|
|
704
796
|
}
|
|
705
797
|
subscribeFragment(artifact, fragmentRef, listener) {
|
|
706
798
|
const entityKey = fragmentRef[FragmentRefKey];
|
|
799
|
+
const fragmentVars = getFragmentVars(fragmentRef, artifact.name);
|
|
707
800
|
const dependencies = /* @__PURE__ */ new Set();
|
|
708
|
-
|
|
801
|
+
const storageView = this.#getStorageView();
|
|
802
|
+
denormalize(artifact.selections, storageView, { [EntityLinkKey]: entityKey }, fragmentVars, (storageKey, fieldKey) => {
|
|
709
803
|
const dependencyKey = makeDependencyKey(storageKey, fieldKey);
|
|
710
804
|
dependencies.add(dependencyKey);
|
|
711
805
|
});
|
|
@@ -735,9 +829,11 @@ var Cache = class {
|
|
|
735
829
|
}
|
|
736
830
|
subscribeFragments(artifact, fragmentRefs, listener) {
|
|
737
831
|
const dependencies = /* @__PURE__ */ new Set();
|
|
832
|
+
const storageView = this.#getStorageView();
|
|
738
833
|
for (const ref of fragmentRefs) {
|
|
739
834
|
const entityKey = ref[FragmentRefKey];
|
|
740
|
-
|
|
835
|
+
const fragmentVars = getFragmentVars(ref, artifact.name);
|
|
836
|
+
denormalize(artifact.selections, storageView, { [EntityLinkKey]: entityKey }, fragmentVars, (storageKey, fieldKey) => {
|
|
741
837
|
dependencies.add(makeDependencyKey(storageKey, fieldKey));
|
|
742
838
|
});
|
|
743
839
|
}
|
|
@@ -818,6 +914,7 @@ var Cache = class {
|
|
|
818
914
|
}
|
|
819
915
|
/**
|
|
820
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.
|
|
821
918
|
*/
|
|
822
919
|
extract() {
|
|
823
920
|
return {
|
|
@@ -835,6 +932,7 @@ var Cache = class {
|
|
|
835
932
|
...fields
|
|
836
933
|
};
|
|
837
934
|
for (const [key, value] of Object.entries(memo)) this.#memo.set(key, value);
|
|
935
|
+
this.#storageView = null;
|
|
838
936
|
}
|
|
839
937
|
/**
|
|
840
938
|
* Clears all cache data.
|
|
@@ -844,6 +942,9 @@ var Cache = class {
|
|
|
844
942
|
this.#subscriptions.clear();
|
|
845
943
|
this.#memo.clear();
|
|
846
944
|
this.#stale.clear();
|
|
945
|
+
this.#optimisticKeys = [];
|
|
946
|
+
this.#optimisticLayers.clear();
|
|
947
|
+
this.#storageView = null;
|
|
847
948
|
}
|
|
848
949
|
};
|
|
849
950
|
|
|
@@ -876,10 +977,10 @@ const cacheExchange = (options = {}) => {
|
|
|
876
977
|
},
|
|
877
978
|
io: (ops$) => {
|
|
878
979
|
const fragment$ = pipe(ops$, filter((op) => op.variant === "request" && op.artifact.kind === "fragment"), mergeMap((op) => {
|
|
879
|
-
const fragmentRef = op.metadata?.
|
|
980
|
+
const fragmentRef = op.metadata?.fragment?.ref;
|
|
880
981
|
if (!fragmentRef) return fromValue({
|
|
881
982
|
operation: op,
|
|
882
|
-
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" })]
|
|
883
984
|
});
|
|
884
985
|
if (isFragmentRefArray(fragmentRef)) {
|
|
885
986
|
const trigger = makeSubject();
|
|
@@ -911,7 +1012,9 @@ const cacheExchange = (options = {}) => {
|
|
|
911
1012
|
errors: []
|
|
912
1013
|
})));
|
|
913
1014
|
}));
|
|
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")))
|
|
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
|
+
}));
|
|
915
1018
|
const query$ = pipe(ops$, filter((op) => op.variant === "request" && op.artifact.kind === "query" && fetchPolicy !== "network-only"), share());
|
|
916
1019
|
const refetch$ = makeSubject();
|
|
917
1020
|
return merge(fragment$, pipe(query$, mergeMap((op) => {
|
|
@@ -954,9 +1057,18 @@ const cacheExchange = (options = {}) => {
|
|
|
954
1057
|
}), filter(() => fetchPolicy === "cache-only" || fetchPolicy === "cache-and-network" || fetchPolicy === "cache-first")), pipe(merge(nonCache$, pipe(query$, filter((op) => {
|
|
955
1058
|
const { data } = cache.readQuery(op.artifact, op.variables);
|
|
956
1059
|
return fetchPolicy === "cache-and-network" || data === null;
|
|
957
|
-
})), 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);
|
|
958
1062
|
if (result.operation.variant === "request" && result.data) cache.writeQuery(result.operation.artifact, result.operation.variables, result.data);
|
|
959
|
-
|
|
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
|
+
})));
|
|
960
1072
|
}
|
|
961
1073
|
};
|
|
962
1074
|
};
|
|
@@ -1008,10 +1120,10 @@ const fragmentExchange = () => {
|
|
|
1008
1120
|
name: "fragment",
|
|
1009
1121
|
io: (ops$) => {
|
|
1010
1122
|
return merge(pipe(ops$, filter((op) => op.variant === "request" && op.artifact.kind === "fragment"), map((op) => {
|
|
1011
|
-
const fragmentRef = op.metadata.
|
|
1123
|
+
const fragmentRef = op.metadata.fragment?.ref;
|
|
1012
1124
|
if (!fragmentRef) return {
|
|
1013
1125
|
operation: op,
|
|
1014
|
-
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" })]
|
|
1015
1127
|
};
|
|
1016
1128
|
return {
|
|
1017
1129
|
operation: op,
|
|
@@ -1397,7 +1509,7 @@ var Client = class {
|
|
|
1397
1509
|
key: this.createOperationKey(),
|
|
1398
1510
|
metadata: {
|
|
1399
1511
|
...options?.metadata,
|
|
1400
|
-
fragmentRef
|
|
1512
|
+
fragment: { ref: fragmentRef }
|
|
1401
1513
|
},
|
|
1402
1514
|
artifact,
|
|
1403
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",
|