@mearie/core 0.5.0 → 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 +61 -27
- package/dist/index.mjs +61 -27
- 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.
|
|
@@ -518,35 +538,49 @@ const makeFieldKeyFromArgs = (field, args) => {
|
|
|
518
538
|
|
|
519
539
|
//#endregion
|
|
520
540
|
//#region src/cache/normalize.ts
|
|
541
|
+
const resolveTypename = (selections, data) => {
|
|
542
|
+
for (const s of selections) if (s.kind === "Field" && s.name === "__typename") return data[s.alias ?? "__typename"];
|
|
543
|
+
return data.__typename;
|
|
544
|
+
};
|
|
521
545
|
const normalize = (schemaMeta, selections, storage, data, variables, accessor) => {
|
|
522
|
-
const
|
|
546
|
+
const resolveEntityKey = (typename, data) => {
|
|
547
|
+
if (!typename) return null;
|
|
548
|
+
const entityMeta = schemaMeta.entities[typename];
|
|
549
|
+
if (!entityMeta) return null;
|
|
550
|
+
const keys = entityMeta.keyFields.map((field) => data[field]);
|
|
551
|
+
if (keys.every((k) => k !== void 0 && k !== null)) return makeEntityKey(typename, keys);
|
|
552
|
+
return null;
|
|
553
|
+
};
|
|
554
|
+
const normalizeField = (storageKey, selections, value, parentType) => {
|
|
523
555
|
if (isNullish(value)) return value;
|
|
524
|
-
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));
|
|
525
557
|
const data = value;
|
|
526
|
-
const typename = data.
|
|
527
|
-
|
|
528
|
-
if (
|
|
529
|
-
const keys = entityMeta.keyFields.map((field) => data[field]);
|
|
530
|
-
if (keys.every((k) => k !== void 0 && k !== null)) storageKey = makeEntityKey(typename, keys);
|
|
531
|
-
else entityMeta = void 0;
|
|
532
|
-
}
|
|
558
|
+
const typename = resolveTypename(selections, data) ?? (parentType && schemaMeta.entities[parentType] ? parentType : void 0);
|
|
559
|
+
const entityKey = resolveEntityKey(typename, data);
|
|
560
|
+
if (entityKey) storageKey = entityKey;
|
|
533
561
|
const fields = {};
|
|
534
562
|
for (const selection of selections) if (selection.kind === "Field") {
|
|
535
563
|
const fieldKey = makeFieldKey(selection, variables);
|
|
536
|
-
|
|
564
|
+
let fieldValue = data[selection.alias ?? selection.name];
|
|
565
|
+
if (selection.name === "__typename" && fieldValue === void 0 && typename) fieldValue = typename;
|
|
566
|
+
if (storageKey !== null && selection.selections && typeof fieldValue === "object" && fieldValue !== null && !Array.isArray(fieldValue)) {
|
|
567
|
+
const fieldTypename = resolveTypename(selection.selections, fieldValue) ?? (selection.type && schemaMeta.entities[selection.type] ? selection.type : void 0);
|
|
568
|
+
if (fieldTypename && schemaMeta.entities[fieldTypename] && !resolveEntityKey(fieldTypename, fieldValue) && isEntityLink(storage[storageKey]?.[fieldKey])) continue;
|
|
569
|
+
}
|
|
537
570
|
const oldValue = storageKey === null ? void 0 : storage[storageKey]?.[fieldKey];
|
|
538
571
|
if (storageKey !== null && (!selection.selections || isNullish(oldValue) || isNullish(fieldValue))) accessor?.(storageKey, fieldKey, oldValue, fieldValue);
|
|
539
|
-
fields[fieldKey] = selection.selections ? normalizeField(null, selection.selections, fieldValue) : fieldValue;
|
|
572
|
+
fields[fieldKey] = selection.selections ? normalizeField(null, selection.selections, fieldValue, selection.type) : fieldValue;
|
|
540
573
|
if (storageKey !== null && selection.selections && !isNullish(oldValue) && !isNullish(fieldValue) && !isEntityLink(fields[fieldKey]) && !isEqual(oldValue, fields[fieldKey])) accessor?.(storageKey, fieldKey, oldValue, fields[fieldKey]);
|
|
541
574
|
} else if (selection.kind === "FragmentSpread" || selection.kind === "InlineFragment" && selection.on === typename) {
|
|
542
575
|
const inner = normalizeField(storageKey, selection.selections, value);
|
|
543
576
|
if (!isEntityLink(inner)) mergeFields(fields, inner);
|
|
544
577
|
}
|
|
545
|
-
|
|
546
|
-
|
|
578
|
+
markNormalized(fields);
|
|
579
|
+
if (entityKey) {
|
|
580
|
+
const existing = storage[entityKey];
|
|
547
581
|
if (existing) mergeFields(existing, fields);
|
|
548
|
-
else storage[
|
|
549
|
-
return { [EntityLinkKey]:
|
|
582
|
+
else storage[entityKey] = fields;
|
|
583
|
+
return { [EntityLinkKey]: entityKey };
|
|
550
584
|
}
|
|
551
585
|
return fields;
|
|
552
586
|
};
|
|
@@ -591,7 +625,7 @@ const denormalize = (selections, storage, value, variables, accessor) => {
|
|
|
591
625
|
}
|
|
592
626
|
const name = selection.alias ?? selection.name;
|
|
593
627
|
const value = selection.selections ? denormalizeField(null, selection.selections, fieldValue) : fieldValue;
|
|
594
|
-
if (name in fields) mergeFields(fields, { [name]: value });
|
|
628
|
+
if (name in fields) mergeFields(fields, { [name]: value }, true);
|
|
595
629
|
else fields[name] = value;
|
|
596
630
|
} else if (selection.kind === "FragmentSpread") if (storageKey !== null && storageKey !== RootFieldKey) {
|
|
597
631
|
fields[FragmentRefKey] = storageKey;
|
|
@@ -607,8 +641,8 @@ const denormalize = (selections, storage, value, variables, accessor) => {
|
|
|
607
641
|
};
|
|
608
642
|
}
|
|
609
643
|
if (accessor) denormalize(selection.selections, storage, { [EntityLinkKey]: storageKey }, variables, accessor);
|
|
610
|
-
} else mergeFields(fields, denormalizeField(storageKey, selection.selections, value));
|
|
611
|
-
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);
|
|
612
646
|
return fields;
|
|
613
647
|
};
|
|
614
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.
|
|
@@ -517,35 +537,49 @@ const makeFieldKeyFromArgs = (field, args) => {
|
|
|
517
537
|
|
|
518
538
|
//#endregion
|
|
519
539
|
//#region src/cache/normalize.ts
|
|
540
|
+
const resolveTypename = (selections, data) => {
|
|
541
|
+
for (const s of selections) if (s.kind === "Field" && s.name === "__typename") return data[s.alias ?? "__typename"];
|
|
542
|
+
return data.__typename;
|
|
543
|
+
};
|
|
520
544
|
const normalize = (schemaMeta, selections, storage, data, variables, accessor) => {
|
|
521
|
-
const
|
|
545
|
+
const resolveEntityKey = (typename, data) => {
|
|
546
|
+
if (!typename) return null;
|
|
547
|
+
const entityMeta = schemaMeta.entities[typename];
|
|
548
|
+
if (!entityMeta) return null;
|
|
549
|
+
const keys = entityMeta.keyFields.map((field) => data[field]);
|
|
550
|
+
if (keys.every((k) => k !== void 0 && k !== null)) return makeEntityKey(typename, keys);
|
|
551
|
+
return null;
|
|
552
|
+
};
|
|
553
|
+
const normalizeField = (storageKey, selections, value, parentType) => {
|
|
522
554
|
if (isNullish(value)) return value;
|
|
523
|
-
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));
|
|
524
556
|
const data = value;
|
|
525
|
-
const typename = data.
|
|
526
|
-
|
|
527
|
-
if (
|
|
528
|
-
const keys = entityMeta.keyFields.map((field) => data[field]);
|
|
529
|
-
if (keys.every((k) => k !== void 0 && k !== null)) storageKey = makeEntityKey(typename, keys);
|
|
530
|
-
else entityMeta = void 0;
|
|
531
|
-
}
|
|
557
|
+
const typename = resolveTypename(selections, data) ?? (parentType && schemaMeta.entities[parentType] ? parentType : void 0);
|
|
558
|
+
const entityKey = resolveEntityKey(typename, data);
|
|
559
|
+
if (entityKey) storageKey = entityKey;
|
|
532
560
|
const fields = {};
|
|
533
561
|
for (const selection of selections) if (selection.kind === "Field") {
|
|
534
562
|
const fieldKey = makeFieldKey(selection, variables);
|
|
535
|
-
|
|
563
|
+
let fieldValue = data[selection.alias ?? selection.name];
|
|
564
|
+
if (selection.name === "__typename" && fieldValue === void 0 && typename) fieldValue = typename;
|
|
565
|
+
if (storageKey !== null && selection.selections && typeof fieldValue === "object" && fieldValue !== null && !Array.isArray(fieldValue)) {
|
|
566
|
+
const fieldTypename = resolveTypename(selection.selections, fieldValue) ?? (selection.type && schemaMeta.entities[selection.type] ? selection.type : void 0);
|
|
567
|
+
if (fieldTypename && schemaMeta.entities[fieldTypename] && !resolveEntityKey(fieldTypename, fieldValue) && isEntityLink(storage[storageKey]?.[fieldKey])) continue;
|
|
568
|
+
}
|
|
536
569
|
const oldValue = storageKey === null ? void 0 : storage[storageKey]?.[fieldKey];
|
|
537
570
|
if (storageKey !== null && (!selection.selections || isNullish(oldValue) || isNullish(fieldValue))) accessor?.(storageKey, fieldKey, oldValue, fieldValue);
|
|
538
|
-
fields[fieldKey] = selection.selections ? normalizeField(null, selection.selections, fieldValue) : fieldValue;
|
|
571
|
+
fields[fieldKey] = selection.selections ? normalizeField(null, selection.selections, fieldValue, selection.type) : fieldValue;
|
|
539
572
|
if (storageKey !== null && selection.selections && !isNullish(oldValue) && !isNullish(fieldValue) && !isEntityLink(fields[fieldKey]) && !isEqual(oldValue, fields[fieldKey])) accessor?.(storageKey, fieldKey, oldValue, fields[fieldKey]);
|
|
540
573
|
} else if (selection.kind === "FragmentSpread" || selection.kind === "InlineFragment" && selection.on === typename) {
|
|
541
574
|
const inner = normalizeField(storageKey, selection.selections, value);
|
|
542
575
|
if (!isEntityLink(inner)) mergeFields(fields, inner);
|
|
543
576
|
}
|
|
544
|
-
|
|
545
|
-
|
|
577
|
+
markNormalized(fields);
|
|
578
|
+
if (entityKey) {
|
|
579
|
+
const existing = storage[entityKey];
|
|
546
580
|
if (existing) mergeFields(existing, fields);
|
|
547
|
-
else storage[
|
|
548
|
-
return { [EntityLinkKey]:
|
|
581
|
+
else storage[entityKey] = fields;
|
|
582
|
+
return { [EntityLinkKey]: entityKey };
|
|
549
583
|
}
|
|
550
584
|
return fields;
|
|
551
585
|
};
|
|
@@ -590,7 +624,7 @@ const denormalize = (selections, storage, value, variables, accessor) => {
|
|
|
590
624
|
}
|
|
591
625
|
const name = selection.alias ?? selection.name;
|
|
592
626
|
const value = selection.selections ? denormalizeField(null, selection.selections, fieldValue) : fieldValue;
|
|
593
|
-
if (name in fields) mergeFields(fields, { [name]: value });
|
|
627
|
+
if (name in fields) mergeFields(fields, { [name]: value }, true);
|
|
594
628
|
else fields[name] = value;
|
|
595
629
|
} else if (selection.kind === "FragmentSpread") if (storageKey !== null && storageKey !== RootFieldKey) {
|
|
596
630
|
fields[FragmentRefKey] = storageKey;
|
|
@@ -606,8 +640,8 @@ const denormalize = (selections, storage, value, variables, accessor) => {
|
|
|
606
640
|
};
|
|
607
641
|
}
|
|
608
642
|
if (accessor) denormalize(selection.selections, storage, { [EntityLinkKey]: storageKey }, variables, accessor);
|
|
609
|
-
} else mergeFields(fields, denormalizeField(storageKey, selection.selections, value));
|
|
610
|
-
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);
|
|
611
645
|
return fields;
|
|
612
646
|
};
|
|
613
647
|
return {
|