@@ -39,45 +39,48 @@ export const mergeFormats = (formats: Format[], root?: RawObject, inheritedType?
3939 return ownType ?? inheritedType ;
4040 } ;
4141
42- // ── Define a lazy getter for every key ────────────────────────────────────
42+ // ── Define a self-memoizing getter for every key ─────────────────────────
43+ // On first access the getter computes the value, overwrites itself with a
44+ // plain data property, and is never called again. After a single traversal
45+ // the object is indistinguishable from a regular {}.
4346 for ( const key of keys ) {
4447 Object . defineProperty ( merged , key , {
4548 get ( ) : unknown {
4649 // $type: own value, or fall back to the inherited type.
47- if ( key === "$type" ) return getNodeType ( ) ;
48-
49- const subFormats : RawObject [ ] = [ ] ;
50- let leafValue : unknown ;
51- let hasLeaf = false ;
52-
53- for ( const format of formats ) {
54- const val = ( format as RawObject ) [ key ] ;
55- if ( val === undefined ) continue ;
56-
57- if ( isPlainObject ( val ) ) {
58- subFormats . push ( val ) ;
59- } else {
60- // A later leaf (primitive or array) wins; reset sub-objects.
61- subFormats . length = 0 ;
62- leafValue = val ;
63- hasLeaf = true ;
50+ let value : unknown = key === "$type" ? getNodeType ( ) : undefined ;
51+
52+ if ( value === undefined && key !== "$type" ) {
53+ const subFormats : RawObject [ ] = [ ] ;
54+ let leafValue : unknown ;
55+ let hasLeaf = false ;
56+
57+ for ( const format of formats ) {
58+ const val = ( format as RawObject ) [ key ] ;
59+ if ( val === undefined ) continue ;
60+
61+ if ( isPlainObject ( val ) ) {
62+ subFormats . push ( val ) ;
63+ } else {
64+ // A later leaf (primitive or array) wins; reset sub-objects.
65+ subFormats . length = 0 ;
66+ leafValue = val ;
67+ hasLeaf = true ;
68+ }
6469 }
65- }
66-
67- if ( subFormats . length > 0 ) {
68- // Pass along the current node's effective type so children can inherit it.
69- return mergeFormats ( subFormats as Format [ ] , effectiveRoot , getNodeType ( ) ) ;
70- }
7170
72- if ( hasLeaf ) {
73- // Resolve DTCG alias strings in $value lazily against the merged root.
74- if ( key === "$value" && isAlias ( leafValue ) ) {
75- return resolveAlias ( leafValue , effectiveRoot ) ;
71+ if ( subFormats . length > 0 ) {
72+ // Pass along the current node's effective type so children can inherit it.
73+ value = mergeFormats ( subFormats as Format [ ] , effectiveRoot , getNodeType ( ) ) ;
74+ } else if ( hasLeaf ) {
75+ // Resolve DTCG alias strings in $value lazily against the merged root.
76+ value = key === "$value" && isAlias ( leafValue ) ? resolveAlias ( leafValue , effectiveRoot ) : leafValue ;
7677 }
77- return leafValue ;
7878 }
7979
80- return undefined ;
80+ // Replace this getter with the computed value so the object becomes
81+ // a plain data property after first access.
82+ Object . defineProperty ( this , key , { value, enumerable : true , configurable : true } ) ;
83+ return value ;
8184 } ,
8285 enumerable : true ,
8386 configurable : true ,
0 commit comments