@mearie/core 0.6.3 → 0.6.5

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
@@ -272,7 +272,10 @@ const dedupExchange = () => {
272
272
  else operations.set(dedupKey, new Set([op.key]));
273
273
  if (!isInflight) resolved.delete(dedupKey);
274
274
  return (op.metadata.dedup?.skip ?? false) || !isInflight;
275
- }), delay(0)), require_make.pipe(ops$, require_make.filter((op) => op.variant === "teardown"), require_make.filter((teardown) => {
275
+ }), delay(0), require_make.filter((op) => {
276
+ const dedupKey = makeDedupKey(op);
277
+ return operations.get(dedupKey)?.has(op.key) ?? false;
278
+ })), require_make.pipe(ops$, require_make.filter((op) => op.variant === "teardown"), require_make.filter((teardown) => {
276
279
  for (const [dedupKey, subs] of operations.entries()) if (subs.delete(teardown.key)) {
277
280
  if (subs.size === 0) {
278
281
  operations.delete(dedupKey);
@@ -347,6 +350,26 @@ const makeEntityKey = (typename, keyValues) => {
347
350
  const resolveArguments = (args, variables) => {
348
351
  return Object.fromEntries(Object.entries(args).map(([key, value]) => [key, value.kind === "literal" ? value.value : variables[value.name]]));
349
352
  };
353
+ const resolveDirectiveValue = (value, variables) => {
354
+ if (value !== null && typeof value === "object" && "kind" in value) {
355
+ const v = value;
356
+ if (v.kind === "variable" && v.name) return variables[v.name];
357
+ if (v.kind === "literal") return v.value;
358
+ }
359
+ return value;
360
+ };
361
+ /**
362
+ * Determines whether a field should be included based on @skip/@include directives.
363
+ * @internal
364
+ */
365
+ const shouldIncludeField = (directives, variables) => {
366
+ if (!directives) return true;
367
+ for (const d of directives) {
368
+ if (d.name === "skip" && resolveDirectiveValue(d.args?.if, variables) === true) return false;
369
+ if (d.name === "include" && resolveDirectiveValue(d.args?.if, variables) === false) return false;
370
+ }
371
+ return true;
372
+ };
350
373
  /**
351
374
  * Generates a cache key for a GraphQL field selection.
352
375
  * Always uses the actual field name (not alias) with a stringified representation of the arguments.
@@ -539,6 +562,13 @@ const resolveTypename = (selections, data) => {
539
562
  return data.__typename;
540
563
  };
541
564
  const normalize = (schemaMeta, selections, storage, data, variables, accessor) => {
565
+ const accessorNotified = accessor ? /* @__PURE__ */ new Map() : void 0;
566
+ const callAccessor = (storageKey, fieldKey, oldValue, newValue) => {
567
+ const key = `${storageKey}\0${fieldKey}`;
568
+ if (accessorNotified.has(key) && isEqual(accessorNotified.get(key), newValue)) return;
569
+ accessorNotified.set(key, newValue);
570
+ accessor(storageKey, fieldKey, oldValue, newValue);
571
+ };
542
572
  const resolveEntityKey = (typename, data) => {
543
573
  if (!typename) return null;
544
574
  const entityMeta = schemaMeta.entities[typename];
@@ -556,6 +586,7 @@ const normalize = (schemaMeta, selections, storage, data, variables, accessor) =
556
586
  if (entityKey) storageKey = entityKey;
557
587
  const fields = {};
558
588
  for (const selection of selections) if (selection.kind === "Field") {
589
+ if (!shouldIncludeField(selection.directives, variables)) continue;
559
590
  const fieldKey = makeFieldKey(selection, variables);
560
591
  let fieldValue = data[selection.alias ?? selection.name];
561
592
  if (selection.name === "__typename" && fieldValue === void 0 && typename) fieldValue = typename;
@@ -564,9 +595,9 @@ const normalize = (schemaMeta, selections, storage, data, variables, accessor) =
564
595
  if (fieldTypename && schemaMeta.entities[fieldTypename] && !resolveEntityKey(fieldTypename, fieldValue) && isEntityLink(storage[storageKey]?.[fieldKey])) continue;
565
596
  }
566
597
  const oldValue = storageKey === null ? void 0 : storage[storageKey]?.[fieldKey];
567
- if (storageKey !== null && (!selection.selections || isNullish(oldValue) || isNullish(fieldValue))) accessor?.(storageKey, fieldKey, oldValue, fieldValue);
598
+ if (storageKey !== null && (!selection.selections || isNullish(oldValue) || isNullish(fieldValue))) callAccessor?.(storageKey, fieldKey, oldValue, fieldValue);
568
599
  fields[fieldKey] = selection.selections ? normalizeField(null, selection.selections, fieldValue, selection.type) : fieldValue;
569
- if (storageKey !== null && selection.selections && !isNullish(oldValue) && !isNullish(fieldValue) && !isEntityLink(fields[fieldKey]) && !isEqual(oldValue, fields[fieldKey])) accessor?.(storageKey, fieldKey, oldValue, fields[fieldKey]);
600
+ if (storageKey !== null && selection.selections && !isNullish(oldValue) && !isNullish(fieldValue) && !isEntityLink(fields[fieldKey]) && !isEqual(oldValue, fields[fieldKey])) callAccessor?.(storageKey, fieldKey, oldValue, fields[fieldKey]);
570
601
  } else if (selection.kind === "FragmentSpread" || selection.kind === "InlineFragment" && selection.on === typename) {
571
602
  const inner = normalizeField(storageKey, selection.selections, value);
572
603
  if (!isEntityLink(inner)) mergeFields(fields, inner);
@@ -581,10 +612,7 @@ const normalize = (schemaMeta, selections, storage, data, variables, accessor) =
581
612
  return fields;
582
613
  };
583
614
  const fields = normalizeField(RootFieldKey, selections, data);
584
- storage[RootFieldKey] = {
585
- ...storage[RootFieldKey],
586
- ...fields
587
- };
615
+ mergeFields(storage[RootFieldKey], fields);
588
616
  };
589
617
 
590
618
  //#endregion
@@ -612,6 +640,7 @@ const denormalize = (selections, storage, value, variables, accessor, options) =
612
640
  }
613
641
  const fields = {};
614
642
  for (const selection of selections) if (selection.kind === "Field") {
643
+ if (!shouldIncludeField(selection.directives, variables)) continue;
615
644
  const fieldKey = makeFieldKey(selection, variables);
616
645
  const fieldValue = data[fieldKey];
617
646
  const fieldPath = [...path, selection.alias ?? selection.name];
@@ -755,6 +784,7 @@ const traceSelections = (selections, storage, value, variables, storageKey, base
755
784
  }
756
785
  const fields = {};
757
786
  for (const selection of sels) if (selection.kind === "Field") {
787
+ if (!shouldIncludeField(selection.directives, variables)) continue;
758
788
  const fieldKey = makeFieldKey(selection, variables);
759
789
  const fieldValue = data[fieldKey];
760
790
  const fieldPath = [...path, selection.alias ?? selection.name];
@@ -946,7 +976,7 @@ const diffSnapshots = (oldData, newData, entityArrayChanges) => {
946
976
  patches.push(...arrayPatches);
947
977
  for (const [i, item] of cur.entries()) {
948
978
  const entityKey = newKeys[i];
949
- diff(entityKey ? oldByKey.get(entityKey) : void 0, item, [...path, i]);
979
+ if (entityKey && oldByKey.has(entityKey)) diff(oldByKey.get(entityKey), item, [...path, i]);
950
980
  }
951
981
  };
952
982
  diff(oldData, newData, []);
@@ -980,7 +1010,7 @@ const classifyChanges = (changes) => {
980
1010
  /**
981
1011
  * @internal
982
1012
  */
983
- const processScalarChanges = (changes, registry, subscriptions) => {
1013
+ const processScalarChanges = (changes, registry, subscriptions, storage) => {
984
1014
  const result = /* @__PURE__ */ new Map();
985
1015
  for (const change of changes) {
986
1016
  const entries = registry.get(change.depKey);
@@ -990,8 +1020,9 @@ const processScalarChanges = (changes, registry, subscriptions) => {
990
1020
  const sub = subscriptions.get(entry.subscriptionId);
991
1021
  if (!sub) continue;
992
1022
  let patchValue = change.newValue;
993
- if (entry.selections && isNormalizedRecord(change.newValue)) {
994
- const { data } = denormalize(entry.selections, {}, change.newValue, sub.variables);
1023
+ if (entry.selections && (isNormalizedRecord(change.newValue) || Array.isArray(change.newValue) && change.newValue.some((v) => isNormalizedRecord(v)))) {
1024
+ const mergedValue = storage[change.storageKey]?.[change.fieldKey] ?? change.newValue;
1025
+ const { data } = denormalize(entry.selections, storage, mergedValue, sub.variables);
995
1026
  patchValue = data;
996
1027
  }
997
1028
  const patches = result.get(entry.subscriptionId) ?? [];
@@ -1567,10 +1598,11 @@ var Cache = class {
1567
1598
  */
1568
1599
  hydrate(snapshot) {
1569
1600
  const { storage } = snapshot;
1570
- for (const [key, fields] of Object.entries(storage)) this.#storage[key] = {
1571
- ...this.#storage[key],
1572
- ...fields
1573
- };
1601
+ for (const [key, fields] of Object.entries(storage)) {
1602
+ const existing = this.#storage[key];
1603
+ if (existing) mergeFields(existing, fields, true);
1604
+ else this.#storage[key] = fields;
1605
+ }
1574
1606
  }
1575
1607
  /**
1576
1608
  * Clears all cache data.
@@ -1586,7 +1618,7 @@ var Cache = class {
1586
1618
  if (changes.length === 0) return;
1587
1619
  const unstalledPatches = this.#checkStalled(changes);
1588
1620
  const { scalar, structural } = classifyChanges(changes);
1589
- const scalarPatches = processScalarChanges(scalar, this.#registry, this.#subscriptions);
1621
+ const scalarPatches = processScalarChanges(scalar, this.#registry, this.#subscriptions, this.#storage);
1590
1622
  const structuralPatches = processStructuralChanges(structural, this.#registry, this.#subscriptions, this.#storage, this.#stalled);
1591
1623
  const allPatches = /* @__PURE__ */ new Map();
1592
1624
  for (const [subId, patches] of unstalledPatches) allPatches.set(subId, patches);
package/dist/index.mjs CHANGED
@@ -271,7 +271,10 @@ const dedupExchange = () => {
271
271
  else operations.set(dedupKey, new Set([op.key]));
272
272
  if (!isInflight) resolved.delete(dedupKey);
273
273
  return (op.metadata.dedup?.skip ?? false) || !isInflight;
274
- }), delay(0)), pipe(ops$, filter((op) => op.variant === "teardown"), filter((teardown) => {
274
+ }), delay(0), filter((op) => {
275
+ const dedupKey = makeDedupKey(op);
276
+ return operations.get(dedupKey)?.has(op.key) ?? false;
277
+ })), pipe(ops$, filter((op) => op.variant === "teardown"), filter((teardown) => {
275
278
  for (const [dedupKey, subs] of operations.entries()) if (subs.delete(teardown.key)) {
276
279
  if (subs.size === 0) {
277
280
  operations.delete(dedupKey);
@@ -346,6 +349,26 @@ const makeEntityKey = (typename, keyValues) => {
346
349
  const resolveArguments = (args, variables) => {
347
350
  return Object.fromEntries(Object.entries(args).map(([key, value]) => [key, value.kind === "literal" ? value.value : variables[value.name]]));
348
351
  };
352
+ const resolveDirectiveValue = (value, variables) => {
353
+ if (value !== null && typeof value === "object" && "kind" in value) {
354
+ const v = value;
355
+ if (v.kind === "variable" && v.name) return variables[v.name];
356
+ if (v.kind === "literal") return v.value;
357
+ }
358
+ return value;
359
+ };
360
+ /**
361
+ * Determines whether a field should be included based on @skip/@include directives.
362
+ * @internal
363
+ */
364
+ const shouldIncludeField = (directives, variables) => {
365
+ if (!directives) return true;
366
+ for (const d of directives) {
367
+ if (d.name === "skip" && resolveDirectiveValue(d.args?.if, variables) === true) return false;
368
+ if (d.name === "include" && resolveDirectiveValue(d.args?.if, variables) === false) return false;
369
+ }
370
+ return true;
371
+ };
349
372
  /**
350
373
  * Generates a cache key for a GraphQL field selection.
351
374
  * Always uses the actual field name (not alias) with a stringified representation of the arguments.
@@ -538,6 +561,13 @@ const resolveTypename = (selections, data) => {
538
561
  return data.__typename;
539
562
  };
540
563
  const normalize = (schemaMeta, selections, storage, data, variables, accessor) => {
564
+ const accessorNotified = accessor ? /* @__PURE__ */ new Map() : void 0;
565
+ const callAccessor = (storageKey, fieldKey, oldValue, newValue) => {
566
+ const key = `${storageKey}\0${fieldKey}`;
567
+ if (accessorNotified.has(key) && isEqual(accessorNotified.get(key), newValue)) return;
568
+ accessorNotified.set(key, newValue);
569
+ accessor(storageKey, fieldKey, oldValue, newValue);
570
+ };
541
571
  const resolveEntityKey = (typename, data) => {
542
572
  if (!typename) return null;
543
573
  const entityMeta = schemaMeta.entities[typename];
@@ -555,6 +585,7 @@ const normalize = (schemaMeta, selections, storage, data, variables, accessor) =
555
585
  if (entityKey) storageKey = entityKey;
556
586
  const fields = {};
557
587
  for (const selection of selections) if (selection.kind === "Field") {
588
+ if (!shouldIncludeField(selection.directives, variables)) continue;
558
589
  const fieldKey = makeFieldKey(selection, variables);
559
590
  let fieldValue = data[selection.alias ?? selection.name];
560
591
  if (selection.name === "__typename" && fieldValue === void 0 && typename) fieldValue = typename;
@@ -563,9 +594,9 @@ const normalize = (schemaMeta, selections, storage, data, variables, accessor) =
563
594
  if (fieldTypename && schemaMeta.entities[fieldTypename] && !resolveEntityKey(fieldTypename, fieldValue) && isEntityLink(storage[storageKey]?.[fieldKey])) continue;
564
595
  }
565
596
  const oldValue = storageKey === null ? void 0 : storage[storageKey]?.[fieldKey];
566
- if (storageKey !== null && (!selection.selections || isNullish(oldValue) || isNullish(fieldValue))) accessor?.(storageKey, fieldKey, oldValue, fieldValue);
597
+ if (storageKey !== null && (!selection.selections || isNullish(oldValue) || isNullish(fieldValue))) callAccessor?.(storageKey, fieldKey, oldValue, fieldValue);
567
598
  fields[fieldKey] = selection.selections ? normalizeField(null, selection.selections, fieldValue, selection.type) : fieldValue;
568
- if (storageKey !== null && selection.selections && !isNullish(oldValue) && !isNullish(fieldValue) && !isEntityLink(fields[fieldKey]) && !isEqual(oldValue, fields[fieldKey])) accessor?.(storageKey, fieldKey, oldValue, fields[fieldKey]);
599
+ if (storageKey !== null && selection.selections && !isNullish(oldValue) && !isNullish(fieldValue) && !isEntityLink(fields[fieldKey]) && !isEqual(oldValue, fields[fieldKey])) callAccessor?.(storageKey, fieldKey, oldValue, fields[fieldKey]);
569
600
  } else if (selection.kind === "FragmentSpread" || selection.kind === "InlineFragment" && selection.on === typename) {
570
601
  const inner = normalizeField(storageKey, selection.selections, value);
571
602
  if (!isEntityLink(inner)) mergeFields(fields, inner);
@@ -580,10 +611,7 @@ const normalize = (schemaMeta, selections, storage, data, variables, accessor) =
580
611
  return fields;
581
612
  };
582
613
  const fields = normalizeField(RootFieldKey, selections, data);
583
- storage[RootFieldKey] = {
584
- ...storage[RootFieldKey],
585
- ...fields
586
- };
614
+ mergeFields(storage[RootFieldKey], fields);
587
615
  };
588
616
 
589
617
  //#endregion
@@ -611,6 +639,7 @@ const denormalize = (selections, storage, value, variables, accessor, options) =
611
639
  }
612
640
  const fields = {};
613
641
  for (const selection of selections) if (selection.kind === "Field") {
642
+ if (!shouldIncludeField(selection.directives, variables)) continue;
614
643
  const fieldKey = makeFieldKey(selection, variables);
615
644
  const fieldValue = data[fieldKey];
616
645
  const fieldPath = [...path, selection.alias ?? selection.name];
@@ -754,6 +783,7 @@ const traceSelections = (selections, storage, value, variables, storageKey, base
754
783
  }
755
784
  const fields = {};
756
785
  for (const selection of sels) if (selection.kind === "Field") {
786
+ if (!shouldIncludeField(selection.directives, variables)) continue;
757
787
  const fieldKey = makeFieldKey(selection, variables);
758
788
  const fieldValue = data[fieldKey];
759
789
  const fieldPath = [...path, selection.alias ?? selection.name];
@@ -945,7 +975,7 @@ const diffSnapshots = (oldData, newData, entityArrayChanges) => {
945
975
  patches.push(...arrayPatches);
946
976
  for (const [i, item] of cur.entries()) {
947
977
  const entityKey = newKeys[i];
948
- diff(entityKey ? oldByKey.get(entityKey) : void 0, item, [...path, i]);
978
+ if (entityKey && oldByKey.has(entityKey)) diff(oldByKey.get(entityKey), item, [...path, i]);
949
979
  }
950
980
  };
951
981
  diff(oldData, newData, []);
@@ -979,7 +1009,7 @@ const classifyChanges = (changes) => {
979
1009
  /**
980
1010
  * @internal
981
1011
  */
982
- const processScalarChanges = (changes, registry, subscriptions) => {
1012
+ const processScalarChanges = (changes, registry, subscriptions, storage) => {
983
1013
  const result = /* @__PURE__ */ new Map();
984
1014
  for (const change of changes) {
985
1015
  const entries = registry.get(change.depKey);
@@ -989,8 +1019,9 @@ const processScalarChanges = (changes, registry, subscriptions) => {
989
1019
  const sub = subscriptions.get(entry.subscriptionId);
990
1020
  if (!sub) continue;
991
1021
  let patchValue = change.newValue;
992
- if (entry.selections && isNormalizedRecord(change.newValue)) {
993
- const { data } = denormalize(entry.selections, {}, change.newValue, sub.variables);
1022
+ if (entry.selections && (isNormalizedRecord(change.newValue) || Array.isArray(change.newValue) && change.newValue.some((v) => isNormalizedRecord(v)))) {
1023
+ const mergedValue = storage[change.storageKey]?.[change.fieldKey] ?? change.newValue;
1024
+ const { data } = denormalize(entry.selections, storage, mergedValue, sub.variables);
994
1025
  patchValue = data;
995
1026
  }
996
1027
  const patches = result.get(entry.subscriptionId) ?? [];
@@ -1566,10 +1597,11 @@ var Cache = class {
1566
1597
  */
1567
1598
  hydrate(snapshot) {
1568
1599
  const { storage } = snapshot;
1569
- for (const [key, fields] of Object.entries(storage)) this.#storage[key] = {
1570
- ...this.#storage[key],
1571
- ...fields
1572
- };
1600
+ for (const [key, fields] of Object.entries(storage)) {
1601
+ const existing = this.#storage[key];
1602
+ if (existing) mergeFields(existing, fields, true);
1603
+ else this.#storage[key] = fields;
1604
+ }
1573
1605
  }
1574
1606
  /**
1575
1607
  * Clears all cache data.
@@ -1585,7 +1617,7 @@ var Cache = class {
1585
1617
  if (changes.length === 0) return;
1586
1618
  const unstalledPatches = this.#checkStalled(changes);
1587
1619
  const { scalar, structural } = classifyChanges(changes);
1588
- const scalarPatches = processScalarChanges(scalar, this.#registry, this.#subscriptions);
1620
+ const scalarPatches = processScalarChanges(scalar, this.#registry, this.#subscriptions, this.#storage);
1589
1621
  const structuralPatches = processStructuralChanges(structural, this.#registry, this.#subscriptions, this.#storage, this.#stalled);
1590
1622
  const allPatches = /* @__PURE__ */ new Map();
1591
1623
  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.3",
3
+ "version": "0.6.5",
4
4
  "description": "Type-safe, zero-overhead GraphQL client",
5
5
  "keywords": [
6
6
  "graphql",