@mearie/core 0.6.2 → 0.6.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
@@ -347,6 +347,26 @@ const makeEntityKey = (typename, keyValues) => {
347
347
  const resolveArguments = (args, variables) => {
348
348
  return Object.fromEntries(Object.entries(args).map(([key, value]) => [key, value.kind === "literal" ? value.value : variables[value.name]]));
349
349
  };
350
+ const resolveDirectiveValue = (value, variables) => {
351
+ if (value !== null && typeof value === "object" && "kind" in value) {
352
+ const v = value;
353
+ if (v.kind === "variable" && v.name) return variables[v.name];
354
+ if (v.kind === "literal") return v.value;
355
+ }
356
+ return value;
357
+ };
358
+ /**
359
+ * Determines whether a field should be included based on @skip/@include directives.
360
+ * @internal
361
+ */
362
+ const shouldIncludeField = (directives, variables) => {
363
+ if (!directives) return true;
364
+ for (const d of directives) {
365
+ if (d.name === "skip" && resolveDirectiveValue(d.args?.if, variables) === true) return false;
366
+ if (d.name === "include" && resolveDirectiveValue(d.args?.if, variables) === false) return false;
367
+ }
368
+ return true;
369
+ };
350
370
  /**
351
371
  * Generates a cache key for a GraphQL field selection.
352
372
  * Always uses the actual field name (not alias) with a stringified representation of the arguments.
@@ -556,6 +576,7 @@ const normalize = (schemaMeta, selections, storage, data, variables, accessor) =
556
576
  if (entityKey) storageKey = entityKey;
557
577
  const fields = {};
558
578
  for (const selection of selections) if (selection.kind === "Field") {
579
+ if (!shouldIncludeField(selection.directives, variables)) continue;
559
580
  const fieldKey = makeFieldKey(selection, variables);
560
581
  let fieldValue = data[selection.alias ?? selection.name];
561
582
  if (selection.name === "__typename" && fieldValue === void 0 && typename) fieldValue = typename;
@@ -612,6 +633,7 @@ const denormalize = (selections, storage, value, variables, accessor, options) =
612
633
  }
613
634
  const fields = {};
614
635
  for (const selection of selections) if (selection.kind === "Field") {
636
+ if (!shouldIncludeField(selection.directives, variables)) continue;
615
637
  const fieldKey = makeFieldKey(selection, variables);
616
638
  const fieldValue = data[fieldKey];
617
639
  const fieldPath = [...path, selection.alias ?? selection.name];
@@ -713,6 +735,29 @@ const traceSelections = (selections, storage, value, variables, storageKey, base
713
735
  const cursors = [];
714
736
  const missingDeps = /* @__PURE__ */ new Set();
715
737
  let complete = true;
738
+ const traceFragmentSpread = (selection, refKey, value, traceKey, fields, path) => {
739
+ fields[FragmentRefKey] = refKey;
740
+ const merged = selection.args ? {
741
+ ...variables,
742
+ ...resolveArguments(selection.args, variables)
743
+ } : { ...variables };
744
+ fields[FragmentVarsKey] = {
745
+ ...fields[FragmentVarsKey],
746
+ [selection.name]: merged
747
+ };
748
+ const inner = traceSelections(selection.selections, storage, value, variables, traceKey, path, subscriptionId);
749
+ for (const cursor of inner.cursors) cursors.push({
750
+ depKey: cursor.depKey,
751
+ entry: cursor.entry.dependency === "transitive" ? cursor.entry : {
752
+ ...cursor.entry,
753
+ dependency: "transitive"
754
+ }
755
+ });
756
+ if (!inner.complete) {
757
+ complete = false;
758
+ for (const dep of inner.missingDeps) missingDeps.add(dep);
759
+ }
760
+ };
716
761
  const traceField = (sk, sels, val, path, trackCursors) => {
717
762
  if (isNullish(val)) return val;
718
763
  if (Array.isArray(val)) return val.map((item, i) => traceField(sk, sels, item, [...path, i], trackCursors));
@@ -732,6 +777,7 @@ const traceSelections = (selections, storage, value, variables, storageKey, base
732
777
  }
733
778
  const fields = {};
734
779
  for (const selection of sels) if (selection.kind === "Field") {
780
+ if (!shouldIncludeField(selection.directives, variables)) continue;
735
781
  const fieldKey = makeFieldKey(selection, variables);
736
782
  const fieldValue = data[fieldKey];
737
783
  const fieldPath = [...path, selection.alias ?? selection.name];
@@ -740,6 +786,7 @@ const traceSelections = (selections, storage, value, variables, storageKey, base
740
786
  const entry = {
741
787
  subscriptionId,
742
788
  path: fieldPath,
789
+ dependency: "direct",
743
790
  ...selection.selections && { selections: selection.selections }
744
791
  };
745
792
  cursors.push({
@@ -759,37 +806,9 @@ const traceSelections = (selections, storage, value, variables, storageKey, base
759
806
  const resolvedValue = selection.selections ? traceField(null, selection.selections, fieldValue, fieldPath, trackCursors) : fieldValue;
760
807
  if (name in fields) mergeFields(fields, { [name]: resolvedValue }, true);
761
808
  else fields[name] = resolvedValue;
762
- } else if (selection.kind === "FragmentSpread") if (sk !== null && sk !== RootFieldKey) {
763
- fields[FragmentRefKey] = sk;
764
- const merged = selection.args ? {
765
- ...variables,
766
- ...resolveArguments(selection.args, variables)
767
- } : { ...variables };
768
- fields[FragmentVarsKey] = {
769
- ...fields[FragmentVarsKey],
770
- [selection.name]: merged
771
- };
772
- const inner = traceSelections(selection.selections, storage, storage[sk], variables, sk, path, subscriptionId);
773
- if (!inner.complete) {
774
- complete = false;
775
- for (const dep of inner.missingDeps) missingDeps.add(dep);
776
- }
777
- } else if (sk === RootFieldKey) {
778
- fields[FragmentRefKey] = RootFieldKey;
779
- const merged = selection.args ? {
780
- ...variables,
781
- ...resolveArguments(selection.args, variables)
782
- } : { ...variables };
783
- fields[FragmentVarsKey] = {
784
- ...fields[FragmentVarsKey],
785
- [selection.name]: merged
786
- };
787
- const inner = traceSelections(selection.selections, storage, storage[RootFieldKey], variables, RootFieldKey, path, subscriptionId);
788
- if (!inner.complete) {
789
- complete = false;
790
- for (const dep of inner.missingDeps) missingDeps.add(dep);
791
- }
792
- } else mergeFields(fields, traceField(sk, selection.selections, val, path, trackCursors), true);
809
+ } else if (selection.kind === "FragmentSpread") if (sk !== null && sk !== RootFieldKey) traceFragmentSpread(selection, sk, storage[sk], sk, fields, path);
810
+ else if (sk === RootFieldKey) traceFragmentSpread(selection, RootFieldKey, storage[RootFieldKey], RootFieldKey, fields, path);
811
+ else mergeFields(fields, traceField(sk, selection.selections, val, path, trackCursors), true);
793
812
  else if (selection.kind === "InlineFragment" && selection.on === data[typenameFieldKey]) mergeFields(fields, traceField(sk, selection.selections, val, path, trackCursors), true);
794
813
  return fields;
795
814
  };
@@ -984,17 +1003,19 @@ const classifyChanges = (changes) => {
984
1003
  /**
985
1004
  * @internal
986
1005
  */
987
- const processScalarChanges = (changes, registry, subscriptions) => {
1006
+ const processScalarChanges = (changes, registry, subscriptions, storage) => {
988
1007
  const result = /* @__PURE__ */ new Map();
989
1008
  for (const change of changes) {
990
1009
  const entries = registry.get(change.depKey);
991
1010
  if (!entries) continue;
992
1011
  for (const entry of entries) {
1012
+ if (entry.dependency === "transitive") continue;
993
1013
  const sub = subscriptions.get(entry.subscriptionId);
994
1014
  if (!sub) continue;
995
1015
  let patchValue = change.newValue;
996
- if (entry.selections && isNormalizedRecord(change.newValue)) {
997
- const { data } = denormalize(entry.selections, {}, change.newValue, sub.variables);
1016
+ if (entry.selections && (isNormalizedRecord(change.newValue) || Array.isArray(change.newValue) && change.newValue.some((v) => isNormalizedRecord(v)))) {
1017
+ const mergedValue = storage[change.storageKey]?.[change.fieldKey] ?? change.newValue;
1018
+ const { data } = denormalize(entry.selections, {}, mergedValue, sub.variables);
998
1019
  patchValue = data;
999
1020
  }
1000
1021
  const patches = result.get(entry.subscriptionId) ?? [];
@@ -1589,7 +1610,7 @@ var Cache = class {
1589
1610
  if (changes.length === 0) return;
1590
1611
  const unstalledPatches = this.#checkStalled(changes);
1591
1612
  const { scalar, structural } = classifyChanges(changes);
1592
- const scalarPatches = processScalarChanges(scalar, this.#registry, this.#subscriptions);
1613
+ const scalarPatches = processScalarChanges(scalar, this.#registry, this.#subscriptions, this.#storage);
1593
1614
  const structuralPatches = processStructuralChanges(structural, this.#registry, this.#subscriptions, this.#storage, this.#stalled);
1594
1615
  const allPatches = /* @__PURE__ */ new Map();
1595
1616
  for (const [subId, patches] of unstalledPatches) allPatches.set(subId, patches);
package/dist/index.mjs CHANGED
@@ -346,6 +346,26 @@ const makeEntityKey = (typename, keyValues) => {
346
346
  const resolveArguments = (args, variables) => {
347
347
  return Object.fromEntries(Object.entries(args).map(([key, value]) => [key, value.kind === "literal" ? value.value : variables[value.name]]));
348
348
  };
349
+ const resolveDirectiveValue = (value, variables) => {
350
+ if (value !== null && typeof value === "object" && "kind" in value) {
351
+ const v = value;
352
+ if (v.kind === "variable" && v.name) return variables[v.name];
353
+ if (v.kind === "literal") return v.value;
354
+ }
355
+ return value;
356
+ };
357
+ /**
358
+ * Determines whether a field should be included based on @skip/@include directives.
359
+ * @internal
360
+ */
361
+ const shouldIncludeField = (directives, variables) => {
362
+ if (!directives) return true;
363
+ for (const d of directives) {
364
+ if (d.name === "skip" && resolveDirectiveValue(d.args?.if, variables) === true) return false;
365
+ if (d.name === "include" && resolveDirectiveValue(d.args?.if, variables) === false) return false;
366
+ }
367
+ return true;
368
+ };
349
369
  /**
350
370
  * Generates a cache key for a GraphQL field selection.
351
371
  * Always uses the actual field name (not alias) with a stringified representation of the arguments.
@@ -555,6 +575,7 @@ const normalize = (schemaMeta, selections, storage, data, variables, accessor) =
555
575
  if (entityKey) storageKey = entityKey;
556
576
  const fields = {};
557
577
  for (const selection of selections) if (selection.kind === "Field") {
578
+ if (!shouldIncludeField(selection.directives, variables)) continue;
558
579
  const fieldKey = makeFieldKey(selection, variables);
559
580
  let fieldValue = data[selection.alias ?? selection.name];
560
581
  if (selection.name === "__typename" && fieldValue === void 0 && typename) fieldValue = typename;
@@ -611,6 +632,7 @@ const denormalize = (selections, storage, value, variables, accessor, options) =
611
632
  }
612
633
  const fields = {};
613
634
  for (const selection of selections) if (selection.kind === "Field") {
635
+ if (!shouldIncludeField(selection.directives, variables)) continue;
614
636
  const fieldKey = makeFieldKey(selection, variables);
615
637
  const fieldValue = data[fieldKey];
616
638
  const fieldPath = [...path, selection.alias ?? selection.name];
@@ -712,6 +734,29 @@ const traceSelections = (selections, storage, value, variables, storageKey, base
712
734
  const cursors = [];
713
735
  const missingDeps = /* @__PURE__ */ new Set();
714
736
  let complete = true;
737
+ const traceFragmentSpread = (selection, refKey, value, traceKey, fields, path) => {
738
+ fields[FragmentRefKey] = refKey;
739
+ const merged = selection.args ? {
740
+ ...variables,
741
+ ...resolveArguments(selection.args, variables)
742
+ } : { ...variables };
743
+ fields[FragmentVarsKey] = {
744
+ ...fields[FragmentVarsKey],
745
+ [selection.name]: merged
746
+ };
747
+ const inner = traceSelections(selection.selections, storage, value, variables, traceKey, path, subscriptionId);
748
+ for (const cursor of inner.cursors) cursors.push({
749
+ depKey: cursor.depKey,
750
+ entry: cursor.entry.dependency === "transitive" ? cursor.entry : {
751
+ ...cursor.entry,
752
+ dependency: "transitive"
753
+ }
754
+ });
755
+ if (!inner.complete) {
756
+ complete = false;
757
+ for (const dep of inner.missingDeps) missingDeps.add(dep);
758
+ }
759
+ };
715
760
  const traceField = (sk, sels, val, path, trackCursors) => {
716
761
  if (isNullish(val)) return val;
717
762
  if (Array.isArray(val)) return val.map((item, i) => traceField(sk, sels, item, [...path, i], trackCursors));
@@ -731,6 +776,7 @@ const traceSelections = (selections, storage, value, variables, storageKey, base
731
776
  }
732
777
  const fields = {};
733
778
  for (const selection of sels) if (selection.kind === "Field") {
779
+ if (!shouldIncludeField(selection.directives, variables)) continue;
734
780
  const fieldKey = makeFieldKey(selection, variables);
735
781
  const fieldValue = data[fieldKey];
736
782
  const fieldPath = [...path, selection.alias ?? selection.name];
@@ -739,6 +785,7 @@ const traceSelections = (selections, storage, value, variables, storageKey, base
739
785
  const entry = {
740
786
  subscriptionId,
741
787
  path: fieldPath,
788
+ dependency: "direct",
742
789
  ...selection.selections && { selections: selection.selections }
743
790
  };
744
791
  cursors.push({
@@ -758,37 +805,9 @@ const traceSelections = (selections, storage, value, variables, storageKey, base
758
805
  const resolvedValue = selection.selections ? traceField(null, selection.selections, fieldValue, fieldPath, trackCursors) : fieldValue;
759
806
  if (name in fields) mergeFields(fields, { [name]: resolvedValue }, true);
760
807
  else fields[name] = resolvedValue;
761
- } else if (selection.kind === "FragmentSpread") if (sk !== null && sk !== RootFieldKey) {
762
- fields[FragmentRefKey] = sk;
763
- const merged = selection.args ? {
764
- ...variables,
765
- ...resolveArguments(selection.args, variables)
766
- } : { ...variables };
767
- fields[FragmentVarsKey] = {
768
- ...fields[FragmentVarsKey],
769
- [selection.name]: merged
770
- };
771
- const inner = traceSelections(selection.selections, storage, storage[sk], variables, sk, path, subscriptionId);
772
- if (!inner.complete) {
773
- complete = false;
774
- for (const dep of inner.missingDeps) missingDeps.add(dep);
775
- }
776
- } else if (sk === RootFieldKey) {
777
- fields[FragmentRefKey] = RootFieldKey;
778
- const merged = selection.args ? {
779
- ...variables,
780
- ...resolveArguments(selection.args, variables)
781
- } : { ...variables };
782
- fields[FragmentVarsKey] = {
783
- ...fields[FragmentVarsKey],
784
- [selection.name]: merged
785
- };
786
- const inner = traceSelections(selection.selections, storage, storage[RootFieldKey], variables, RootFieldKey, path, subscriptionId);
787
- if (!inner.complete) {
788
- complete = false;
789
- for (const dep of inner.missingDeps) missingDeps.add(dep);
790
- }
791
- } else mergeFields(fields, traceField(sk, selection.selections, val, path, trackCursors), true);
808
+ } else if (selection.kind === "FragmentSpread") if (sk !== null && sk !== RootFieldKey) traceFragmentSpread(selection, sk, storage[sk], sk, fields, path);
809
+ else if (sk === RootFieldKey) traceFragmentSpread(selection, RootFieldKey, storage[RootFieldKey], RootFieldKey, fields, path);
810
+ else mergeFields(fields, traceField(sk, selection.selections, val, path, trackCursors), true);
792
811
  else if (selection.kind === "InlineFragment" && selection.on === data[typenameFieldKey]) mergeFields(fields, traceField(sk, selection.selections, val, path, trackCursors), true);
793
812
  return fields;
794
813
  };
@@ -983,17 +1002,19 @@ const classifyChanges = (changes) => {
983
1002
  /**
984
1003
  * @internal
985
1004
  */
986
- const processScalarChanges = (changes, registry, subscriptions) => {
1005
+ const processScalarChanges = (changes, registry, subscriptions, storage) => {
987
1006
  const result = /* @__PURE__ */ new Map();
988
1007
  for (const change of changes) {
989
1008
  const entries = registry.get(change.depKey);
990
1009
  if (!entries) continue;
991
1010
  for (const entry of entries) {
1011
+ if (entry.dependency === "transitive") continue;
992
1012
  const sub = subscriptions.get(entry.subscriptionId);
993
1013
  if (!sub) continue;
994
1014
  let patchValue = change.newValue;
995
- if (entry.selections && isNormalizedRecord(change.newValue)) {
996
- const { data } = denormalize(entry.selections, {}, change.newValue, sub.variables);
1015
+ if (entry.selections && (isNormalizedRecord(change.newValue) || Array.isArray(change.newValue) && change.newValue.some((v) => isNormalizedRecord(v)))) {
1016
+ const mergedValue = storage[change.storageKey]?.[change.fieldKey] ?? change.newValue;
1017
+ const { data } = denormalize(entry.selections, {}, mergedValue, sub.variables);
997
1018
  patchValue = data;
998
1019
  }
999
1020
  const patches = result.get(entry.subscriptionId) ?? [];
@@ -1588,7 +1609,7 @@ var Cache = class {
1588
1609
  if (changes.length === 0) return;
1589
1610
  const unstalledPatches = this.#checkStalled(changes);
1590
1611
  const { scalar, structural } = classifyChanges(changes);
1591
- const scalarPatches = processScalarChanges(scalar, this.#registry, this.#subscriptions);
1612
+ const scalarPatches = processScalarChanges(scalar, this.#registry, this.#subscriptions, this.#storage);
1592
1613
  const structuralPatches = processStructuralChanges(structural, this.#registry, this.#subscriptions, this.#storage, this.#stalled);
1593
1614
  const allPatches = /* @__PURE__ */ new Map();
1594
1615
  for (const [subId, patches] of unstalledPatches) allPatches.set(subId, patches);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mearie/core",
3
- "version": "0.6.2",
3
+ "version": "0.6.4",
4
4
  "description": "Type-safe, zero-overhead GraphQL client",
5
5
  "keywords": [
6
6
  "graphql",