@mearie/core 0.2.3 → 0.3.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 CHANGED
@@ -594,8 +594,10 @@ const denormalize = (selections, storage, value, variables, accessor) => {
594
594
  const value = selection.selections ? denormalizeField(null, selection.selections, fieldValue) : fieldValue;
595
595
  if (name in fields) mergeFields(fields, { [name]: value });
596
596
  else fields[name] = value;
597
- } else if (selection.kind === "FragmentSpread") if (storageKey !== null && storageKey !== RootFieldKey) fields[FragmentRefKey] = storageKey;
598
- else mergeFields(fields, denormalizeField(storageKey, selection.selections, value));
597
+ } else if (selection.kind === "FragmentSpread") if (storageKey !== null && storageKey !== RootFieldKey) {
598
+ fields[FragmentRefKey] = storageKey;
599
+ if (accessor) denormalize(selection.selections, storage, { [EntityLinkKey]: storageKey }, variables, accessor);
600
+ } else mergeFields(fields, denormalizeField(storageKey, selection.selections, value));
599
601
  else if (selection.kind === "InlineFragment" && selection.on === data[typenameFieldKey]) mergeFields(fields, denormalizeField(storageKey, selection.selections, value));
600
602
  return fields;
601
603
  };
@@ -616,6 +618,7 @@ var Cache = class {
616
618
  #storage = { [RootFieldKey]: {} };
617
619
  #subscriptions = /* @__PURE__ */ new Map();
618
620
  #memo = /* @__PURE__ */ new Map();
621
+ #stale = /* @__PURE__ */ new Set();
619
622
  constructor(schemaMetadata) {
620
623
  this.#schemaMeta = schemaMetadata;
621
624
  }
@@ -628,12 +631,14 @@ var Cache = class {
628
631
  writeQuery(artifact, variables, data) {
629
632
  const dependencies = /* @__PURE__ */ new Set();
630
633
  const subscriptions = /* @__PURE__ */ new Set();
634
+ const entityStaleCleared = /* @__PURE__ */ new Set();
631
635
  normalize(this.#schemaMeta, artifact.selections, this.#storage, data, variables, (storageKey, fieldKey, oldValue, newValue) => {
632
- if (oldValue !== newValue) {
633
- const dependencyKey = makeDependencyKey(storageKey, fieldKey);
634
- dependencies.add(dependencyKey);
635
- }
636
+ const depKey = makeDependencyKey(storageKey, fieldKey);
637
+ if (this.#stale.delete(depKey)) dependencies.add(depKey);
638
+ if (!entityStaleCleared.has(storageKey) && this.#stale.delete(storageKey)) entityStaleCleared.add(storageKey);
639
+ if (oldValue !== newValue) dependencies.add(depKey);
636
640
  });
641
+ for (const entityKey of entityStaleCleared) this.#collectSubscriptions(entityKey, void 0, subscriptions);
637
642
  for (const dependency of dependencies) {
638
643
  const ss = this.#subscriptions.get(dependency);
639
644
  if (ss) for (const s of ss) subscriptions.add(s);
@@ -648,13 +653,22 @@ var Cache = class {
648
653
  * @returns Denormalized query result or null if not found.
649
654
  */
650
655
  readQuery(artifact, variables) {
651
- const { data, partial } = denormalize(artifact.selections, this.#storage, this.#storage[RootFieldKey], variables);
652
- if (partial) return null;
656
+ let stale = false;
657
+ const { data, partial } = denormalize(artifact.selections, this.#storage, this.#storage[RootFieldKey], variables, (storageKey, fieldKey) => {
658
+ if (this.#stale.has(storageKey) || this.#stale.has(makeDependencyKey(storageKey, fieldKey))) stale = true;
659
+ });
660
+ if (partial) return {
661
+ data: null,
662
+ stale: false
663
+ };
653
664
  const key = makeMemoKey("query", artifact.name, stringify(variables));
654
665
  const prev = this.#memo.get(key);
655
666
  const result = prev === void 0 ? data : replaceEqualDeep(prev, data);
656
667
  this.#memo.set(key, result);
657
- return result;
668
+ return {
669
+ data: result,
670
+ stale
671
+ };
658
672
  }
659
673
  /**
660
674
  * Subscribes to cache invalidations for a specific query.
@@ -680,14 +694,26 @@ var Cache = class {
680
694
  */
681
695
  readFragment(artifact, fragmentRef) {
682
696
  const entityKey = fragmentRef[FragmentRefKey];
683
- if (!this.#storage[entityKey]) return null;
684
- const { data, partial } = denormalize(artifact.selections, this.#storage, { [EntityLinkKey]: entityKey }, {});
685
- if (partial) return null;
697
+ if (!this.#storage[entityKey]) return {
698
+ data: null,
699
+ stale: false
700
+ };
701
+ let stale = false;
702
+ const { data, partial } = denormalize(artifact.selections, this.#storage, { [EntityLinkKey]: entityKey }, {}, (storageKey, fieldKey) => {
703
+ if (this.#stale.has(storageKey) || this.#stale.has(makeDependencyKey(storageKey, fieldKey))) stale = true;
704
+ });
705
+ if (partial) return {
706
+ data: null,
707
+ stale: false
708
+ };
686
709
  const key = makeMemoKey("fragment", artifact.name, entityKey);
687
710
  const prev = this.#memo.get(key);
688
711
  const result = prev === void 0 ? data : replaceEqualDeep(prev, data);
689
712
  this.#memo.set(key, result);
690
- return result;
713
+ return {
714
+ data: result,
715
+ stale
716
+ };
691
717
  }
692
718
  subscribeFragment(artifact, fragmentRef, listener) {
693
719
  const entityKey = fragmentRef[FragmentRefKey];
@@ -700,17 +726,25 @@ var Cache = class {
700
726
  }
701
727
  readFragments(artifact, fragmentRefs) {
702
728
  const results = [];
729
+ let stale = false;
703
730
  for (const ref of fragmentRefs) {
704
- const data = this.readFragment(artifact, ref);
705
- if (data === null) return null;
706
- results.push(data);
731
+ const result = this.readFragment(artifact, ref);
732
+ if (result.data === null) return {
733
+ data: null,
734
+ stale: false
735
+ };
736
+ if (result.stale) stale = true;
737
+ results.push(result.data);
707
738
  }
708
739
  const entityKeys = fragmentRefs.map((ref) => ref[FragmentRefKey]);
709
740
  const key = makeMemoKey("fragments", artifact.name, entityKeys.join(","));
710
741
  const prev = this.#memo.get(key);
711
742
  const result = prev === void 0 ? results : replaceEqualDeep(prev, results);
712
743
  this.#memo.set(key, result);
713
- return result;
744
+ return {
745
+ data: result,
746
+ stale
747
+ };
714
748
  }
715
749
  subscribeFragments(artifact, fragmentRefs, listener) {
716
750
  const dependencies = /* @__PURE__ */ new Set();
@@ -730,20 +764,21 @@ var Cache = class {
730
764
  const subscriptions = /* @__PURE__ */ new Set();
731
765
  for (const target of targets) if (target.__typename === "Query") if ("field" in target) {
732
766
  const fieldKey = makeFieldKeyFromArgs(target.field, target.args);
733
- delete this.#storage[RootFieldKey]?.[fieldKey];
767
+ const depKey = makeDependencyKey(RootFieldKey, fieldKey);
768
+ this.#stale.add(depKey);
734
769
  this.#collectSubscriptions(RootFieldKey, fieldKey, subscriptions);
735
770
  } else {
736
- this.#storage[RootFieldKey] = {};
771
+ this.#stale.add(RootFieldKey);
737
772
  this.#collectSubscriptions(RootFieldKey, void 0, subscriptions);
738
773
  }
739
774
  else if ("id" in target) {
740
775
  const entityKey = resolveEntityKey(target.__typename, target.id, this.#schemaMeta.entities[target.__typename]?.keyFields);
741
776
  if ("field" in target) {
742
777
  const fieldKey = makeFieldKeyFromArgs(target.field, target.args);
743
- delete this.#storage[entityKey]?.[fieldKey];
778
+ this.#stale.add(makeDependencyKey(entityKey, fieldKey));
744
779
  this.#collectSubscriptions(entityKey, fieldKey, subscriptions);
745
780
  } else {
746
- delete this.#storage[entityKey];
781
+ this.#stale.add(entityKey);
747
782
  this.#collectSubscriptions(entityKey, void 0, subscriptions);
748
783
  }
749
784
  } else {
@@ -752,10 +787,10 @@ var Cache = class {
752
787
  const entityKey = key;
753
788
  if ("field" in target) {
754
789
  const fieldKey = makeFieldKeyFromArgs(target.field, target.args);
755
- delete this.#storage[entityKey]?.[fieldKey];
790
+ this.#stale.add(makeDependencyKey(entityKey, fieldKey));
756
791
  this.#collectSubscriptions(entityKey, fieldKey, subscriptions);
757
792
  } else {
758
- delete this.#storage[entityKey];
793
+ this.#stale.add(entityKey);
759
794
  this.#collectSubscriptions(entityKey, void 0, subscriptions);
760
795
  }
761
796
  }
@@ -814,6 +849,7 @@ var Cache = class {
814
849
  this.#storage = { [RootFieldKey]: {} };
815
850
  this.#subscriptions.clear();
816
851
  this.#memo.clear();
852
+ this.#stale.clear();
817
853
  }
818
854
  };
819
855
 
@@ -857,9 +893,10 @@ const cacheExchange = (options = {}) => {
857
893
  return require_make.pipe(require_make.merge(require_make.fromValue(void 0), trigger.source), require_make.switchMap(() => require_make.fromSubscription(() => cache.readFragments(op.artifact, fragmentRef), () => cache.subscribeFragments(op.artifact, fragmentRef, async () => {
858
894
  await Promise.resolve();
859
895
  trigger.next();
860
- }))), require_make.takeUntil(teardown$), require_make.map((data) => ({
896
+ }))), require_make.takeUntil(teardown$), require_make.map(({ data, stale }) => ({
861
897
  operation: op,
862
898
  data,
899
+ ...stale && { metadata: { cache: { stale: true } } },
863
900
  errors: []
864
901
  })));
865
902
  }
@@ -873,9 +910,10 @@ const cacheExchange = (options = {}) => {
873
910
  return require_make.pipe(require_make.merge(require_make.fromValue(void 0), trigger.source), require_make.switchMap(() => require_make.fromSubscription(() => cache.readFragment(op.artifact, fragmentRef), () => cache.subscribeFragment(op.artifact, fragmentRef, async () => {
874
911
  await Promise.resolve();
875
912
  trigger.next();
876
- }))), require_make.takeUntil(teardown$), require_make.map((data) => ({
913
+ }))), require_make.takeUntil(teardown$), require_make.map(({ data, stale }) => ({
877
914
  operation: op,
878
915
  data,
916
+ ...stale && { metadata: { cache: { stale: true } } },
879
917
  errors: []
880
918
  })));
881
919
  }));
@@ -889,8 +927,8 @@ const cacheExchange = (options = {}) => {
889
927
  return require_make.pipe(require_make.merge(require_make.fromValue(void 0), trigger.source), require_make.switchMap(() => require_make.fromSubscription(() => cache.readQuery(op.artifact, op.variables), () => cache.subscribeQuery(op.artifact, op.variables, async () => {
890
928
  await Promise.resolve();
891
929
  trigger.next();
892
- }))), require_make.takeUntil(teardown$), require_make.mergeMap((data) => {
893
- if (data !== null) {
930
+ }))), require_make.takeUntil(teardown$), require_make.mergeMap(({ data, stale }) => {
931
+ if (data !== null && !stale) {
894
932
  hasData = true;
895
933
  return require_make.fromValue({
896
934
  operation: op,
@@ -898,6 +936,16 @@ const cacheExchange = (options = {}) => {
898
936
  errors: []
899
937
  });
900
938
  }
939
+ if (data !== null && stale) {
940
+ hasData = true;
941
+ refetch$.next(op);
942
+ return require_make.fromValue({
943
+ operation: op,
944
+ data,
945
+ metadata: { cache: { stale: true } },
946
+ errors: []
947
+ });
948
+ }
901
949
  if (hasData) {
902
950
  refetch$.next(op);
903
951
  return empty();
@@ -910,8 +958,8 @@ const cacheExchange = (options = {}) => {
910
958
  return empty();
911
959
  }));
912
960
  }), 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) => {
913
- const cached = cache.readQuery(op.artifact, op.variables);
914
- return fetchPolicy === "cache-and-network" || cached === null;
961
+ const { data } = cache.readQuery(op.artifact, op.variables);
962
+ return fetchPolicy === "cache-and-network" || data === null;
915
963
  })), require_make.pipe(ops$, require_make.filter((op) => op.variant === "teardown")), refetch$.source), forward, require_make.tap((result) => {
916
964
  if (result.operation.variant === "request" && result.data) cache.writeQuery(result.operation.artifact, result.operation.variables, result.data);
917
965
  }), require_make.filter((result) => result.operation.variant !== "request" || result.operation.artifact.kind !== "query" || fetchPolicy === "network-only" || !!(result.errors && result.errors.length > 0))));
package/dist/index.d.cts CHANGED
@@ -116,6 +116,7 @@ declare const createClient: <T extends SchemaMeta$1>(config: ClientOptions<T>) =
116
116
  //#endregion
117
117
  //#region src/exchange.d.ts
118
118
  interface OperationMetadataMap {}
119
+ interface OperationResultMetadataMap {}
119
120
  type OperationMetadata = { [K in keyof OperationMetadataMap]?: OperationMetadataMap[K] } & Record<string, unknown>;
120
121
  type BaseOperation = {
121
122
  key: string;
@@ -135,7 +136,7 @@ type OperationResult = {
135
136
  data?: unknown;
136
137
  errors?: readonly OperationError[];
137
138
  extensions?: Record<string, unknown>;
138
- stale?: boolean;
139
+ metadata?: OperationResultMetadataMap & Record<string, unknown>;
139
140
  };
140
141
  type ExchangeInput<TMeta extends SchemaMeta$1 = SchemaMeta$1> = {
141
142
  forward: ExchangeIO;
@@ -243,6 +244,11 @@ declare module '@mearie/core' {
243
244
  interface ExchangeExtensionMap {
244
245
  cache: CacheOperations;
245
246
  }
247
+ interface OperationResultMetadataMap {
248
+ cache?: {
249
+ stale: boolean;
250
+ };
251
+ }
246
252
  }
247
253
  type CacheOptions = {
248
254
  fetchPolicy?: 'cache-first' | 'cache-and-network' | 'network-only' | 'cache-only';
@@ -345,4 +351,4 @@ declare class RequiredFieldError extends Error {
345
351
  */
346
352
  declare const stringify: (value: unknown) => string;
347
353
  //#endregion
348
- export { AggregatedError, type Artifact, type ArtifactKind, type CacheOptions, type CacheSnapshot, Client, type ClientOptions, type DataOf, type Exchange, ExchangeError, type ExchangeErrorExtensionsMap, type ExchangeExtensionMap, type ExchangeIO, type ExchangeResult, type FragmentOptions, type FragmentRefs, GraphQLError, type HttpOptions, type MutationOptions, type Operation, type OperationError, type OperationMetadata, type OperationMetadataMap, type OperationResult, type QueryOptions, type RequiredAction, RequiredFieldError, type RetryOptions, type SchemaMeta, type SubscriptionClient, type SubscriptionExchangeOptions, type SubscriptionOptions, type VariablesOf, cacheExchange, createClient, dedupExchange, fragmentExchange, httpExchange, isAggregatedError, isExchangeError, isGraphQLError, requiredExchange, retryExchange, stringify, subscriptionExchange };
354
+ export { AggregatedError, type Artifact, type ArtifactKind, type CacheOptions, type CacheSnapshot, Client, type ClientOptions, type DataOf, type Exchange, ExchangeError, type ExchangeErrorExtensionsMap, type ExchangeExtensionMap, type ExchangeIO, type ExchangeResult, type FragmentOptions, type FragmentRefs, GraphQLError, type HttpOptions, type MutationOptions, type Operation, type OperationError, type OperationMetadata, type OperationMetadataMap, type OperationResult, type OperationResultMetadataMap, type QueryOptions, type RequiredAction, RequiredFieldError, type RetryOptions, type SchemaMeta, type SubscriptionClient, type SubscriptionExchangeOptions, type SubscriptionOptions, type VariablesOf, cacheExchange, createClient, dedupExchange, fragmentExchange, httpExchange, isAggregatedError, isExchangeError, isGraphQLError, requiredExchange, retryExchange, stringify, subscriptionExchange };
package/dist/index.d.mts CHANGED
@@ -116,6 +116,7 @@ declare const createClient: <T extends SchemaMeta$1>(config: ClientOptions<T>) =
116
116
  //#endregion
117
117
  //#region src/exchange.d.ts
118
118
  interface OperationMetadataMap {}
119
+ interface OperationResultMetadataMap {}
119
120
  type OperationMetadata = { [K in keyof OperationMetadataMap]?: OperationMetadataMap[K] } & Record<string, unknown>;
120
121
  type BaseOperation = {
121
122
  key: string;
@@ -135,7 +136,7 @@ type OperationResult = {
135
136
  data?: unknown;
136
137
  errors?: readonly OperationError[];
137
138
  extensions?: Record<string, unknown>;
138
- stale?: boolean;
139
+ metadata?: OperationResultMetadataMap & Record<string, unknown>;
139
140
  };
140
141
  type ExchangeInput<TMeta extends SchemaMeta$1 = SchemaMeta$1> = {
141
142
  forward: ExchangeIO;
@@ -243,6 +244,11 @@ declare module '@mearie/core' {
243
244
  interface ExchangeExtensionMap {
244
245
  cache: CacheOperations;
245
246
  }
247
+ interface OperationResultMetadataMap {
248
+ cache?: {
249
+ stale: boolean;
250
+ };
251
+ }
246
252
  }
247
253
  type CacheOptions = {
248
254
  fetchPolicy?: 'cache-first' | 'cache-and-network' | 'network-only' | 'cache-only';
@@ -345,4 +351,4 @@ declare class RequiredFieldError extends Error {
345
351
  */
346
352
  declare const stringify: (value: unknown) => string;
347
353
  //#endregion
348
- export { AggregatedError, type Artifact, type ArtifactKind, type CacheOptions, type CacheSnapshot, Client, type ClientOptions, type DataOf, type Exchange, ExchangeError, type ExchangeErrorExtensionsMap, type ExchangeExtensionMap, type ExchangeIO, type ExchangeResult, type FragmentOptions, type FragmentRefs, GraphQLError, type HttpOptions, type MutationOptions, type Operation, type OperationError, type OperationMetadata, type OperationMetadataMap, type OperationResult, type QueryOptions, type RequiredAction, RequiredFieldError, type RetryOptions, type SchemaMeta, type SubscriptionClient, type SubscriptionExchangeOptions, type SubscriptionOptions, type VariablesOf, cacheExchange, createClient, dedupExchange, fragmentExchange, httpExchange, isAggregatedError, isExchangeError, isGraphQLError, requiredExchange, retryExchange, stringify, subscriptionExchange };
354
+ export { AggregatedError, type Artifact, type ArtifactKind, type CacheOptions, type CacheSnapshot, Client, type ClientOptions, type DataOf, type Exchange, ExchangeError, type ExchangeErrorExtensionsMap, type ExchangeExtensionMap, type ExchangeIO, type ExchangeResult, type FragmentOptions, type FragmentRefs, GraphQLError, type HttpOptions, type MutationOptions, type Operation, type OperationError, type OperationMetadata, type OperationMetadataMap, type OperationResult, type OperationResultMetadataMap, type QueryOptions, type RequiredAction, RequiredFieldError, type RetryOptions, type SchemaMeta, type SubscriptionClient, type SubscriptionExchangeOptions, type SubscriptionOptions, type VariablesOf, cacheExchange, createClient, dedupExchange, fragmentExchange, httpExchange, isAggregatedError, isExchangeError, isGraphQLError, requiredExchange, retryExchange, stringify, subscriptionExchange };
package/dist/index.mjs CHANGED
@@ -593,8 +593,10 @@ const denormalize = (selections, storage, value, variables, accessor) => {
593
593
  const value = selection.selections ? denormalizeField(null, selection.selections, fieldValue) : fieldValue;
594
594
  if (name in fields) mergeFields(fields, { [name]: value });
595
595
  else fields[name] = value;
596
- } else if (selection.kind === "FragmentSpread") if (storageKey !== null && storageKey !== RootFieldKey) fields[FragmentRefKey] = storageKey;
597
- else mergeFields(fields, denormalizeField(storageKey, selection.selections, value));
596
+ } else if (selection.kind === "FragmentSpread") if (storageKey !== null && storageKey !== RootFieldKey) {
597
+ fields[FragmentRefKey] = storageKey;
598
+ if (accessor) denormalize(selection.selections, storage, { [EntityLinkKey]: storageKey }, variables, accessor);
599
+ } else mergeFields(fields, denormalizeField(storageKey, selection.selections, value));
598
600
  else if (selection.kind === "InlineFragment" && selection.on === data[typenameFieldKey]) mergeFields(fields, denormalizeField(storageKey, selection.selections, value));
599
601
  return fields;
600
602
  };
@@ -615,6 +617,7 @@ var Cache = class {
615
617
  #storage = { [RootFieldKey]: {} };
616
618
  #subscriptions = /* @__PURE__ */ new Map();
617
619
  #memo = /* @__PURE__ */ new Map();
620
+ #stale = /* @__PURE__ */ new Set();
618
621
  constructor(schemaMetadata) {
619
622
  this.#schemaMeta = schemaMetadata;
620
623
  }
@@ -627,12 +630,14 @@ var Cache = class {
627
630
  writeQuery(artifact, variables, data) {
628
631
  const dependencies = /* @__PURE__ */ new Set();
629
632
  const subscriptions = /* @__PURE__ */ new Set();
633
+ const entityStaleCleared = /* @__PURE__ */ new Set();
630
634
  normalize(this.#schemaMeta, artifact.selections, this.#storage, data, variables, (storageKey, fieldKey, oldValue, newValue) => {
631
- if (oldValue !== newValue) {
632
- const dependencyKey = makeDependencyKey(storageKey, fieldKey);
633
- dependencies.add(dependencyKey);
634
- }
635
+ const depKey = makeDependencyKey(storageKey, fieldKey);
636
+ if (this.#stale.delete(depKey)) dependencies.add(depKey);
637
+ if (!entityStaleCleared.has(storageKey) && this.#stale.delete(storageKey)) entityStaleCleared.add(storageKey);
638
+ if (oldValue !== newValue) dependencies.add(depKey);
635
639
  });
640
+ for (const entityKey of entityStaleCleared) this.#collectSubscriptions(entityKey, void 0, subscriptions);
636
641
  for (const dependency of dependencies) {
637
642
  const ss = this.#subscriptions.get(dependency);
638
643
  if (ss) for (const s of ss) subscriptions.add(s);
@@ -647,13 +652,22 @@ var Cache = class {
647
652
  * @returns Denormalized query result or null if not found.
648
653
  */
649
654
  readQuery(artifact, variables) {
650
- const { data, partial } = denormalize(artifact.selections, this.#storage, this.#storage[RootFieldKey], variables);
651
- if (partial) return null;
655
+ let stale = false;
656
+ const { data, partial } = denormalize(artifact.selections, this.#storage, this.#storage[RootFieldKey], variables, (storageKey, fieldKey) => {
657
+ if (this.#stale.has(storageKey) || this.#stale.has(makeDependencyKey(storageKey, fieldKey))) stale = true;
658
+ });
659
+ if (partial) return {
660
+ data: null,
661
+ stale: false
662
+ };
652
663
  const key = makeMemoKey("query", artifact.name, stringify(variables));
653
664
  const prev = this.#memo.get(key);
654
665
  const result = prev === void 0 ? data : replaceEqualDeep(prev, data);
655
666
  this.#memo.set(key, result);
656
- return result;
667
+ return {
668
+ data: result,
669
+ stale
670
+ };
657
671
  }
658
672
  /**
659
673
  * Subscribes to cache invalidations for a specific query.
@@ -679,14 +693,26 @@ var Cache = class {
679
693
  */
680
694
  readFragment(artifact, fragmentRef) {
681
695
  const entityKey = fragmentRef[FragmentRefKey];
682
- if (!this.#storage[entityKey]) return null;
683
- const { data, partial } = denormalize(artifact.selections, this.#storage, { [EntityLinkKey]: entityKey }, {});
684
- if (partial) return null;
696
+ if (!this.#storage[entityKey]) return {
697
+ data: null,
698
+ stale: false
699
+ };
700
+ let stale = false;
701
+ const { data, partial } = denormalize(artifact.selections, this.#storage, { [EntityLinkKey]: entityKey }, {}, (storageKey, fieldKey) => {
702
+ if (this.#stale.has(storageKey) || this.#stale.has(makeDependencyKey(storageKey, fieldKey))) stale = true;
703
+ });
704
+ if (partial) return {
705
+ data: null,
706
+ stale: false
707
+ };
685
708
  const key = makeMemoKey("fragment", artifact.name, entityKey);
686
709
  const prev = this.#memo.get(key);
687
710
  const result = prev === void 0 ? data : replaceEqualDeep(prev, data);
688
711
  this.#memo.set(key, result);
689
- return result;
712
+ return {
713
+ data: result,
714
+ stale
715
+ };
690
716
  }
691
717
  subscribeFragment(artifact, fragmentRef, listener) {
692
718
  const entityKey = fragmentRef[FragmentRefKey];
@@ -699,17 +725,25 @@ var Cache = class {
699
725
  }
700
726
  readFragments(artifact, fragmentRefs) {
701
727
  const results = [];
728
+ let stale = false;
702
729
  for (const ref of fragmentRefs) {
703
- const data = this.readFragment(artifact, ref);
704
- if (data === null) return null;
705
- results.push(data);
730
+ const result = this.readFragment(artifact, ref);
731
+ if (result.data === null) return {
732
+ data: null,
733
+ stale: false
734
+ };
735
+ if (result.stale) stale = true;
736
+ results.push(result.data);
706
737
  }
707
738
  const entityKeys = fragmentRefs.map((ref) => ref[FragmentRefKey]);
708
739
  const key = makeMemoKey("fragments", artifact.name, entityKeys.join(","));
709
740
  const prev = this.#memo.get(key);
710
741
  const result = prev === void 0 ? results : replaceEqualDeep(prev, results);
711
742
  this.#memo.set(key, result);
712
- return result;
743
+ return {
744
+ data: result,
745
+ stale
746
+ };
713
747
  }
714
748
  subscribeFragments(artifact, fragmentRefs, listener) {
715
749
  const dependencies = /* @__PURE__ */ new Set();
@@ -729,20 +763,21 @@ var Cache = class {
729
763
  const subscriptions = /* @__PURE__ */ new Set();
730
764
  for (const target of targets) if (target.__typename === "Query") if ("field" in target) {
731
765
  const fieldKey = makeFieldKeyFromArgs(target.field, target.args);
732
- delete this.#storage[RootFieldKey]?.[fieldKey];
766
+ const depKey = makeDependencyKey(RootFieldKey, fieldKey);
767
+ this.#stale.add(depKey);
733
768
  this.#collectSubscriptions(RootFieldKey, fieldKey, subscriptions);
734
769
  } else {
735
- this.#storage[RootFieldKey] = {};
770
+ this.#stale.add(RootFieldKey);
736
771
  this.#collectSubscriptions(RootFieldKey, void 0, subscriptions);
737
772
  }
738
773
  else if ("id" in target) {
739
774
  const entityKey = resolveEntityKey(target.__typename, target.id, this.#schemaMeta.entities[target.__typename]?.keyFields);
740
775
  if ("field" in target) {
741
776
  const fieldKey = makeFieldKeyFromArgs(target.field, target.args);
742
- delete this.#storage[entityKey]?.[fieldKey];
777
+ this.#stale.add(makeDependencyKey(entityKey, fieldKey));
743
778
  this.#collectSubscriptions(entityKey, fieldKey, subscriptions);
744
779
  } else {
745
- delete this.#storage[entityKey];
780
+ this.#stale.add(entityKey);
746
781
  this.#collectSubscriptions(entityKey, void 0, subscriptions);
747
782
  }
748
783
  } else {
@@ -751,10 +786,10 @@ var Cache = class {
751
786
  const entityKey = key;
752
787
  if ("field" in target) {
753
788
  const fieldKey = makeFieldKeyFromArgs(target.field, target.args);
754
- delete this.#storage[entityKey]?.[fieldKey];
789
+ this.#stale.add(makeDependencyKey(entityKey, fieldKey));
755
790
  this.#collectSubscriptions(entityKey, fieldKey, subscriptions);
756
791
  } else {
757
- delete this.#storage[entityKey];
792
+ this.#stale.add(entityKey);
758
793
  this.#collectSubscriptions(entityKey, void 0, subscriptions);
759
794
  }
760
795
  }
@@ -813,6 +848,7 @@ var Cache = class {
813
848
  this.#storage = { [RootFieldKey]: {} };
814
849
  this.#subscriptions.clear();
815
850
  this.#memo.clear();
851
+ this.#stale.clear();
816
852
  }
817
853
  };
818
854
 
@@ -856,9 +892,10 @@ const cacheExchange = (options = {}) => {
856
892
  return pipe(merge(fromValue(void 0), trigger.source), switchMap(() => fromSubscription(() => cache.readFragments(op.artifact, fragmentRef), () => cache.subscribeFragments(op.artifact, fragmentRef, async () => {
857
893
  await Promise.resolve();
858
894
  trigger.next();
859
- }))), takeUntil(teardown$), map((data) => ({
895
+ }))), takeUntil(teardown$), map(({ data, stale }) => ({
860
896
  operation: op,
861
897
  data,
898
+ ...stale && { metadata: { cache: { stale: true } } },
862
899
  errors: []
863
900
  })));
864
901
  }
@@ -872,9 +909,10 @@ const cacheExchange = (options = {}) => {
872
909
  return pipe(merge(fromValue(void 0), trigger.source), switchMap(() => fromSubscription(() => cache.readFragment(op.artifact, fragmentRef), () => cache.subscribeFragment(op.artifact, fragmentRef, async () => {
873
910
  await Promise.resolve();
874
911
  trigger.next();
875
- }))), takeUntil(teardown$), map((data) => ({
912
+ }))), takeUntil(teardown$), map(({ data, stale }) => ({
876
913
  operation: op,
877
914
  data,
915
+ ...stale && { metadata: { cache: { stale: true } } },
878
916
  errors: []
879
917
  })));
880
918
  }));
@@ -888,8 +926,8 @@ const cacheExchange = (options = {}) => {
888
926
  return pipe(merge(fromValue(void 0), trigger.source), switchMap(() => fromSubscription(() => cache.readQuery(op.artifact, op.variables), () => cache.subscribeQuery(op.artifact, op.variables, async () => {
889
927
  await Promise.resolve();
890
928
  trigger.next();
891
- }))), takeUntil(teardown$), mergeMap((data) => {
892
- if (data !== null) {
929
+ }))), takeUntil(teardown$), mergeMap(({ data, stale }) => {
930
+ if (data !== null && !stale) {
893
931
  hasData = true;
894
932
  return fromValue({
895
933
  operation: op,
@@ -897,6 +935,16 @@ const cacheExchange = (options = {}) => {
897
935
  errors: []
898
936
  });
899
937
  }
938
+ if (data !== null && stale) {
939
+ hasData = true;
940
+ refetch$.next(op);
941
+ return fromValue({
942
+ operation: op,
943
+ data,
944
+ metadata: { cache: { stale: true } },
945
+ errors: []
946
+ });
947
+ }
900
948
  if (hasData) {
901
949
  refetch$.next(op);
902
950
  return empty();
@@ -909,8 +957,8 @@ const cacheExchange = (options = {}) => {
909
957
  return empty();
910
958
  }));
911
959
  }), filter(() => fetchPolicy === "cache-only" || fetchPolicy === "cache-and-network" || fetchPolicy === "cache-first")), pipe(merge(nonCache$, pipe(query$, filter((op) => {
912
- const cached = cache.readQuery(op.artifact, op.variables);
913
- return fetchPolicy === "cache-and-network" || cached === null;
960
+ const { data } = cache.readQuery(op.artifact, op.variables);
961
+ return fetchPolicy === "cache-and-network" || data === null;
914
962
  })), pipe(ops$, filter((op) => op.variant === "teardown")), refetch$.source), forward, tap((result) => {
915
963
  if (result.operation.variant === "request" && result.data) cache.writeQuery(result.operation.artifact, result.operation.variables, result.data);
916
964
  }), filter((result) => result.operation.variant !== "request" || result.operation.artifact.kind !== "query" || fetchPolicy === "network-only" || !!(result.errors && result.errors.length > 0))));
package/package.json CHANGED
@@ -1,15 +1,22 @@
1
1
  {
2
2
  "name": "@mearie/core",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
4
4
  "description": "Type-safe, zero-overhead GraphQL client",
5
5
  "keywords": [
6
6
  "graphql",
7
7
  "graphql-client",
8
8
  "typescript",
9
+ "type-safe",
9
10
  "codegen",
10
- "cache"
11
+ "cache",
12
+ "normalized-cache",
13
+ "react",
14
+ "vue",
15
+ "svelte",
16
+ "solid",
17
+ "vite"
11
18
  ],
12
- "homepage": "https://github.com/devunt/mearie#readme",
19
+ "homepage": "https://mearie.dev/",
13
20
  "bugs": {
14
21
  "url": "https://github.com/devunt/mearie/issues"
15
22
  },
@@ -55,7 +62,7 @@
55
62
  "README.md"
56
63
  ],
57
64
  "dependencies": {
58
- "@mearie/shared": "0.2.1"
65
+ "@mearie/shared": "0.2.2"
59
66
  },
60
67
  "devDependencies": {
61
68
  "tsdown": "^0.20.3",