@mearie/core 0.2.2 → 0.2.4

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
@@ -91,6 +91,7 @@ const executeFetch = async ({ url, fetchFn, fetchOptions, operation, signal }) =
91
91
  ...fetchOptions.headers
92
92
  },
93
93
  body: JSON.stringify({
94
+ operationName: artifact.name,
94
95
  query: artifact.body,
95
96
  variables
96
97
  }),
@@ -263,16 +264,19 @@ const dedupExchange = () => {
263
264
  name: "dedup",
264
265
  io: (ops$) => {
265
266
  const operations = /* @__PURE__ */ new Map();
267
+ const resolved = /* @__PURE__ */ new Set();
266
268
  return require_make.pipe(require_make.merge(require_make.pipe(ops$, require_make.filter((op) => op.variant === "request" && (op.artifact.kind === "mutation" || op.artifact.kind === "fragment"))), require_make.pipe(ops$, require_make.filter((op) => op.variant === "request" && op.artifact.kind !== "mutation" && op.artifact.kind !== "fragment"), require_make.filter((op) => {
267
269
  const dedupKey = makeDedupKey(op);
268
- const isInflight = operations.has(dedupKey);
270
+ const isInflight = operations.has(dedupKey) && !resolved.has(dedupKey);
269
271
  if (isInflight) operations.get(dedupKey).add(op.key);
270
272
  else operations.set(dedupKey, new Set([op.key]));
273
+ if (!isInflight) resolved.delete(dedupKey);
271
274
  return (op.metadata.dedup?.skip ?? false) || !isInflight;
272
275
  }), delay(0)), require_make.pipe(ops$, require_make.filter((op) => op.variant === "teardown"), require_make.filter((teardown) => {
273
276
  for (const [dedupKey, subs] of operations.entries()) if (subs.delete(teardown.key)) {
274
277
  if (subs.size === 0) {
275
278
  operations.delete(dedupKey);
279
+ resolved.delete(dedupKey);
276
280
  return true;
277
281
  }
278
282
  return false;
@@ -281,6 +285,7 @@ const dedupExchange = () => {
281
285
  }))), forward, require_make.mergeMap((result) => {
282
286
  if (result.operation.variant !== "request" || result.operation.artifact.kind === "mutation" || result.operation.artifact.kind === "fragment") return require_make.fromValue(result);
283
287
  const dedupKey = makeDedupKey(result.operation);
288
+ resolved.add(dedupKey);
284
289
  return require_make.fromArray([...operations.get(dedupKey) ?? /* @__PURE__ */ new Set()].map((key) => ({
285
290
  ...result,
286
291
  operation: {
@@ -633,7 +638,7 @@ var Cache = class {
633
638
  const ss = this.#subscriptions.get(dependency);
634
639
  if (ss) for (const s of ss) subscriptions.add(s);
635
640
  }
636
- for (const subscription of subscriptions) subscription.listener();
641
+ for (const subscription of subscriptions) subscription.listener("write");
637
642
  }
638
643
  /**
639
644
  * Reads a query result from the cache, denormalizing entities if available.
@@ -741,6 +746,7 @@ var Cache = class {
741
746
  delete this.#storage[entityKey];
742
747
  this.#collectSubscriptions(entityKey, void 0, subscriptions);
743
748
  }
749
+ this.#collectLinkedEntitySubscriptions((linkedEntityKey) => linkedEntityKey === entityKey, subscriptions);
744
750
  } else {
745
751
  const prefix = `${target.__typename}:`;
746
752
  for (const key of Object.keys(this.#storage)) if (key.startsWith(prefix)) {
@@ -754,8 +760,18 @@ var Cache = class {
754
760
  this.#collectSubscriptions(entityKey, void 0, subscriptions);
755
761
  }
756
762
  }
763
+ this.#collectLinkedEntitySubscriptions((linkedEntityKey) => linkedEntityKey.startsWith(prefix), subscriptions);
757
764
  }
758
- for (const subscription of subscriptions) subscription.listener();
765
+ for (const subscription of subscriptions) subscription.listener("invalidate");
766
+ }
767
+ #collectLinkedEntitySubscriptions(matcher, out) {
768
+ for (const [storageKey, fields] of Object.entries(this.#storage)) for (const [fieldKey, value] of Object.entries(fields)) if (this.#containsEntityLink(value, matcher)) this.#collectSubscriptions(storageKey, fieldKey, out);
769
+ }
770
+ #containsEntityLink(value, matcher) {
771
+ if (isEntityLink(value)) return matcher(value[EntityLinkKey]);
772
+ if (Array.isArray(value)) return value.some((item) => this.#containsEntityLink(item, matcher));
773
+ if (value && typeof value === "object") return Object.values(value).some((item) => this.#containsEntityLink(item, matcher));
774
+ return false;
759
775
  }
760
776
  #collectSubscriptions(storageKey, fieldKey, out) {
761
777
  if (fieldKey === void 0) {
@@ -848,15 +864,27 @@ const cacheExchange = (options = {}) => {
848
864
  });
849
865
  if (isFragmentRefArray(fragmentRef)) {
850
866
  const trigger = require_make.makeSubject();
867
+ let hasData = false;
851
868
  const teardown$ = require_make.pipe(ops$, require_make.filter((operation) => operation.variant === "teardown" && operation.key === op.key), require_make.tap(() => trigger.complete()));
852
869
  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 () => {
853
870
  await Promise.resolve();
854
871
  trigger.next();
855
- }))), require_make.takeUntil(teardown$), require_make.map((data) => ({
856
- operation: op,
857
- data,
858
- errors: []
859
- })));
872
+ }))), require_make.takeUntil(teardown$), require_make.mergeMap((data) => {
873
+ if (data !== null) {
874
+ hasData = true;
875
+ return require_make.fromValue({
876
+ operation: op,
877
+ data,
878
+ errors: []
879
+ });
880
+ }
881
+ if (hasData) return empty();
882
+ return require_make.fromValue({
883
+ operation: op,
884
+ data,
885
+ errors: []
886
+ });
887
+ }));
860
888
  }
861
889
  if (!isFragmentRef(fragmentRef)) return require_make.fromValue({
862
890
  operation: op,
@@ -864,15 +892,27 @@ const cacheExchange = (options = {}) => {
864
892
  errors: []
865
893
  });
866
894
  const trigger = require_make.makeSubject();
895
+ let hasData = false;
867
896
  const teardown$ = require_make.pipe(ops$, require_make.filter((operation) => operation.variant === "teardown" && operation.key === op.key), require_make.tap(() => trigger.complete()));
868
897
  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 () => {
869
898
  await Promise.resolve();
870
899
  trigger.next();
871
- }))), require_make.takeUntil(teardown$), require_make.map((data) => ({
872
- operation: op,
873
- data,
874
- errors: []
875
- })));
900
+ }))), require_make.takeUntil(teardown$), require_make.mergeMap((data) => {
901
+ if (data !== null) {
902
+ hasData = true;
903
+ return require_make.fromValue({
904
+ operation: op,
905
+ data,
906
+ errors: []
907
+ });
908
+ }
909
+ if (hasData) return empty();
910
+ return require_make.fromValue({
911
+ operation: op,
912
+ data,
913
+ errors: []
914
+ });
915
+ }));
876
916
  }));
877
917
  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")));
878
918
  const query$ = require_make.pipe(ops$, require_make.filter((op) => op.variant === "request" && op.artifact.kind === "query" && fetchPolicy !== "network-only"), require_make.share());
@@ -880,13 +920,21 @@ const cacheExchange = (options = {}) => {
880
920
  return require_make.merge(fragment$, require_make.pipe(query$, require_make.mergeMap((op) => {
881
921
  const trigger = require_make.makeSubject();
882
922
  let hasData = false;
923
+ let invalidated = false;
883
924
  const teardown$ = require_make.pipe(ops$, require_make.filter((operation) => operation.variant === "teardown" && operation.key === op.key), require_make.tap(() => trigger.complete()));
884
- 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 () => {
925
+ return require_make.pipe(require_make.merge(require_make.fromValue(void 0), trigger.source), require_make.switchMap(() => require_make.fromSubscription(() => cache.readQuery(op.artifact, op.variables), () => cache.subscribeQuery(op.artifact, op.variables, async (event) => {
926
+ if (event === "invalidate") invalidated = true;
885
927
  await Promise.resolve();
886
928
  trigger.next();
887
929
  }))), require_make.takeUntil(teardown$), require_make.mergeMap((data) => {
888
930
  if (data !== null) {
931
+ if (invalidated && hasData && fetchPolicy !== "cache-only") {
932
+ invalidated = false;
933
+ refetch$.next(op);
934
+ return empty();
935
+ }
889
936
  hasData = true;
937
+ invalidated = false;
890
938
  return require_make.fromValue({
891
939
  operation: op,
892
940
  data,
@@ -894,7 +942,8 @@ const cacheExchange = (options = {}) => {
894
942
  });
895
943
  }
896
944
  if (hasData) {
897
- refetch$.next(op);
945
+ if (fetchPolicy !== "cache-only") refetch$.next(op);
946
+ invalidated = false;
898
947
  return empty();
899
948
  }
900
949
  if (fetchPolicy === "cache-only") return require_make.fromValue({
@@ -1108,6 +1157,7 @@ const subscriptionExchange = (options) => {
1108
1157
  Promise.resolve().then(() => {
1109
1158
  if (completed) return;
1110
1159
  unsubscribe = client.subscribe({
1160
+ operationName: op.artifact.name,
1111
1161
  query: op.artifact.body,
1112
1162
  variables: op.variables
1113
1163
  }, {
package/dist/index.d.cts CHANGED
@@ -279,6 +279,7 @@ declare const requiredExchange: () => Exchange;
279
279
  //#region src/exchanges/subscription.d.ts
280
280
  interface SubscriptionClient {
281
281
  subscribe(payload: {
282
+ operationName?: string;
282
283
  query: string;
283
284
  variables?: Record<string, unknown>;
284
285
  }, sink: {
package/dist/index.d.mts CHANGED
@@ -279,6 +279,7 @@ declare const requiredExchange: () => Exchange;
279
279
  //#region src/exchanges/subscription.d.ts
280
280
  interface SubscriptionClient {
281
281
  subscribe(payload: {
282
+ operationName?: string;
282
283
  query: string;
283
284
  variables?: Record<string, unknown>;
284
285
  }, sink: {
package/dist/index.mjs CHANGED
@@ -90,6 +90,7 @@ const executeFetch = async ({ url, fetchFn, fetchOptions, operation, signal }) =
90
90
  ...fetchOptions.headers
91
91
  },
92
92
  body: JSON.stringify({
93
+ operationName: artifact.name,
93
94
  query: artifact.body,
94
95
  variables
95
96
  }),
@@ -262,16 +263,19 @@ const dedupExchange = () => {
262
263
  name: "dedup",
263
264
  io: (ops$) => {
264
265
  const operations = /* @__PURE__ */ new Map();
266
+ const resolved = /* @__PURE__ */ new Set();
265
267
  return pipe(merge(pipe(ops$, filter((op) => op.variant === "request" && (op.artifact.kind === "mutation" || op.artifact.kind === "fragment"))), pipe(ops$, filter((op) => op.variant === "request" && op.artifact.kind !== "mutation" && op.artifact.kind !== "fragment"), filter((op) => {
266
268
  const dedupKey = makeDedupKey(op);
267
- const isInflight = operations.has(dedupKey);
269
+ const isInflight = operations.has(dedupKey) && !resolved.has(dedupKey);
268
270
  if (isInflight) operations.get(dedupKey).add(op.key);
269
271
  else operations.set(dedupKey, new Set([op.key]));
272
+ if (!isInflight) resolved.delete(dedupKey);
270
273
  return (op.metadata.dedup?.skip ?? false) || !isInflight;
271
274
  }), delay(0)), pipe(ops$, filter((op) => op.variant === "teardown"), filter((teardown) => {
272
275
  for (const [dedupKey, subs] of operations.entries()) if (subs.delete(teardown.key)) {
273
276
  if (subs.size === 0) {
274
277
  operations.delete(dedupKey);
278
+ resolved.delete(dedupKey);
275
279
  return true;
276
280
  }
277
281
  return false;
@@ -280,6 +284,7 @@ const dedupExchange = () => {
280
284
  }))), forward, mergeMap((result) => {
281
285
  if (result.operation.variant !== "request" || result.operation.artifact.kind === "mutation" || result.operation.artifact.kind === "fragment") return fromValue(result);
282
286
  const dedupKey = makeDedupKey(result.operation);
287
+ resolved.add(dedupKey);
283
288
  return fromArray([...operations.get(dedupKey) ?? /* @__PURE__ */ new Set()].map((key) => ({
284
289
  ...result,
285
290
  operation: {
@@ -632,7 +637,7 @@ var Cache = class {
632
637
  const ss = this.#subscriptions.get(dependency);
633
638
  if (ss) for (const s of ss) subscriptions.add(s);
634
639
  }
635
- for (const subscription of subscriptions) subscription.listener();
640
+ for (const subscription of subscriptions) subscription.listener("write");
636
641
  }
637
642
  /**
638
643
  * Reads a query result from the cache, denormalizing entities if available.
@@ -740,6 +745,7 @@ var Cache = class {
740
745
  delete this.#storage[entityKey];
741
746
  this.#collectSubscriptions(entityKey, void 0, subscriptions);
742
747
  }
748
+ this.#collectLinkedEntitySubscriptions((linkedEntityKey) => linkedEntityKey === entityKey, subscriptions);
743
749
  } else {
744
750
  const prefix = `${target.__typename}:`;
745
751
  for (const key of Object.keys(this.#storage)) if (key.startsWith(prefix)) {
@@ -753,8 +759,18 @@ var Cache = class {
753
759
  this.#collectSubscriptions(entityKey, void 0, subscriptions);
754
760
  }
755
761
  }
762
+ this.#collectLinkedEntitySubscriptions((linkedEntityKey) => linkedEntityKey.startsWith(prefix), subscriptions);
756
763
  }
757
- for (const subscription of subscriptions) subscription.listener();
764
+ for (const subscription of subscriptions) subscription.listener("invalidate");
765
+ }
766
+ #collectLinkedEntitySubscriptions(matcher, out) {
767
+ for (const [storageKey, fields] of Object.entries(this.#storage)) for (const [fieldKey, value] of Object.entries(fields)) if (this.#containsEntityLink(value, matcher)) this.#collectSubscriptions(storageKey, fieldKey, out);
768
+ }
769
+ #containsEntityLink(value, matcher) {
770
+ if (isEntityLink(value)) return matcher(value[EntityLinkKey]);
771
+ if (Array.isArray(value)) return value.some((item) => this.#containsEntityLink(item, matcher));
772
+ if (value && typeof value === "object") return Object.values(value).some((item) => this.#containsEntityLink(item, matcher));
773
+ return false;
758
774
  }
759
775
  #collectSubscriptions(storageKey, fieldKey, out) {
760
776
  if (fieldKey === void 0) {
@@ -847,15 +863,27 @@ const cacheExchange = (options = {}) => {
847
863
  });
848
864
  if (isFragmentRefArray(fragmentRef)) {
849
865
  const trigger = makeSubject();
866
+ let hasData = false;
850
867
  const teardown$ = pipe(ops$, filter((operation) => operation.variant === "teardown" && operation.key === op.key), tap(() => trigger.complete()));
851
868
  return pipe(merge(fromValue(void 0), trigger.source), switchMap(() => fromSubscription(() => cache.readFragments(op.artifact, fragmentRef), () => cache.subscribeFragments(op.artifact, fragmentRef, async () => {
852
869
  await Promise.resolve();
853
870
  trigger.next();
854
- }))), takeUntil(teardown$), map((data) => ({
855
- operation: op,
856
- data,
857
- errors: []
858
- })));
871
+ }))), takeUntil(teardown$), mergeMap((data) => {
872
+ if (data !== null) {
873
+ hasData = true;
874
+ return fromValue({
875
+ operation: op,
876
+ data,
877
+ errors: []
878
+ });
879
+ }
880
+ if (hasData) return empty();
881
+ return fromValue({
882
+ operation: op,
883
+ data,
884
+ errors: []
885
+ });
886
+ }));
859
887
  }
860
888
  if (!isFragmentRef(fragmentRef)) return fromValue({
861
889
  operation: op,
@@ -863,15 +891,27 @@ const cacheExchange = (options = {}) => {
863
891
  errors: []
864
892
  });
865
893
  const trigger = makeSubject();
894
+ let hasData = false;
866
895
  const teardown$ = pipe(ops$, filter((operation) => operation.variant === "teardown" && operation.key === op.key), tap(() => trigger.complete()));
867
896
  return pipe(merge(fromValue(void 0), trigger.source), switchMap(() => fromSubscription(() => cache.readFragment(op.artifact, fragmentRef), () => cache.subscribeFragment(op.artifact, fragmentRef, async () => {
868
897
  await Promise.resolve();
869
898
  trigger.next();
870
- }))), takeUntil(teardown$), map((data) => ({
871
- operation: op,
872
- data,
873
- errors: []
874
- })));
899
+ }))), takeUntil(teardown$), mergeMap((data) => {
900
+ if (data !== null) {
901
+ hasData = true;
902
+ return fromValue({
903
+ operation: op,
904
+ data,
905
+ errors: []
906
+ });
907
+ }
908
+ if (hasData) return empty();
909
+ return fromValue({
910
+ operation: op,
911
+ data,
912
+ errors: []
913
+ });
914
+ }));
875
915
  }));
876
916
  const nonCache$ = pipe(ops$, filter((op) => op.variant === "request" && (op.artifact.kind === "mutation" || op.artifact.kind === "subscription" || op.artifact.kind === "query" && fetchPolicy === "network-only")));
877
917
  const query$ = pipe(ops$, filter((op) => op.variant === "request" && op.artifact.kind === "query" && fetchPolicy !== "network-only"), share());
@@ -879,13 +919,21 @@ const cacheExchange = (options = {}) => {
879
919
  return merge(fragment$, pipe(query$, mergeMap((op) => {
880
920
  const trigger = makeSubject();
881
921
  let hasData = false;
922
+ let invalidated = false;
882
923
  const teardown$ = pipe(ops$, filter((operation) => operation.variant === "teardown" && operation.key === op.key), tap(() => trigger.complete()));
883
- return pipe(merge(fromValue(void 0), trigger.source), switchMap(() => fromSubscription(() => cache.readQuery(op.artifact, op.variables), () => cache.subscribeQuery(op.artifact, op.variables, async () => {
924
+ return pipe(merge(fromValue(void 0), trigger.source), switchMap(() => fromSubscription(() => cache.readQuery(op.artifact, op.variables), () => cache.subscribeQuery(op.artifact, op.variables, async (event) => {
925
+ if (event === "invalidate") invalidated = true;
884
926
  await Promise.resolve();
885
927
  trigger.next();
886
928
  }))), takeUntil(teardown$), mergeMap((data) => {
887
929
  if (data !== null) {
930
+ if (invalidated && hasData && fetchPolicy !== "cache-only") {
931
+ invalidated = false;
932
+ refetch$.next(op);
933
+ return empty();
934
+ }
888
935
  hasData = true;
936
+ invalidated = false;
889
937
  return fromValue({
890
938
  operation: op,
891
939
  data,
@@ -893,7 +941,8 @@ const cacheExchange = (options = {}) => {
893
941
  });
894
942
  }
895
943
  if (hasData) {
896
- refetch$.next(op);
944
+ if (fetchPolicy !== "cache-only") refetch$.next(op);
945
+ invalidated = false;
897
946
  return empty();
898
947
  }
899
948
  if (fetchPolicy === "cache-only") return fromValue({
@@ -1107,6 +1156,7 @@ const subscriptionExchange = (options) => {
1107
1156
  Promise.resolve().then(() => {
1108
1157
  if (completed) return;
1109
1158
  unsubscribe = client.subscribe({
1159
+ operationName: op.artifact.name,
1110
1160
  query: op.artifact.body,
1111
1161
  variables: op.variables
1112
1162
  }, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mearie/core",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Type-safe, zero-overhead GraphQL client",
5
5
  "keywords": [
6
6
  "graphql",