@mearie/core 0.4.0 → 0.5.1
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 +155 -31
- package/dist/index.d.cts +15 -8
- package/dist/index.d.mts +15 -8
- package/dist/index.mjs +155 -31
- 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,38 +518,47 @@ const makeFieldKeyFromArgs = (field, args) => {
|
|
|
504
518
|
|
|
505
519
|
//#endregion
|
|
506
520
|
//#region src/cache/normalize.ts
|
|
507
|
-
const
|
|
521
|
+
const resolveTypename = (selections, data) => {
|
|
522
|
+
for (const s of selections) if (s.kind === "Field" && s.name === "__typename") return data[s.alias ?? "__typename"];
|
|
523
|
+
return data.__typename;
|
|
524
|
+
};
|
|
508
525
|
const normalize = (schemaMeta, selections, storage, data, variables, accessor) => {
|
|
526
|
+
const resolveEntityKey = (typename, data) => {
|
|
527
|
+
if (!typename) return null;
|
|
528
|
+
const entityMeta = schemaMeta.entities[typename];
|
|
529
|
+
if (!entityMeta) return null;
|
|
530
|
+
const keys = entityMeta.keyFields.map((field) => data[field]);
|
|
531
|
+
if (keys.every((k) => k !== void 0 && k !== null)) return makeEntityKey(typename, keys);
|
|
532
|
+
return null;
|
|
533
|
+
};
|
|
509
534
|
const normalizeField = (storageKey, selections, value) => {
|
|
510
535
|
if (isNullish(value)) return value;
|
|
511
536
|
if (Array.isArray(value)) return value.map((item) => normalizeField(storageKey, selections, item));
|
|
512
537
|
const data = value;
|
|
513
|
-
const typename = data
|
|
514
|
-
const
|
|
515
|
-
if (
|
|
516
|
-
const keys = entityMeta.keyFields.map((field) => data[field]);
|
|
517
|
-
if (!keys.every((k) => k !== void 0 && k !== null)) return SKIP;
|
|
518
|
-
storageKey = makeEntityKey(typename, keys);
|
|
519
|
-
}
|
|
538
|
+
const typename = resolveTypename(selections, data);
|
|
539
|
+
const entityKey = resolveEntityKey(typename, data);
|
|
540
|
+
if (entityKey) storageKey = entityKey;
|
|
520
541
|
const fields = {};
|
|
521
542
|
for (const selection of selections) if (selection.kind === "Field") {
|
|
522
543
|
const fieldKey = makeFieldKey(selection, variables);
|
|
523
544
|
const fieldValue = data[selection.alias ?? selection.name];
|
|
545
|
+
if (storageKey !== null && selection.selections && typeof fieldValue === "object" && fieldValue !== null && !Array.isArray(fieldValue)) {
|
|
546
|
+
const fieldTypename = resolveTypename(selection.selections, fieldValue);
|
|
547
|
+
if (fieldTypename && schemaMeta.entities[fieldTypename] && !resolveEntityKey(fieldTypename, fieldValue) && isEntityLink(storage[storageKey]?.[fieldKey])) continue;
|
|
548
|
+
}
|
|
524
549
|
const oldValue = storageKey === null ? void 0 : storage[storageKey]?.[fieldKey];
|
|
525
550
|
if (storageKey !== null && (!selection.selections || isNullish(oldValue) || isNullish(fieldValue))) accessor?.(storageKey, fieldKey, oldValue, fieldValue);
|
|
526
|
-
|
|
527
|
-
if (normalized === SKIP) continue;
|
|
528
|
-
fields[fieldKey] = normalized;
|
|
551
|
+
fields[fieldKey] = selection.selections ? normalizeField(null, selection.selections, fieldValue) : fieldValue;
|
|
529
552
|
if (storageKey !== null && selection.selections && !isNullish(oldValue) && !isNullish(fieldValue) && !isEntityLink(fields[fieldKey]) && !isEqual(oldValue, fields[fieldKey])) accessor?.(storageKey, fieldKey, oldValue, fields[fieldKey]);
|
|
530
553
|
} else if (selection.kind === "FragmentSpread" || selection.kind === "InlineFragment" && selection.on === typename) {
|
|
531
554
|
const inner = normalizeField(storageKey, selection.selections, value);
|
|
532
|
-
if (
|
|
555
|
+
if (!isEntityLink(inner)) mergeFields(fields, inner);
|
|
533
556
|
}
|
|
534
|
-
if (
|
|
535
|
-
const existing = storage[
|
|
557
|
+
if (entityKey) {
|
|
558
|
+
const existing = storage[entityKey];
|
|
536
559
|
if (existing) mergeFields(existing, fields);
|
|
537
|
-
else storage[
|
|
538
|
-
return { [EntityLinkKey]:
|
|
560
|
+
else storage[entityKey] = fields;
|
|
561
|
+
return { [EntityLinkKey]: entityKey };
|
|
539
562
|
}
|
|
540
563
|
return fields;
|
|
541
564
|
};
|
|
@@ -584,6 +607,17 @@ const denormalize = (selections, storage, value, variables, accessor) => {
|
|
|
584
607
|
else fields[name] = value;
|
|
585
608
|
} else if (selection.kind === "FragmentSpread") if (storageKey !== null && storageKey !== RootFieldKey) {
|
|
586
609
|
fields[FragmentRefKey] = storageKey;
|
|
610
|
+
if (selection.args) {
|
|
611
|
+
const resolvedArgs = resolveArguments(selection.args, variables);
|
|
612
|
+
const mergedVars = {
|
|
613
|
+
...variables,
|
|
614
|
+
...resolvedArgs
|
|
615
|
+
};
|
|
616
|
+
fields[FragmentVarsKey] = {
|
|
617
|
+
...fields[FragmentVarsKey],
|
|
618
|
+
[selection.name]: mergedVars
|
|
619
|
+
};
|
|
620
|
+
}
|
|
587
621
|
if (accessor) denormalize(selection.selections, storage, { [EntityLinkKey]: storageKey }, variables, accessor);
|
|
588
622
|
} else mergeFields(fields, denormalizeField(storageKey, selection.selections, value));
|
|
589
623
|
else if (selection.kind === "InlineFragment" && selection.on === data[typenameFieldKey]) mergeFields(fields, denormalizeField(storageKey, selection.selections, value));
|
|
@@ -607,9 +641,74 @@ var Cache = class {
|
|
|
607
641
|
#subscriptions = /* @__PURE__ */ new Map();
|
|
608
642
|
#memo = /* @__PURE__ */ new Map();
|
|
609
643
|
#stale = /* @__PURE__ */ new Set();
|
|
644
|
+
#optimisticKeys = [];
|
|
645
|
+
#optimisticLayers = /* @__PURE__ */ new Map();
|
|
646
|
+
#storageView = null;
|
|
610
647
|
constructor(schemaMetadata) {
|
|
611
648
|
this.#schemaMeta = schemaMetadata;
|
|
612
649
|
}
|
|
650
|
+
#getStorageView() {
|
|
651
|
+
if (this.#optimisticKeys.length === 0) return this.#storage;
|
|
652
|
+
if (this.#storageView) return this.#storageView;
|
|
653
|
+
const merged = { ...this.#storage };
|
|
654
|
+
for (const storageKey of Object.keys(this.#storage)) merged[storageKey] = { ...this.#storage[storageKey] };
|
|
655
|
+
for (const key of this.#optimisticKeys) {
|
|
656
|
+
const layer = this.#optimisticLayers.get(key);
|
|
657
|
+
if (!layer) continue;
|
|
658
|
+
for (const storageKey of Object.keys(layer.storage)) merged[storageKey] = merged[storageKey] ? {
|
|
659
|
+
...merged[storageKey],
|
|
660
|
+
...layer.storage[storageKey]
|
|
661
|
+
} : { ...layer.storage[storageKey] };
|
|
662
|
+
}
|
|
663
|
+
this.#storageView = merged;
|
|
664
|
+
return merged;
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Writes an optimistic response to a separate cache layer.
|
|
668
|
+
* The optimistic data is immediately visible in reads but does not affect the base storage.
|
|
669
|
+
* @internal
|
|
670
|
+
* @param key - Unique key identifying this optimistic mutation (typically the operation key).
|
|
671
|
+
* @param artifact - GraphQL document artifact.
|
|
672
|
+
* @param variables - Operation variables.
|
|
673
|
+
* @param data - The optimistic response data.
|
|
674
|
+
*/
|
|
675
|
+
writeOptimistic(key, artifact, variables, data) {
|
|
676
|
+
const layerStorage = { [RootFieldKey]: {} };
|
|
677
|
+
const dependencies = /* @__PURE__ */ new Set();
|
|
678
|
+
normalize(this.#schemaMeta, artifact.selections, layerStorage, data, variables, (storageKey, fieldKey) => {
|
|
679
|
+
dependencies.add(makeDependencyKey(storageKey, fieldKey));
|
|
680
|
+
});
|
|
681
|
+
this.#optimisticKeys.push(key);
|
|
682
|
+
this.#optimisticLayers.set(key, {
|
|
683
|
+
storage: layerStorage,
|
|
684
|
+
dependencies
|
|
685
|
+
});
|
|
686
|
+
this.#storageView = null;
|
|
687
|
+
const subscriptions = /* @__PURE__ */ new Set();
|
|
688
|
+
for (const depKey of dependencies) {
|
|
689
|
+
const ss = this.#subscriptions.get(depKey);
|
|
690
|
+
if (ss) for (const s of ss) subscriptions.add(s);
|
|
691
|
+
}
|
|
692
|
+
for (const subscription of subscriptions) subscription.listener();
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Removes an optimistic layer and notifies affected subscribers.
|
|
696
|
+
* @internal
|
|
697
|
+
* @param key - The key of the optimistic layer to remove.
|
|
698
|
+
*/
|
|
699
|
+
removeOptimistic(key) {
|
|
700
|
+
const layer = this.#optimisticLayers.get(key);
|
|
701
|
+
if (!layer) return;
|
|
702
|
+
this.#optimisticLayers.delete(key);
|
|
703
|
+
this.#optimisticKeys = this.#optimisticKeys.filter((k) => k !== key);
|
|
704
|
+
this.#storageView = null;
|
|
705
|
+
const subscriptions = /* @__PURE__ */ new Set();
|
|
706
|
+
for (const depKey of layer.dependencies) {
|
|
707
|
+
const ss = this.#subscriptions.get(depKey);
|
|
708
|
+
if (ss) for (const s of ss) subscriptions.add(s);
|
|
709
|
+
}
|
|
710
|
+
for (const subscription of subscriptions) subscription.listener();
|
|
711
|
+
}
|
|
613
712
|
/**
|
|
614
713
|
* Writes a query result to the cache, normalizing entities.
|
|
615
714
|
* @param artifact - GraphQL document artifact.
|
|
@@ -642,7 +741,8 @@ var Cache = class {
|
|
|
642
741
|
*/
|
|
643
742
|
readQuery(artifact, variables) {
|
|
644
743
|
let stale = false;
|
|
645
|
-
const
|
|
744
|
+
const storage = this.#getStorageView();
|
|
745
|
+
const { data, partial } = denormalize(artifact.selections, storage, storage[RootFieldKey], variables, (storageKey, fieldKey) => {
|
|
646
746
|
if (this.#stale.has(storageKey) || this.#stale.has(makeDependencyKey(storageKey, fieldKey))) stale = true;
|
|
647
747
|
});
|
|
648
748
|
if (partial) return {
|
|
@@ -667,7 +767,8 @@ var Cache = class {
|
|
|
667
767
|
*/
|
|
668
768
|
subscribeQuery(artifact, variables, listener) {
|
|
669
769
|
const dependencies = /* @__PURE__ */ new Set();
|
|
670
|
-
|
|
770
|
+
const storageView = this.#getStorageView();
|
|
771
|
+
denormalize(artifact.selections, storageView, storageView[RootFieldKey], variables, (storageKey, fieldKey) => {
|
|
671
772
|
const dependencyKey = makeDependencyKey(storageKey, fieldKey);
|
|
672
773
|
dependencies.add(dependencyKey);
|
|
673
774
|
});
|
|
@@ -682,19 +783,22 @@ var Cache = class {
|
|
|
682
783
|
*/
|
|
683
784
|
readFragment(artifact, fragmentRef) {
|
|
684
785
|
const entityKey = fragmentRef[FragmentRefKey];
|
|
685
|
-
|
|
786
|
+
const fragmentVars = getFragmentVars(fragmentRef, artifact.name);
|
|
787
|
+
const storageView = this.#getStorageView();
|
|
788
|
+
if (!storageView[entityKey]) return {
|
|
686
789
|
data: null,
|
|
687
790
|
stale: false
|
|
688
791
|
};
|
|
689
792
|
let stale = false;
|
|
690
|
-
const { data, partial } = denormalize(artifact.selections,
|
|
793
|
+
const { data, partial } = denormalize(artifact.selections, storageView, { [EntityLinkKey]: entityKey }, fragmentVars, (storageKey, fieldKey) => {
|
|
691
794
|
if (this.#stale.has(storageKey) || this.#stale.has(makeDependencyKey(storageKey, fieldKey))) stale = true;
|
|
692
795
|
});
|
|
693
796
|
if (partial) return {
|
|
694
797
|
data: null,
|
|
695
798
|
stale: false
|
|
696
799
|
};
|
|
697
|
-
const
|
|
800
|
+
const argsId = Object.keys(fragmentVars).length > 0 ? entityKey + stringify(fragmentVars) : entityKey;
|
|
801
|
+
const key = makeMemoKey("fragment", artifact.name, argsId);
|
|
698
802
|
const prev = this.#memo.get(key);
|
|
699
803
|
const result = prev === void 0 ? data : replaceEqualDeep(prev, data);
|
|
700
804
|
this.#memo.set(key, result);
|
|
@@ -705,8 +809,10 @@ var Cache = class {
|
|
|
705
809
|
}
|
|
706
810
|
subscribeFragment(artifact, fragmentRef, listener) {
|
|
707
811
|
const entityKey = fragmentRef[FragmentRefKey];
|
|
812
|
+
const fragmentVars = getFragmentVars(fragmentRef, artifact.name);
|
|
708
813
|
const dependencies = /* @__PURE__ */ new Set();
|
|
709
|
-
|
|
814
|
+
const storageView = this.#getStorageView();
|
|
815
|
+
denormalize(artifact.selections, storageView, { [EntityLinkKey]: entityKey }, fragmentVars, (storageKey, fieldKey) => {
|
|
710
816
|
const dependencyKey = makeDependencyKey(storageKey, fieldKey);
|
|
711
817
|
dependencies.add(dependencyKey);
|
|
712
818
|
});
|
|
@@ -736,9 +842,11 @@ var Cache = class {
|
|
|
736
842
|
}
|
|
737
843
|
subscribeFragments(artifact, fragmentRefs, listener) {
|
|
738
844
|
const dependencies = /* @__PURE__ */ new Set();
|
|
845
|
+
const storageView = this.#getStorageView();
|
|
739
846
|
for (const ref of fragmentRefs) {
|
|
740
847
|
const entityKey = ref[FragmentRefKey];
|
|
741
|
-
|
|
848
|
+
const fragmentVars = getFragmentVars(ref, artifact.name);
|
|
849
|
+
denormalize(artifact.selections, storageView, { [EntityLinkKey]: entityKey }, fragmentVars, (storageKey, fieldKey) => {
|
|
742
850
|
dependencies.add(makeDependencyKey(storageKey, fieldKey));
|
|
743
851
|
});
|
|
744
852
|
}
|
|
@@ -819,6 +927,7 @@ var Cache = class {
|
|
|
819
927
|
}
|
|
820
928
|
/**
|
|
821
929
|
* Extracts a serializable snapshot of the cache storage and structural sharing state.
|
|
930
|
+
* Optimistic layers are excluded because they represent transient in-flight state.
|
|
822
931
|
*/
|
|
823
932
|
extract() {
|
|
824
933
|
return {
|
|
@@ -836,6 +945,7 @@ var Cache = class {
|
|
|
836
945
|
...fields
|
|
837
946
|
};
|
|
838
947
|
for (const [key, value] of Object.entries(memo)) this.#memo.set(key, value);
|
|
948
|
+
this.#storageView = null;
|
|
839
949
|
}
|
|
840
950
|
/**
|
|
841
951
|
* Clears all cache data.
|
|
@@ -845,6 +955,9 @@ var Cache = class {
|
|
|
845
955
|
this.#subscriptions.clear();
|
|
846
956
|
this.#memo.clear();
|
|
847
957
|
this.#stale.clear();
|
|
958
|
+
this.#optimisticKeys = [];
|
|
959
|
+
this.#optimisticLayers.clear();
|
|
960
|
+
this.#storageView = null;
|
|
848
961
|
}
|
|
849
962
|
};
|
|
850
963
|
|
|
@@ -877,10 +990,10 @@ const cacheExchange = (options = {}) => {
|
|
|
877
990
|
},
|
|
878
991
|
io: (ops$) => {
|
|
879
992
|
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?.
|
|
993
|
+
const fragmentRef = op.metadata?.fragment?.ref;
|
|
881
994
|
if (!fragmentRef) return require_make.fromValue({
|
|
882
995
|
operation: op,
|
|
883
|
-
errors: [new ExchangeError("Fragment operation missing
|
|
996
|
+
errors: [new ExchangeError("Fragment operation missing fragment.ref in metadata. This usually happens when the wrong fragment reference was passed.", { exchangeName: "cache" })]
|
|
884
997
|
});
|
|
885
998
|
if (isFragmentRefArray(fragmentRef)) {
|
|
886
999
|
const trigger = require_make.makeSubject();
|
|
@@ -912,7 +1025,9 @@ const cacheExchange = (options = {}) => {
|
|
|
912
1025
|
errors: []
|
|
913
1026
|
})));
|
|
914
1027
|
}));
|
|
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")))
|
|
1028
|
+
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) => {
|
|
1029
|
+
if (op.artifact.kind === "mutation" && op.metadata?.cache?.optimisticResponse) cache.writeOptimistic(op.key, op.artifact, op.variables, op.metadata.cache.optimisticResponse);
|
|
1030
|
+
}));
|
|
916
1031
|
const query$ = require_make.pipe(ops$, require_make.filter((op) => op.variant === "request" && op.artifact.kind === "query" && fetchPolicy !== "network-only"), require_make.share());
|
|
917
1032
|
const refetch$ = require_make.makeSubject();
|
|
918
1033
|
return require_make.merge(fragment$, require_make.pipe(query$, require_make.mergeMap((op) => {
|
|
@@ -955,9 +1070,18 @@ const cacheExchange = (options = {}) => {
|
|
|
955
1070
|
}), 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
1071
|
const { data } = cache.readQuery(op.artifact, op.variables);
|
|
957
1072
|
return fetchPolicy === "cache-and-network" || data === null;
|
|
958
|
-
})), require_make.pipe(ops$, require_make.filter((op) => op.variant === "teardown")), refetch$.source), forward, require_make.
|
|
1073
|
+
})), require_make.pipe(ops$, require_make.filter((op) => op.variant === "teardown")), refetch$.source), forward, require_make.mergeMap((result) => {
|
|
1074
|
+
if (result.operation.variant === "request" && result.operation.artifact.kind === "mutation" && result.operation.metadata?.cache?.optimisticResponse) cache.removeOptimistic(result.operation.key);
|
|
959
1075
|
if (result.operation.variant === "request" && result.data) cache.writeQuery(result.operation.artifact, result.operation.variables, result.data);
|
|
960
|
-
|
|
1076
|
+
if (result.operation.variant !== "request" || result.operation.artifact.kind !== "query" || fetchPolicy === "network-only" || !!(result.errors && result.errors.length > 0)) return require_make.fromValue(result);
|
|
1077
|
+
const { data } = cache.readQuery(result.operation.artifact, result.operation.variables);
|
|
1078
|
+
if (data !== null) return empty();
|
|
1079
|
+
return require_make.fromValue({
|
|
1080
|
+
operation: result.operation,
|
|
1081
|
+
data: void 0,
|
|
1082
|
+
errors: [new ExchangeError("Cache failed to denormalize the network response. This is likely a bug in the cache normalizer.", { exchangeName: "cache" })]
|
|
1083
|
+
});
|
|
1084
|
+
})));
|
|
961
1085
|
}
|
|
962
1086
|
};
|
|
963
1087
|
};
|
|
@@ -1009,10 +1133,10 @@ const fragmentExchange = () => {
|
|
|
1009
1133
|
name: "fragment",
|
|
1010
1134
|
io: (ops$) => {
|
|
1011
1135
|
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.
|
|
1136
|
+
const fragmentRef = op.metadata.fragment?.ref;
|
|
1013
1137
|
if (!fragmentRef) return {
|
|
1014
1138
|
operation: op,
|
|
1015
|
-
errors: [new ExchangeError("Fragment operation missing
|
|
1139
|
+
errors: [new ExchangeError("Fragment operation missing fragment.ref in metadata. This usually happens when the wrong fragment reference was passed.", { exchangeName: "fragment" })]
|
|
1016
1140
|
};
|
|
1017
1141
|
return {
|
|
1018
1142
|
operation: op,
|
|
@@ -1398,7 +1522,7 @@ var Client = class {
|
|
|
1398
1522
|
key: this.createOperationKey(),
|
|
1399
1523
|
metadata: {
|
|
1400
1524
|
...options?.metadata,
|
|
1401
|
-
fragmentRef
|
|
1525
|
+
fragment: { ref: fragmentRef }
|
|
1402
1526
|
},
|
|
1403
1527
|
artifact,
|
|
1404
1528
|
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,38 +517,47 @@ const makeFieldKeyFromArgs = (field, args) => {
|
|
|
503
517
|
|
|
504
518
|
//#endregion
|
|
505
519
|
//#region src/cache/normalize.ts
|
|
506
|
-
const
|
|
520
|
+
const resolveTypename = (selections, data) => {
|
|
521
|
+
for (const s of selections) if (s.kind === "Field" && s.name === "__typename") return data[s.alias ?? "__typename"];
|
|
522
|
+
return data.__typename;
|
|
523
|
+
};
|
|
507
524
|
const normalize = (schemaMeta, selections, storage, data, variables, accessor) => {
|
|
525
|
+
const resolveEntityKey = (typename, data) => {
|
|
526
|
+
if (!typename) return null;
|
|
527
|
+
const entityMeta = schemaMeta.entities[typename];
|
|
528
|
+
if (!entityMeta) return null;
|
|
529
|
+
const keys = entityMeta.keyFields.map((field) => data[field]);
|
|
530
|
+
if (keys.every((k) => k !== void 0 && k !== null)) return makeEntityKey(typename, keys);
|
|
531
|
+
return null;
|
|
532
|
+
};
|
|
508
533
|
const normalizeField = (storageKey, selections, value) => {
|
|
509
534
|
if (isNullish(value)) return value;
|
|
510
535
|
if (Array.isArray(value)) return value.map((item) => normalizeField(storageKey, selections, item));
|
|
511
536
|
const data = value;
|
|
512
|
-
const typename = data
|
|
513
|
-
const
|
|
514
|
-
if (
|
|
515
|
-
const keys = entityMeta.keyFields.map((field) => data[field]);
|
|
516
|
-
if (!keys.every((k) => k !== void 0 && k !== null)) return SKIP;
|
|
517
|
-
storageKey = makeEntityKey(typename, keys);
|
|
518
|
-
}
|
|
537
|
+
const typename = resolveTypename(selections, data);
|
|
538
|
+
const entityKey = resolveEntityKey(typename, data);
|
|
539
|
+
if (entityKey) storageKey = entityKey;
|
|
519
540
|
const fields = {};
|
|
520
541
|
for (const selection of selections) if (selection.kind === "Field") {
|
|
521
542
|
const fieldKey = makeFieldKey(selection, variables);
|
|
522
543
|
const fieldValue = data[selection.alias ?? selection.name];
|
|
544
|
+
if (storageKey !== null && selection.selections && typeof fieldValue === "object" && fieldValue !== null && !Array.isArray(fieldValue)) {
|
|
545
|
+
const fieldTypename = resolveTypename(selection.selections, fieldValue);
|
|
546
|
+
if (fieldTypename && schemaMeta.entities[fieldTypename] && !resolveEntityKey(fieldTypename, fieldValue) && isEntityLink(storage[storageKey]?.[fieldKey])) continue;
|
|
547
|
+
}
|
|
523
548
|
const oldValue = storageKey === null ? void 0 : storage[storageKey]?.[fieldKey];
|
|
524
549
|
if (storageKey !== null && (!selection.selections || isNullish(oldValue) || isNullish(fieldValue))) accessor?.(storageKey, fieldKey, oldValue, fieldValue);
|
|
525
|
-
|
|
526
|
-
if (normalized === SKIP) continue;
|
|
527
|
-
fields[fieldKey] = normalized;
|
|
550
|
+
fields[fieldKey] = selection.selections ? normalizeField(null, selection.selections, fieldValue) : fieldValue;
|
|
528
551
|
if (storageKey !== null && selection.selections && !isNullish(oldValue) && !isNullish(fieldValue) && !isEntityLink(fields[fieldKey]) && !isEqual(oldValue, fields[fieldKey])) accessor?.(storageKey, fieldKey, oldValue, fields[fieldKey]);
|
|
529
552
|
} else if (selection.kind === "FragmentSpread" || selection.kind === "InlineFragment" && selection.on === typename) {
|
|
530
553
|
const inner = normalizeField(storageKey, selection.selections, value);
|
|
531
|
-
if (
|
|
554
|
+
if (!isEntityLink(inner)) mergeFields(fields, inner);
|
|
532
555
|
}
|
|
533
|
-
if (
|
|
534
|
-
const existing = storage[
|
|
556
|
+
if (entityKey) {
|
|
557
|
+
const existing = storage[entityKey];
|
|
535
558
|
if (existing) mergeFields(existing, fields);
|
|
536
|
-
else storage[
|
|
537
|
-
return { [EntityLinkKey]:
|
|
559
|
+
else storage[entityKey] = fields;
|
|
560
|
+
return { [EntityLinkKey]: entityKey };
|
|
538
561
|
}
|
|
539
562
|
return fields;
|
|
540
563
|
};
|
|
@@ -583,6 +606,17 @@ const denormalize = (selections, storage, value, variables, accessor) => {
|
|
|
583
606
|
else fields[name] = value;
|
|
584
607
|
} else if (selection.kind === "FragmentSpread") if (storageKey !== null && storageKey !== RootFieldKey) {
|
|
585
608
|
fields[FragmentRefKey] = storageKey;
|
|
609
|
+
if (selection.args) {
|
|
610
|
+
const resolvedArgs = resolveArguments(selection.args, variables);
|
|
611
|
+
const mergedVars = {
|
|
612
|
+
...variables,
|
|
613
|
+
...resolvedArgs
|
|
614
|
+
};
|
|
615
|
+
fields[FragmentVarsKey] = {
|
|
616
|
+
...fields[FragmentVarsKey],
|
|
617
|
+
[selection.name]: mergedVars
|
|
618
|
+
};
|
|
619
|
+
}
|
|
586
620
|
if (accessor) denormalize(selection.selections, storage, { [EntityLinkKey]: storageKey }, variables, accessor);
|
|
587
621
|
} else mergeFields(fields, denormalizeField(storageKey, selection.selections, value));
|
|
588
622
|
else if (selection.kind === "InlineFragment" && selection.on === data[typenameFieldKey]) mergeFields(fields, denormalizeField(storageKey, selection.selections, value));
|
|
@@ -606,9 +640,74 @@ var Cache = class {
|
|
|
606
640
|
#subscriptions = /* @__PURE__ */ new Map();
|
|
607
641
|
#memo = /* @__PURE__ */ new Map();
|
|
608
642
|
#stale = /* @__PURE__ */ new Set();
|
|
643
|
+
#optimisticKeys = [];
|
|
644
|
+
#optimisticLayers = /* @__PURE__ */ new Map();
|
|
645
|
+
#storageView = null;
|
|
609
646
|
constructor(schemaMetadata) {
|
|
610
647
|
this.#schemaMeta = schemaMetadata;
|
|
611
648
|
}
|
|
649
|
+
#getStorageView() {
|
|
650
|
+
if (this.#optimisticKeys.length === 0) return this.#storage;
|
|
651
|
+
if (this.#storageView) return this.#storageView;
|
|
652
|
+
const merged = { ...this.#storage };
|
|
653
|
+
for (const storageKey of Object.keys(this.#storage)) merged[storageKey] = { ...this.#storage[storageKey] };
|
|
654
|
+
for (const key of this.#optimisticKeys) {
|
|
655
|
+
const layer = this.#optimisticLayers.get(key);
|
|
656
|
+
if (!layer) continue;
|
|
657
|
+
for (const storageKey of Object.keys(layer.storage)) merged[storageKey] = merged[storageKey] ? {
|
|
658
|
+
...merged[storageKey],
|
|
659
|
+
...layer.storage[storageKey]
|
|
660
|
+
} : { ...layer.storage[storageKey] };
|
|
661
|
+
}
|
|
662
|
+
this.#storageView = merged;
|
|
663
|
+
return merged;
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Writes an optimistic response to a separate cache layer.
|
|
667
|
+
* The optimistic data is immediately visible in reads but does not affect the base storage.
|
|
668
|
+
* @internal
|
|
669
|
+
* @param key - Unique key identifying this optimistic mutation (typically the operation key).
|
|
670
|
+
* @param artifact - GraphQL document artifact.
|
|
671
|
+
* @param variables - Operation variables.
|
|
672
|
+
* @param data - The optimistic response data.
|
|
673
|
+
*/
|
|
674
|
+
writeOptimistic(key, artifact, variables, data) {
|
|
675
|
+
const layerStorage = { [RootFieldKey]: {} };
|
|
676
|
+
const dependencies = /* @__PURE__ */ new Set();
|
|
677
|
+
normalize(this.#schemaMeta, artifact.selections, layerStorage, data, variables, (storageKey, fieldKey) => {
|
|
678
|
+
dependencies.add(makeDependencyKey(storageKey, fieldKey));
|
|
679
|
+
});
|
|
680
|
+
this.#optimisticKeys.push(key);
|
|
681
|
+
this.#optimisticLayers.set(key, {
|
|
682
|
+
storage: layerStorage,
|
|
683
|
+
dependencies
|
|
684
|
+
});
|
|
685
|
+
this.#storageView = null;
|
|
686
|
+
const subscriptions = /* @__PURE__ */ new Set();
|
|
687
|
+
for (const depKey of dependencies) {
|
|
688
|
+
const ss = this.#subscriptions.get(depKey);
|
|
689
|
+
if (ss) for (const s of ss) subscriptions.add(s);
|
|
690
|
+
}
|
|
691
|
+
for (const subscription of subscriptions) subscription.listener();
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Removes an optimistic layer and notifies affected subscribers.
|
|
695
|
+
* @internal
|
|
696
|
+
* @param key - The key of the optimistic layer to remove.
|
|
697
|
+
*/
|
|
698
|
+
removeOptimistic(key) {
|
|
699
|
+
const layer = this.#optimisticLayers.get(key);
|
|
700
|
+
if (!layer) return;
|
|
701
|
+
this.#optimisticLayers.delete(key);
|
|
702
|
+
this.#optimisticKeys = this.#optimisticKeys.filter((k) => k !== key);
|
|
703
|
+
this.#storageView = null;
|
|
704
|
+
const subscriptions = /* @__PURE__ */ new Set();
|
|
705
|
+
for (const depKey of layer.dependencies) {
|
|
706
|
+
const ss = this.#subscriptions.get(depKey);
|
|
707
|
+
if (ss) for (const s of ss) subscriptions.add(s);
|
|
708
|
+
}
|
|
709
|
+
for (const subscription of subscriptions) subscription.listener();
|
|
710
|
+
}
|
|
612
711
|
/**
|
|
613
712
|
* Writes a query result to the cache, normalizing entities.
|
|
614
713
|
* @param artifact - GraphQL document artifact.
|
|
@@ -641,7 +740,8 @@ var Cache = class {
|
|
|
641
740
|
*/
|
|
642
741
|
readQuery(artifact, variables) {
|
|
643
742
|
let stale = false;
|
|
644
|
-
const
|
|
743
|
+
const storage = this.#getStorageView();
|
|
744
|
+
const { data, partial } = denormalize(artifact.selections, storage, storage[RootFieldKey], variables, (storageKey, fieldKey) => {
|
|
645
745
|
if (this.#stale.has(storageKey) || this.#stale.has(makeDependencyKey(storageKey, fieldKey))) stale = true;
|
|
646
746
|
});
|
|
647
747
|
if (partial) return {
|
|
@@ -666,7 +766,8 @@ var Cache = class {
|
|
|
666
766
|
*/
|
|
667
767
|
subscribeQuery(artifact, variables, listener) {
|
|
668
768
|
const dependencies = /* @__PURE__ */ new Set();
|
|
669
|
-
|
|
769
|
+
const storageView = this.#getStorageView();
|
|
770
|
+
denormalize(artifact.selections, storageView, storageView[RootFieldKey], variables, (storageKey, fieldKey) => {
|
|
670
771
|
const dependencyKey = makeDependencyKey(storageKey, fieldKey);
|
|
671
772
|
dependencies.add(dependencyKey);
|
|
672
773
|
});
|
|
@@ -681,19 +782,22 @@ var Cache = class {
|
|
|
681
782
|
*/
|
|
682
783
|
readFragment(artifact, fragmentRef) {
|
|
683
784
|
const entityKey = fragmentRef[FragmentRefKey];
|
|
684
|
-
|
|
785
|
+
const fragmentVars = getFragmentVars(fragmentRef, artifact.name);
|
|
786
|
+
const storageView = this.#getStorageView();
|
|
787
|
+
if (!storageView[entityKey]) return {
|
|
685
788
|
data: null,
|
|
686
789
|
stale: false
|
|
687
790
|
};
|
|
688
791
|
let stale = false;
|
|
689
|
-
const { data, partial } = denormalize(artifact.selections,
|
|
792
|
+
const { data, partial } = denormalize(artifact.selections, storageView, { [EntityLinkKey]: entityKey }, fragmentVars, (storageKey, fieldKey) => {
|
|
690
793
|
if (this.#stale.has(storageKey) || this.#stale.has(makeDependencyKey(storageKey, fieldKey))) stale = true;
|
|
691
794
|
});
|
|
692
795
|
if (partial) return {
|
|
693
796
|
data: null,
|
|
694
797
|
stale: false
|
|
695
798
|
};
|
|
696
|
-
const
|
|
799
|
+
const argsId = Object.keys(fragmentVars).length > 0 ? entityKey + stringify(fragmentVars) : entityKey;
|
|
800
|
+
const key = makeMemoKey("fragment", artifact.name, argsId);
|
|
697
801
|
const prev = this.#memo.get(key);
|
|
698
802
|
const result = prev === void 0 ? data : replaceEqualDeep(prev, data);
|
|
699
803
|
this.#memo.set(key, result);
|
|
@@ -704,8 +808,10 @@ var Cache = class {
|
|
|
704
808
|
}
|
|
705
809
|
subscribeFragment(artifact, fragmentRef, listener) {
|
|
706
810
|
const entityKey = fragmentRef[FragmentRefKey];
|
|
811
|
+
const fragmentVars = getFragmentVars(fragmentRef, artifact.name);
|
|
707
812
|
const dependencies = /* @__PURE__ */ new Set();
|
|
708
|
-
|
|
813
|
+
const storageView = this.#getStorageView();
|
|
814
|
+
denormalize(artifact.selections, storageView, { [EntityLinkKey]: entityKey }, fragmentVars, (storageKey, fieldKey) => {
|
|
709
815
|
const dependencyKey = makeDependencyKey(storageKey, fieldKey);
|
|
710
816
|
dependencies.add(dependencyKey);
|
|
711
817
|
});
|
|
@@ -735,9 +841,11 @@ var Cache = class {
|
|
|
735
841
|
}
|
|
736
842
|
subscribeFragments(artifact, fragmentRefs, listener) {
|
|
737
843
|
const dependencies = /* @__PURE__ */ new Set();
|
|
844
|
+
const storageView = this.#getStorageView();
|
|
738
845
|
for (const ref of fragmentRefs) {
|
|
739
846
|
const entityKey = ref[FragmentRefKey];
|
|
740
|
-
|
|
847
|
+
const fragmentVars = getFragmentVars(ref, artifact.name);
|
|
848
|
+
denormalize(artifact.selections, storageView, { [EntityLinkKey]: entityKey }, fragmentVars, (storageKey, fieldKey) => {
|
|
741
849
|
dependencies.add(makeDependencyKey(storageKey, fieldKey));
|
|
742
850
|
});
|
|
743
851
|
}
|
|
@@ -818,6 +926,7 @@ var Cache = class {
|
|
|
818
926
|
}
|
|
819
927
|
/**
|
|
820
928
|
* Extracts a serializable snapshot of the cache storage and structural sharing state.
|
|
929
|
+
* Optimistic layers are excluded because they represent transient in-flight state.
|
|
821
930
|
*/
|
|
822
931
|
extract() {
|
|
823
932
|
return {
|
|
@@ -835,6 +944,7 @@ var Cache = class {
|
|
|
835
944
|
...fields
|
|
836
945
|
};
|
|
837
946
|
for (const [key, value] of Object.entries(memo)) this.#memo.set(key, value);
|
|
947
|
+
this.#storageView = null;
|
|
838
948
|
}
|
|
839
949
|
/**
|
|
840
950
|
* Clears all cache data.
|
|
@@ -844,6 +954,9 @@ var Cache = class {
|
|
|
844
954
|
this.#subscriptions.clear();
|
|
845
955
|
this.#memo.clear();
|
|
846
956
|
this.#stale.clear();
|
|
957
|
+
this.#optimisticKeys = [];
|
|
958
|
+
this.#optimisticLayers.clear();
|
|
959
|
+
this.#storageView = null;
|
|
847
960
|
}
|
|
848
961
|
};
|
|
849
962
|
|
|
@@ -876,10 +989,10 @@ const cacheExchange = (options = {}) => {
|
|
|
876
989
|
},
|
|
877
990
|
io: (ops$) => {
|
|
878
991
|
const fragment$ = pipe(ops$, filter((op) => op.variant === "request" && op.artifact.kind === "fragment"), mergeMap((op) => {
|
|
879
|
-
const fragmentRef = op.metadata?.
|
|
992
|
+
const fragmentRef = op.metadata?.fragment?.ref;
|
|
880
993
|
if (!fragmentRef) return fromValue({
|
|
881
994
|
operation: op,
|
|
882
|
-
errors: [new ExchangeError("Fragment operation missing
|
|
995
|
+
errors: [new ExchangeError("Fragment operation missing fragment.ref in metadata. This usually happens when the wrong fragment reference was passed.", { exchangeName: "cache" })]
|
|
883
996
|
});
|
|
884
997
|
if (isFragmentRefArray(fragmentRef)) {
|
|
885
998
|
const trigger = makeSubject();
|
|
@@ -911,7 +1024,9 @@ const cacheExchange = (options = {}) => {
|
|
|
911
1024
|
errors: []
|
|
912
1025
|
})));
|
|
913
1026
|
}));
|
|
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")))
|
|
1027
|
+
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) => {
|
|
1028
|
+
if (op.artifact.kind === "mutation" && op.metadata?.cache?.optimisticResponse) cache.writeOptimistic(op.key, op.artifact, op.variables, op.metadata.cache.optimisticResponse);
|
|
1029
|
+
}));
|
|
915
1030
|
const query$ = pipe(ops$, filter((op) => op.variant === "request" && op.artifact.kind === "query" && fetchPolicy !== "network-only"), share());
|
|
916
1031
|
const refetch$ = makeSubject();
|
|
917
1032
|
return merge(fragment$, pipe(query$, mergeMap((op) => {
|
|
@@ -954,9 +1069,18 @@ const cacheExchange = (options = {}) => {
|
|
|
954
1069
|
}), filter(() => fetchPolicy === "cache-only" || fetchPolicy === "cache-and-network" || fetchPolicy === "cache-first")), pipe(merge(nonCache$, pipe(query$, filter((op) => {
|
|
955
1070
|
const { data } = cache.readQuery(op.artifact, op.variables);
|
|
956
1071
|
return fetchPolicy === "cache-and-network" || data === null;
|
|
957
|
-
})), pipe(ops$, filter((op) => op.variant === "teardown")), refetch$.source), forward,
|
|
1072
|
+
})), pipe(ops$, filter((op) => op.variant === "teardown")), refetch$.source), forward, mergeMap((result) => {
|
|
1073
|
+
if (result.operation.variant === "request" && result.operation.artifact.kind === "mutation" && result.operation.metadata?.cache?.optimisticResponse) cache.removeOptimistic(result.operation.key);
|
|
958
1074
|
if (result.operation.variant === "request" && result.data) cache.writeQuery(result.operation.artifact, result.operation.variables, result.data);
|
|
959
|
-
|
|
1075
|
+
if (result.operation.variant !== "request" || result.operation.artifact.kind !== "query" || fetchPolicy === "network-only" || !!(result.errors && result.errors.length > 0)) return fromValue(result);
|
|
1076
|
+
const { data } = cache.readQuery(result.operation.artifact, result.operation.variables);
|
|
1077
|
+
if (data !== null) return empty();
|
|
1078
|
+
return fromValue({
|
|
1079
|
+
operation: result.operation,
|
|
1080
|
+
data: void 0,
|
|
1081
|
+
errors: [new ExchangeError("Cache failed to denormalize the network response. This is likely a bug in the cache normalizer.", { exchangeName: "cache" })]
|
|
1082
|
+
});
|
|
1083
|
+
})));
|
|
960
1084
|
}
|
|
961
1085
|
};
|
|
962
1086
|
};
|
|
@@ -1008,10 +1132,10 @@ const fragmentExchange = () => {
|
|
|
1008
1132
|
name: "fragment",
|
|
1009
1133
|
io: (ops$) => {
|
|
1010
1134
|
return merge(pipe(ops$, filter((op) => op.variant === "request" && op.artifact.kind === "fragment"), map((op) => {
|
|
1011
|
-
const fragmentRef = op.metadata.
|
|
1135
|
+
const fragmentRef = op.metadata.fragment?.ref;
|
|
1012
1136
|
if (!fragmentRef) return {
|
|
1013
1137
|
operation: op,
|
|
1014
|
-
errors: [new ExchangeError("Fragment operation missing
|
|
1138
|
+
errors: [new ExchangeError("Fragment operation missing fragment.ref in metadata. This usually happens when the wrong fragment reference was passed.", { exchangeName: "fragment" })]
|
|
1015
1139
|
};
|
|
1016
1140
|
return {
|
|
1017
1141
|
operation: op,
|
|
@@ -1397,7 +1521,7 @@ var Client = class {
|
|
|
1397
1521
|
key: this.createOperationKey(),
|
|
1398
1522
|
metadata: {
|
|
1399
1523
|
...options?.metadata,
|
|
1400
|
-
fragmentRef
|
|
1524
|
+
fragment: { ref: fragmentRef }
|
|
1401
1525
|
},
|
|
1402
1526
|
artifact,
|
|
1403
1527
|
variables: {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mearie/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
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",
|