@mearie/core 0.5.1 → 0.5.2
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 +40 -18
- package/dist/index.mjs +40 -18
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -482,28 +482,48 @@ const replaceEqualDeep = (prev, next) => {
|
|
|
482
482
|
}
|
|
483
483
|
return allSame ? prev : result;
|
|
484
484
|
};
|
|
485
|
+
const NormalizedKey = Symbol("mearie.normalized");
|
|
485
486
|
/**
|
|
486
|
-
*
|
|
487
|
-
*
|
|
487
|
+
* Marks a record as a normalized cache object so that {@link mergeFields}
|
|
488
|
+
* can distinguish it from opaque scalar values (e.g. JSON scalars).
|
|
489
|
+
* Only normalized records are deep-merged; unmarked objects are treated as
|
|
490
|
+
* atomic values and replaced entirely on write.
|
|
488
491
|
* @internal
|
|
489
492
|
*/
|
|
490
|
-
const
|
|
493
|
+
const markNormalized = (obj) => {
|
|
494
|
+
Object.defineProperty(obj, NormalizedKey, { value: true });
|
|
495
|
+
};
|
|
496
|
+
const isNormalizedRecord = (value) => {
|
|
497
|
+
return typeof value === "object" && value !== null && NormalizedKey in value;
|
|
498
|
+
};
|
|
499
|
+
/**
|
|
500
|
+
* Deeply merges two values. When {@link deep} is false (default), only
|
|
501
|
+
* {@link markNormalized normalized} cache objects are recursively merged;
|
|
502
|
+
* unmarked plain objects (e.g. JSON scalars) are atomically replaced.
|
|
503
|
+
* When {@link deep} is true, all objects are recursively merged unconditionally.
|
|
504
|
+
* Arrays are element-wise merged, entity links and primitives use last-write-wins.
|
|
505
|
+
* @internal
|
|
506
|
+
*/
|
|
507
|
+
const mergeFieldValue = (existing, incoming, deep) => {
|
|
491
508
|
if (isNullish(existing) || isNullish(incoming)) return incoming;
|
|
492
509
|
if (typeof existing !== "object" || typeof incoming !== "object") return incoming;
|
|
493
510
|
if (isEntityLink(existing) || isEntityLink(incoming)) return incoming;
|
|
494
|
-
if (Array.isArray(existing) && Array.isArray(incoming)) return incoming.map((item, i) => i < existing.length ? mergeFieldValue(existing[i], item) : item);
|
|
511
|
+
if (Array.isArray(existing) && Array.isArray(incoming)) return incoming.map((item, i) => i < existing.length ? mergeFieldValue(existing[i], item, deep) : item);
|
|
495
512
|
if (Array.isArray(existing) || Array.isArray(incoming)) return incoming;
|
|
496
|
-
|
|
513
|
+
if (!deep && !isNormalizedRecord(incoming)) return incoming;
|
|
514
|
+
mergeFields(existing, incoming, deep);
|
|
497
515
|
return existing;
|
|
498
516
|
};
|
|
499
517
|
/**
|
|
500
|
-
* Deeply merges source fields into target.
|
|
501
|
-
*
|
|
518
|
+
* Deeply merges source fields into target.
|
|
519
|
+
* When {@link deep} is false (default), only {@link markNormalized normalized}
|
|
520
|
+
* objects are recursively merged; unmarked objects are atomically replaced.
|
|
521
|
+
* When {@link deep} is true, all objects are recursively merged unconditionally.
|
|
502
522
|
* @internal
|
|
503
523
|
*/
|
|
504
|
-
const mergeFields = (target, source) => {
|
|
524
|
+
const mergeFields = (target, source, deep) => {
|
|
505
525
|
if (isNullish(source) || typeof source !== "object" || Array.isArray(source)) return;
|
|
506
|
-
for (const key of Object.keys(source)) target[key] = mergeFieldValue(target[key], source[key]);
|
|
526
|
+
for (const key of Object.keys(source)) target[key] = mergeFieldValue(target[key], source[key], deep ?? false);
|
|
507
527
|
};
|
|
508
528
|
/**
|
|
509
529
|
* Creates a FieldKey from a raw field name and optional arguments.
|
|
@@ -531,29 +551,31 @@ const normalize = (schemaMeta, selections, storage, data, variables, accessor) =
|
|
|
531
551
|
if (keys.every((k) => k !== void 0 && k !== null)) return makeEntityKey(typename, keys);
|
|
532
552
|
return null;
|
|
533
553
|
};
|
|
534
|
-
const normalizeField = (storageKey, selections, value) => {
|
|
554
|
+
const normalizeField = (storageKey, selections, value, parentType) => {
|
|
535
555
|
if (isNullish(value)) return value;
|
|
536
|
-
if (Array.isArray(value)) return value.map((item) => normalizeField(storageKey, selections, item));
|
|
556
|
+
if (Array.isArray(value)) return value.map((item) => normalizeField(storageKey, selections, item, parentType));
|
|
537
557
|
const data = value;
|
|
538
|
-
const typename = resolveTypename(selections, data);
|
|
558
|
+
const typename = resolveTypename(selections, data) ?? (parentType && schemaMeta.entities[parentType] ? parentType : void 0);
|
|
539
559
|
const entityKey = resolveEntityKey(typename, data);
|
|
540
560
|
if (entityKey) storageKey = entityKey;
|
|
541
561
|
const fields = {};
|
|
542
562
|
for (const selection of selections) if (selection.kind === "Field") {
|
|
543
563
|
const fieldKey = makeFieldKey(selection, variables);
|
|
544
|
-
|
|
564
|
+
let fieldValue = data[selection.alias ?? selection.name];
|
|
565
|
+
if (selection.name === "__typename" && fieldValue === void 0 && typename) fieldValue = typename;
|
|
545
566
|
if (storageKey !== null && selection.selections && typeof fieldValue === "object" && fieldValue !== null && !Array.isArray(fieldValue)) {
|
|
546
|
-
const fieldTypename = resolveTypename(selection.selections, fieldValue);
|
|
567
|
+
const fieldTypename = resolveTypename(selection.selections, fieldValue) ?? (selection.type && schemaMeta.entities[selection.type] ? selection.type : void 0);
|
|
547
568
|
if (fieldTypename && schemaMeta.entities[fieldTypename] && !resolveEntityKey(fieldTypename, fieldValue) && isEntityLink(storage[storageKey]?.[fieldKey])) continue;
|
|
548
569
|
}
|
|
549
570
|
const oldValue = storageKey === null ? void 0 : storage[storageKey]?.[fieldKey];
|
|
550
571
|
if (storageKey !== null && (!selection.selections || isNullish(oldValue) || isNullish(fieldValue))) accessor?.(storageKey, fieldKey, oldValue, fieldValue);
|
|
551
|
-
fields[fieldKey] = selection.selections ? normalizeField(null, selection.selections, fieldValue) : fieldValue;
|
|
572
|
+
fields[fieldKey] = selection.selections ? normalizeField(null, selection.selections, fieldValue, selection.type) : fieldValue;
|
|
552
573
|
if (storageKey !== null && selection.selections && !isNullish(oldValue) && !isNullish(fieldValue) && !isEntityLink(fields[fieldKey]) && !isEqual(oldValue, fields[fieldKey])) accessor?.(storageKey, fieldKey, oldValue, fields[fieldKey]);
|
|
553
574
|
} else if (selection.kind === "FragmentSpread" || selection.kind === "InlineFragment" && selection.on === typename) {
|
|
554
575
|
const inner = normalizeField(storageKey, selection.selections, value);
|
|
555
576
|
if (!isEntityLink(inner)) mergeFields(fields, inner);
|
|
556
577
|
}
|
|
578
|
+
markNormalized(fields);
|
|
557
579
|
if (entityKey) {
|
|
558
580
|
const existing = storage[entityKey];
|
|
559
581
|
if (existing) mergeFields(existing, fields);
|
|
@@ -603,7 +625,7 @@ const denormalize = (selections, storage, value, variables, accessor) => {
|
|
|
603
625
|
}
|
|
604
626
|
const name = selection.alias ?? selection.name;
|
|
605
627
|
const value = selection.selections ? denormalizeField(null, selection.selections, fieldValue) : fieldValue;
|
|
606
|
-
if (name in fields) mergeFields(fields, { [name]: value });
|
|
628
|
+
if (name in fields) mergeFields(fields, { [name]: value }, true);
|
|
607
629
|
else fields[name] = value;
|
|
608
630
|
} else if (selection.kind === "FragmentSpread") if (storageKey !== null && storageKey !== RootFieldKey) {
|
|
609
631
|
fields[FragmentRefKey] = storageKey;
|
|
@@ -619,8 +641,8 @@ const denormalize = (selections, storage, value, variables, accessor) => {
|
|
|
619
641
|
};
|
|
620
642
|
}
|
|
621
643
|
if (accessor) denormalize(selection.selections, storage, { [EntityLinkKey]: storageKey }, variables, accessor);
|
|
622
|
-
} else mergeFields(fields, denormalizeField(storageKey, selection.selections, value));
|
|
623
|
-
else if (selection.kind === "InlineFragment" && selection.on === data[typenameFieldKey]) mergeFields(fields, denormalizeField(storageKey, selection.selections, value));
|
|
644
|
+
} else mergeFields(fields, denormalizeField(storageKey, selection.selections, value), true);
|
|
645
|
+
else if (selection.kind === "InlineFragment" && selection.on === data[typenameFieldKey]) mergeFields(fields, denormalizeField(storageKey, selection.selections, value), true);
|
|
624
646
|
return fields;
|
|
625
647
|
};
|
|
626
648
|
return {
|
package/dist/index.mjs
CHANGED
|
@@ -481,28 +481,48 @@ const replaceEqualDeep = (prev, next) => {
|
|
|
481
481
|
}
|
|
482
482
|
return allSame ? prev : result;
|
|
483
483
|
};
|
|
484
|
+
const NormalizedKey = Symbol("mearie.normalized");
|
|
484
485
|
/**
|
|
485
|
-
*
|
|
486
|
-
*
|
|
486
|
+
* Marks a record as a normalized cache object so that {@link mergeFields}
|
|
487
|
+
* can distinguish it from opaque scalar values (e.g. JSON scalars).
|
|
488
|
+
* Only normalized records are deep-merged; unmarked objects are treated as
|
|
489
|
+
* atomic values and replaced entirely on write.
|
|
487
490
|
* @internal
|
|
488
491
|
*/
|
|
489
|
-
const
|
|
492
|
+
const markNormalized = (obj) => {
|
|
493
|
+
Object.defineProperty(obj, NormalizedKey, { value: true });
|
|
494
|
+
};
|
|
495
|
+
const isNormalizedRecord = (value) => {
|
|
496
|
+
return typeof value === "object" && value !== null && NormalizedKey in value;
|
|
497
|
+
};
|
|
498
|
+
/**
|
|
499
|
+
* Deeply merges two values. When {@link deep} is false (default), only
|
|
500
|
+
* {@link markNormalized normalized} cache objects are recursively merged;
|
|
501
|
+
* unmarked plain objects (e.g. JSON scalars) are atomically replaced.
|
|
502
|
+
* When {@link deep} is true, all objects are recursively merged unconditionally.
|
|
503
|
+
* Arrays are element-wise merged, entity links and primitives use last-write-wins.
|
|
504
|
+
* @internal
|
|
505
|
+
*/
|
|
506
|
+
const mergeFieldValue = (existing, incoming, deep) => {
|
|
490
507
|
if (isNullish(existing) || isNullish(incoming)) return incoming;
|
|
491
508
|
if (typeof existing !== "object" || typeof incoming !== "object") return incoming;
|
|
492
509
|
if (isEntityLink(existing) || isEntityLink(incoming)) return incoming;
|
|
493
|
-
if (Array.isArray(existing) && Array.isArray(incoming)) return incoming.map((item, i) => i < existing.length ? mergeFieldValue(existing[i], item) : item);
|
|
510
|
+
if (Array.isArray(existing) && Array.isArray(incoming)) return incoming.map((item, i) => i < existing.length ? mergeFieldValue(existing[i], item, deep) : item);
|
|
494
511
|
if (Array.isArray(existing) || Array.isArray(incoming)) return incoming;
|
|
495
|
-
|
|
512
|
+
if (!deep && !isNormalizedRecord(incoming)) return incoming;
|
|
513
|
+
mergeFields(existing, incoming, deep);
|
|
496
514
|
return existing;
|
|
497
515
|
};
|
|
498
516
|
/**
|
|
499
|
-
* Deeply merges source fields into target.
|
|
500
|
-
*
|
|
517
|
+
* Deeply merges source fields into target.
|
|
518
|
+
* When {@link deep} is false (default), only {@link markNormalized normalized}
|
|
519
|
+
* objects are recursively merged; unmarked objects are atomically replaced.
|
|
520
|
+
* When {@link deep} is true, all objects are recursively merged unconditionally.
|
|
501
521
|
* @internal
|
|
502
522
|
*/
|
|
503
|
-
const mergeFields = (target, source) => {
|
|
523
|
+
const mergeFields = (target, source, deep) => {
|
|
504
524
|
if (isNullish(source) || typeof source !== "object" || Array.isArray(source)) return;
|
|
505
|
-
for (const key of Object.keys(source)) target[key] = mergeFieldValue(target[key], source[key]);
|
|
525
|
+
for (const key of Object.keys(source)) target[key] = mergeFieldValue(target[key], source[key], deep ?? false);
|
|
506
526
|
};
|
|
507
527
|
/**
|
|
508
528
|
* Creates a FieldKey from a raw field name and optional arguments.
|
|
@@ -530,29 +550,31 @@ const normalize = (schemaMeta, selections, storage, data, variables, accessor) =
|
|
|
530
550
|
if (keys.every((k) => k !== void 0 && k !== null)) return makeEntityKey(typename, keys);
|
|
531
551
|
return null;
|
|
532
552
|
};
|
|
533
|
-
const normalizeField = (storageKey, selections, value) => {
|
|
553
|
+
const normalizeField = (storageKey, selections, value, parentType) => {
|
|
534
554
|
if (isNullish(value)) return value;
|
|
535
|
-
if (Array.isArray(value)) return value.map((item) => normalizeField(storageKey, selections, item));
|
|
555
|
+
if (Array.isArray(value)) return value.map((item) => normalizeField(storageKey, selections, item, parentType));
|
|
536
556
|
const data = value;
|
|
537
|
-
const typename = resolveTypename(selections, data);
|
|
557
|
+
const typename = resolveTypename(selections, data) ?? (parentType && schemaMeta.entities[parentType] ? parentType : void 0);
|
|
538
558
|
const entityKey = resolveEntityKey(typename, data);
|
|
539
559
|
if (entityKey) storageKey = entityKey;
|
|
540
560
|
const fields = {};
|
|
541
561
|
for (const selection of selections) if (selection.kind === "Field") {
|
|
542
562
|
const fieldKey = makeFieldKey(selection, variables);
|
|
543
|
-
|
|
563
|
+
let fieldValue = data[selection.alias ?? selection.name];
|
|
564
|
+
if (selection.name === "__typename" && fieldValue === void 0 && typename) fieldValue = typename;
|
|
544
565
|
if (storageKey !== null && selection.selections && typeof fieldValue === "object" && fieldValue !== null && !Array.isArray(fieldValue)) {
|
|
545
|
-
const fieldTypename = resolveTypename(selection.selections, fieldValue);
|
|
566
|
+
const fieldTypename = resolveTypename(selection.selections, fieldValue) ?? (selection.type && schemaMeta.entities[selection.type] ? selection.type : void 0);
|
|
546
567
|
if (fieldTypename && schemaMeta.entities[fieldTypename] && !resolveEntityKey(fieldTypename, fieldValue) && isEntityLink(storage[storageKey]?.[fieldKey])) continue;
|
|
547
568
|
}
|
|
548
569
|
const oldValue = storageKey === null ? void 0 : storage[storageKey]?.[fieldKey];
|
|
549
570
|
if (storageKey !== null && (!selection.selections || isNullish(oldValue) || isNullish(fieldValue))) accessor?.(storageKey, fieldKey, oldValue, fieldValue);
|
|
550
|
-
fields[fieldKey] = selection.selections ? normalizeField(null, selection.selections, fieldValue) : fieldValue;
|
|
571
|
+
fields[fieldKey] = selection.selections ? normalizeField(null, selection.selections, fieldValue, selection.type) : fieldValue;
|
|
551
572
|
if (storageKey !== null && selection.selections && !isNullish(oldValue) && !isNullish(fieldValue) && !isEntityLink(fields[fieldKey]) && !isEqual(oldValue, fields[fieldKey])) accessor?.(storageKey, fieldKey, oldValue, fields[fieldKey]);
|
|
552
573
|
} else if (selection.kind === "FragmentSpread" || selection.kind === "InlineFragment" && selection.on === typename) {
|
|
553
574
|
const inner = normalizeField(storageKey, selection.selections, value);
|
|
554
575
|
if (!isEntityLink(inner)) mergeFields(fields, inner);
|
|
555
576
|
}
|
|
577
|
+
markNormalized(fields);
|
|
556
578
|
if (entityKey) {
|
|
557
579
|
const existing = storage[entityKey];
|
|
558
580
|
if (existing) mergeFields(existing, fields);
|
|
@@ -602,7 +624,7 @@ const denormalize = (selections, storage, value, variables, accessor) => {
|
|
|
602
624
|
}
|
|
603
625
|
const name = selection.alias ?? selection.name;
|
|
604
626
|
const value = selection.selections ? denormalizeField(null, selection.selections, fieldValue) : fieldValue;
|
|
605
|
-
if (name in fields) mergeFields(fields, { [name]: value });
|
|
627
|
+
if (name in fields) mergeFields(fields, { [name]: value }, true);
|
|
606
628
|
else fields[name] = value;
|
|
607
629
|
} else if (selection.kind === "FragmentSpread") if (storageKey !== null && storageKey !== RootFieldKey) {
|
|
608
630
|
fields[FragmentRefKey] = storageKey;
|
|
@@ -618,8 +640,8 @@ const denormalize = (selections, storage, value, variables, accessor) => {
|
|
|
618
640
|
};
|
|
619
641
|
}
|
|
620
642
|
if (accessor) denormalize(selection.selections, storage, { [EntityLinkKey]: storageKey }, variables, accessor);
|
|
621
|
-
} else mergeFields(fields, denormalizeField(storageKey, selection.selections, value));
|
|
622
|
-
else if (selection.kind === "InlineFragment" && selection.on === data[typenameFieldKey]) mergeFields(fields, denormalizeField(storageKey, selection.selections, value));
|
|
643
|
+
} else mergeFields(fields, denormalizeField(storageKey, selection.selections, value), true);
|
|
644
|
+
else if (selection.kind === "InlineFragment" && selection.on === data[typenameFieldKey]) mergeFields(fields, denormalizeField(storageKey, selection.selections, value), true);
|
|
623
645
|
return fields;
|
|
624
646
|
};
|
|
625
647
|
return {
|