[dart][inspect] API changes from design review

Combine Metric/Property into Value.

PS1: Make a nice class/mixin hierarchy with no duplicated code.
Combine metric.dart and property.dart into value.dart, but leave
metric_test.dart and property_test.dart alone (aside from
method and class name changes) to make it easier to see that the existing
tests didn't change.

PS2: Combine property_test.dart and metric_test.dart into
value_test.dart. No edits within the moved code.

PS3: Some search-and-replace, one bugfix.

PS4: Wording changes; specifying the exceptions thrown; and a refactor
in VMO-code (inspired by the bugfix).

PS5: Rebase; refactor of Value to recombine some classes/mixins; wording.

PS6: Minor changes

Test: fx run-host-tests fuchsia_inspect_package_unittests

CF-597 #progress

Change-Id: I3a6c592cb5cf7a01165da784c17c51bf41102510
diff --git a/public/dart/fuchsia_inspect/BUILD.gn b/public/dart/fuchsia_inspect/BUILD.gn
index bb0fa4c..45da0e4 100644
--- a/public/dart/fuchsia_inspect/BUILD.gn
+++ b/public/dart/fuchsia_inspect/BUILD.gn
@@ -17,8 +17,7 @@
     "src/inspect/inspect.dart",
     "src/inspect/internal/_inspect_impl.dart",
     "src/inspect/node.dart",
-    "src/inspect/metric.dart",
-    "src/inspect/property.dart",
+    "src/inspect/value.dart",
     "src/vmo/bitfield64.dart",
     "src/vmo/block.dart",
     "src/vmo/heap.dart",
@@ -43,8 +42,7 @@
     "inspect/internal/inspect_impl_test.dart",
     "inspect/inspect_test.dart",
     "inspect/node_test.dart",
-    "inspect/metric_test.dart",
-    "inspect/property_test.dart",
+    "inspect/value_test.dart",
     "integration/writer.dart",
     "util.dart",
     "vmo/bitfield64_test.dart",
diff --git a/public/dart/fuchsia_inspect/examples/inspect_mod/lib/src/inspect_example_app.dart b/public/dart/fuchsia_inspect/examples/inspect_mod/lib/src/inspect_example_app.dart
index 179216f..a8e2396 100644
--- a/public/dart/fuchsia_inspect/examples/inspect_mod/lib/src/inspect_example_app.dart
+++ b/public/dart/fuchsia_inspect/examples/inspect_mod/lib/src/inspect_example_app.dart
@@ -12,7 +12,7 @@
   final inspect.Node _inspectNode;
 
   InspectExampleApp(this._inspectNode) {
-    _initMetrics();
+    _initValues();
   }
 
   @override
@@ -24,13 +24,13 @@
       ),
       home: _InspectHomePage(
           title: 'Hello Inspect!',
-          inspectNode: _inspectNode.createChild('home-page')),
+          inspectNode: _inspectNode.child('home-page')),
     );
   }
 
-  /// Initializes the [Inspect] metrics for this widget.
-  void _initMetrics() {
-    _inspectNode.createStringProperty('app-color').setValue('$_appColor');
+  /// Initializes the [Inspect] values for this widget.
+  void _initValues() {
+    _inspectNode.stringValue('app-color').setValue('$_appColor');
   }
 }
 
@@ -39,7 +39,7 @@
   final inspect.Node inspectNode;
 
   _InspectHomePage({Key key, this.title, this.inspectNode}) : super(key: key) {
-    inspectNode.createStringProperty('title').setValue(title);
+    inspectNode.stringValue('title').setValue(title);
   }
 
   @override
@@ -56,17 +56,17 @@
 
   final inspect.Node _inspectNode;
 
-  /// A metric that tracks the value of [_counter].
-  final inspect.IntMetric _counterMetric;
+  /// A value that tracks [_counter].
+  final inspect.IntValue _counterValue;
 
-  inspect.StringProperty _backgroundProperty;
+  inspect.StringValue _backgroundValue;
 
   int _counter = 0;
   int _colorIndex = 0;
 
   _InspectHomePageState(this._inspectNode)
-      : _counterMetric = _inspectNode.createIntMetric('counter') {
-    _backgroundProperty = _inspectNode.createStringProperty('background-color')
+      : _counterValue = _inspectNode.intValue('counter') {
+    _backgroundValue = _inspectNode.stringValue('background-color')
       ..setValue('$_backgroundColor');
   }
 
@@ -76,18 +76,18 @@
     setState(() {
       _counter++;
 
-      // Note: an alternate approach that is also valid is to set the metric to
+      // Note: an alternate approach that is also valid is to set the value to
       // the new value:
       //
-      //     _counterMetric.value = _counter;
-      _counterMetric.add(1);
+      //     _counterValue.value = _counter;
+      _counterValue.add(1);
     });
   }
 
   void _decrementCounter() {
     setState(() {
       _counter--;
-      _counterMetric.subtract(1);
+      _counterValue.subtract(1);
     });
   }
 
@@ -101,16 +101,16 @@
       if (_colorIndex >= _colors.length) {
         _colorIndex = 0;
 
-        // Contrived example of removing an Inspect property:
-        // Once we've looped through the colors once, delete the property.
+        // Contrived example of removing an Inspect value:
+        // Once we've looped through the colors once, delete the value.
         //
         // A more realistic example would be if something were being removed
         // from the UI, but this is intended to be super simple.
-        _backgroundProperty.delete();
-        _backgroundProperty = null;
+        _backgroundValue.delete();
+        _backgroundValue = null;
       }
 
-      _backgroundProperty?.setValue('$_backgroundColor');
+      _backgroundValue?.setValue('$_backgroundColor');
     });
   }
 
diff --git a/public/dart/fuchsia_inspect/lib/inspect.dart b/public/dart/fuchsia_inspect/lib/inspect.dart
index 60067fd..2f8c855 100644
--- a/public/dart/fuchsia_inspect/lib/inspect.dart
+++ b/public/dart/fuchsia_inspect/lib/inspect.dart
@@ -3,4 +3,12 @@
 // found in the LICENSE file.
 
 /// The Inspect API for Dart.
-export 'src/inspect/inspect.dart' hide RootNode;
+export 'src/inspect/inspect.dart'
+    show
+        Inspect,
+        InspectStateError,
+        Node,
+        IntValue,
+        DoubleValue,
+        StringValue,
+        ByteDataValue;
diff --git a/public/dart/fuchsia_inspect/lib/src/inspect/inspect.dart b/public/dart/fuchsia_inspect/lib/src/inspect/inspect.dart
index 3bda525..4f33144 100644
--- a/public/dart/fuchsia_inspect/lib/src/inspect/inspect.dart
+++ b/public/dart/fuchsia_inspect/lib/src/inspect/inspect.dart
@@ -13,8 +13,7 @@
 import 'internal/_inspect_impl.dart';
 
 part 'node.dart';
-part 'metric.dart';
-part 'property.dart';
+part 'value.dart';
 
 /// Unless reconfigured, the VMO will be this size.
 /// @nodoc
@@ -31,11 +30,15 @@
 // verify that Inspect() returns the same object on each call.
 // (It can't be tested in host unit tests.)
 
-/// Inspect exposes a structured tree of internal component state.
+/// [Inspect] exposes a structured tree of internal component state.
+///
+/// The [Inspect] object maintains a hierarchy of [Node] objects whose data are
+/// exposed for reading by specialized tools such as iquery.
+///
+/// The classes exposed by this library do not support reading.
 abstract class Inspect {
   /// Size of the VMO that was / will be created.
   /// @nodoc
-  @visibleForTesting
   static int vmoSize = defaultVmoSizeBytes;
   static InspectImpl _singleton;
 
@@ -53,8 +56,9 @@
   ///
   /// This may not be called after the first call to Inspect().
   ///
-  /// [vmoSizeBytes]: Sets the maximum size of the VMO used to store
-  /// inspection data for this program. Must be at least 64 bytes.
+  /// [vmoSizeBytes]: Sets the maximum size of the virtual memory object (VMO)
+  /// used to store inspection data for this program.
+  /// Must be at least 64 bytes.
   ///
   /// Throws [InspectStateError] if called after Inspect() or with an
   /// invalid vmoSizeBytes.
diff --git a/public/dart/fuchsia_inspect/lib/src/inspect/metric.dart b/public/dart/fuchsia_inspect/lib/src/inspect/metric.dart
deleted file mode 100644
index 930cb81..0000000
--- a/public/dart/fuchsia_inspect/lib/src/inspect/metric.dart
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-part of 'inspect.dart';
-
-/// A VMO-backed key-value pair with a [String] key and num value.
-abstract class _Metric<T extends num> {
-  /// The VMO index for this metric.
-  /// @nodoc
-  @visibleForTesting
-  final int index;
-
-  /// The writer for the underlying VMO.
-  ///
-  /// Will be set to null if the metric has been deleted or could not be
-  /// created in the VMO.
-  /// If so, all actions on this metric should be no-ops and not throw.
-  VmoWriter _writer;
-
-  /// Creates a [_Metric] with [name] and [value] under the [parentIndex].
-  _Metric(String name, int parentIndex, this._writer, T value)
-      : index = _writer.createMetric(parentIndex, name, value) {
-    if (index == invalidIndex) {
-      _writer = null;
-    }
-  }
-
-  /// Creates a [_Metric] that never does anything.
-  ///
-  /// These are returned when calling create__Metric on a deleted [Node].
-  _Metric.deleted()
-      : _writer = null,
-        index = invalidIndex;
-
-  bool get _isDeleted => _writer == null;
-
-  /// Sets the value of this [_Metric].
-  void setValue(T value) {
-    _writer?.setMetric(index, value);
-  }
-
-  /// Adds [delta] to the value of this metric in the VMO.
-  void add(T delta) {
-    _writer?.addMetric(index, delta);
-  }
-
-  /// Subtracts [delta] from the value of this metric in the VMO.
-  void subtract(T delta) {
-    _writer?.subMetric(index, delta);
-  }
-
-  /// Delete this metric from the VMO.
-  ///
-  /// After a metric has been deleted, it should no longer be used, so callers
-  /// should clear their references to this metric after calling delete.
-  /// Any calls on an already deleted metric will be no-ops.
-  void delete() {
-    _writer?.deleteMetric(index);
-    _writer = null;
-  }
-}
-
-/// A VMO-backed key-value pair with a [String] key and [int] value.
-class IntMetric extends _Metric<int> {
-  IntMetric._(String name, int parentIndex, VmoWriter writer)
-      : super(name, parentIndex, writer, 0);
-
-  IntMetric._deleted() : super.deleted();
-}
-
-/// A VMO-backed key-value pair with a [String] key and [double] value.
-class DoubleMetric extends _Metric<double> {
-  DoubleMetric._(String name, int parentIndex, VmoWriter writer)
-      : super(name, parentIndex, writer, 0.0);
-
-  DoubleMetric._deleted() : super.deleted();
-}
diff --git a/public/dart/fuchsia_inspect/lib/src/inspect/node.dart b/public/dart/fuchsia_inspect/lib/src/inspect/node.dart
index 8b2e320..42375d5 100644
--- a/public/dart/fuchsia_inspect/lib/src/inspect/node.dart
+++ b/public/dart/fuchsia_inspect/lib/src/inspect/node.dart
@@ -4,7 +4,8 @@
 
 part of 'inspect.dart';
 
-/// A node in the [Inspect] tree that can have associated key-values (KVs).
+/// A named node in the Inspect tree that can have [Node]s and
+/// values under it.
 class Node {
   /// The VMO index of this node.
   /// @nodoc
@@ -13,19 +14,18 @@
 
   /// The writer for the underlying VMO.
   ///
-  /// Will be set to null if the Metric has been deleted or could not be
+  /// Will be set to null if the Node has been deleted or could not be
   /// created in the VMO.
-  /// If so, all actions on this Metric should be no-ops and not throw.
+  /// If so, all actions on this Node should be no-ops and not throw.
   VmoWriter _writer;
 
-  final _properties = <String, _Property>{};
-  final _metrics = <String, _Metric>{};
+  final _values = <String, Value>{};
   final _children = <String, Node>{};
 
   /// Creates a [Node] with [name] under the [parentIndex].
   ///
   /// Private as an implementation detail to code that understands VMO indices.
-  /// Client code that wishes to create [Node]s should use [createChild].
+  /// Client code that wishes to create [Node]s should use [child].
   Node._(String name, int parentIndex, this._writer)
       : index = _writer.createNode(parentIndex, name) {
     if (index == invalidIndex) {
@@ -45,11 +45,11 @@
 
   bool get _isDeleted => _writer == null;
 
-  /// Creates a child [Node] with [name].
+  /// Returns a child [Node] with [name].
   ///
-  /// If a child with [name] already exists, this
+  /// If a child with [name] already exists and was not deleted, this
   /// method returns it. Otherwise, it creates a new [Node].
-  Node createChild(String name) {
+  Node child(String name) {
     if (_writer == null) {
       return Node._deleted();
     }
@@ -62,119 +62,117 @@
     return _children[name] = Node._(name, index, _writer);
   }
 
-  /// Delete this node and any children from underlying storage.
+  /// Deletes this node and any children from underlying storage.
   ///
-  /// After a node has been deleted, all calls on it and its children will have
-  /// no effect, but will not result in an error.
+  /// After a node has been deleted, all calls on it and its children have
+  /// no effect and do not result in an error. Calls on a deleted node that
+  /// return a Node or Value return an already-deleted object.
   void delete() {
     if (_writer == null) {
       return;
     }
-    _properties
-      ..forEach((_, property) => property.delete())
-      ..clear();
-    _metrics
-      ..forEach((_, metric) => metric.delete())
+    _values
+      ..forEach((_, value) => value.delete())
       ..clear();
     _children
       ..forEach((_, node) => node.delete())
       ..clear();
 
-    _writer.deleteNode(index);
+    _writer.deleteEntity(index);
     _writer = null;
   }
 
-  /// Creates a [StringProperty] with [name] on this node.
+  /// Returns a [StringValue] with [name] on this node.
   ///
-  /// If a [StringProperty] with [name] already exists and is not deleted,
+  /// If a [StringValue] with [name] already exists and is not deleted,
   /// this method returns it.
   ///
-  /// Otherwise, it creates a new property initialized to the empty string.
+  /// Otherwise, it creates a new value initialized to the empty string.
   ///
-  /// Throws [StateError] if a non-deleted property with [name] already exists
-  /// but it is not a [StringProperty].
-  StringProperty createStringProperty(String name) {
+  /// Throws [InspectStateError] if a non-deleted value with [name] already
+  /// exists but it is not a [StringValue].
+  StringValue stringValue(String name) {
     if (_writer == null) {
-      return StringProperty._deleted();
+      return StringValue._deleted();
     }
-    if (_properties.containsKey(name) && !_properties[name]._isDeleted) {
-      if (_properties[name] is! StringProperty) {
-        throw InspectStateError("Can't create StringProperty named $name;"
+    if (_values.containsKey(name) && !_values[name]._isDeleted) {
+      if (_values[name] is! StringValue) {
+        throw InspectStateError("Can't create StringValue named $name;"
             ' a different type exists.');
       }
-      return _properties[name];
+      return _values[name];
     }
-    return _properties[name] = StringProperty._(name, index, _writer);
+    return _values[name] = StringValue._(name, index, _writer);
   }
 
-  /// Creates a [ByteDataProperty] with [name] on this node.
+  /// Returns a [ByteDataValue] with [name] on this node.
   ///
-  /// If a [ByteDataProperty] with [name] already exists and is not deleted,
+  /// If a [ByteDataValue] with [name] already exists and is not deleted,
   /// this method returns it.
   ///
-  /// Otherwise, it creates a new property initialized to the empty
+  /// Otherwise, it creates a new value initialized to the empty
   /// byte data container.
   ///
-  /// Throws [StateError] if a non-deleted property with [name] already exists
-  /// but it is not a [ByteDataProperty].
-  ByteDataProperty createByteDataProperty(String name) {
+  /// Throws [InspectStateError] if a non-deleted value with [name] already exists
+  /// but it is not a [ByteDataValue].
+  ByteDataValue byteDataValue(String name) {
     if (_writer == null) {
-      return ByteDataProperty._deleted();
+      return ByteDataValue._deleted();
     }
-    if (_properties.containsKey(name) && !_properties[name]._isDeleted) {
-      if (_properties[name] is! ByteDataProperty) {
-        throw InspectStateError("Can't create ByteDataProperty named $name;"
+    if (_values.containsKey(name) && !_values[name]._isDeleted) {
+      if (_values[name] is! ByteDataValue) {
+        throw InspectStateError("Can't create ByteDataValue named $name;"
             ' a different type exists.');
       }
-      return _properties[name];
+      return _values[name];
     }
-    return _properties[name] = ByteDataProperty._(name, index, _writer);
+    return _values[name] = ByteDataValue._(name, index, _writer);
   }
 
-  /// Creates an [IntMetric] with [name] on this node.
+  /// Returns an [IntValue] with [name] on this node.
   ///
-  /// If an [IntMetric] with [name] already exists and is not
+  /// If an [IntValue] with [name] already exists and is not
   /// deleted, this method returns it.
   ///
-  /// Otherwise, it creates a new metric initialized to 0.
+  /// Otherwise, it creates a new value initialized to 0.
   ///
-  /// Throws [StateError] if a non-deleted metric with [name]
-  /// already exists but it is not an [IntMetric].
-  IntMetric createIntMetric(String name) {
+  /// Throws [InspectStateError] if a non-deleted value with [name]
+  /// already exists but it is not an [IntValue].
+  IntValue intValue(String name) {
     if (_writer == null) {
-      return IntMetric._deleted();
+      return IntValue._deleted();
     }
-    if (_metrics.containsKey(name) && !_metrics[name]._isDeleted) {
-      if (_metrics[name] is! IntMetric) {
+    if (_values.containsKey(name) && !_values[name]._isDeleted) {
+      if (_values[name] is! IntValue) {
         throw InspectStateError(
-            "Can't create IntMetric named $name; a different type exists.");
+            "Can't create IntValue named $name; a different type exists.");
       }
-      return _metrics[name];
+      return _values[name];
     }
-    return _metrics[name] = IntMetric._(name, index, _writer);
+    return _values[name] = IntValue._(name, index, _writer);
   }
 
-  /// Creates a [DoubleMetric] with [name] on this node.
+  /// Returns a [DoubleValue] with [name] on this node.
   ///
-  /// If a [DoubleMetric] with [name] already exists and is not
+  /// If a [DoubleValue] with [name] already exists and is not
   /// deleted, this method returns it.
   ///
-  /// Otherwise, it creates a new metric initialized to 0.0.
+  /// Otherwise, it creates a new value initialized to 0.0.
   ///
-  /// Throws [StateError] if a non-deleted metric with [name]
-  /// already exists but it is not a [DoubleMetric].
-  DoubleMetric createDoubleMetric(String name) {
+  /// Throws [InspectStateError] if a non-deleted value with [name]
+  /// already exists but it is not a [DoubleValue].
+  DoubleValue doubleValue(String name) {
     if (_writer == null) {
-      return DoubleMetric._deleted();
+      return DoubleValue._deleted();
     }
-    if (_metrics.containsKey(name) && !_metrics[name]._isDeleted) {
-      if (_metrics[name] is! DoubleMetric) {
-        throw InspectStateError("Can't create DoubleMetric named $name;"
+    if (_values.containsKey(name) && !_values[name]._isDeleted) {
+      if (_values[name] is! DoubleValue) {
+        throw InspectStateError("Can't create DoubleValue named $name;"
             ' a different type exists.');
       }
-      return _metrics[name];
+      return _values[name];
     }
-    return _metrics[name] = DoubleMetric._(name, index, _writer);
+    return _values[name] = DoubleValue._(name, index, _writer);
   }
 }
 
@@ -183,6 +181,7 @@
 /// The root node has special behavior: Delete is a NOP.
 ///
 /// This class should be hidden from the public API.
+/// @nodoc
 class RootNode extends Node {
   /// Creates a Node wrapping the root of the Inspect hierarchy.
   RootNode(VmoWriter writer) : super._root(writer);
diff --git a/public/dart/fuchsia_inspect/lib/src/inspect/property.dart b/public/dart/fuchsia_inspect/lib/src/inspect/property.dart
deleted file mode 100644
index ed67df3..0000000
--- a/public/dart/fuchsia_inspect/lib/src/inspect/property.dart
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-part of 'inspect.dart';
-
-/// A VMO-backed key-value pair with a [String] key and [T] value.
-class _Property<T> {
-  /// The VMO index for this property.
-  /// @nodoc
-  @visibleForTesting
-  final int index;
-
-  /// The writer for the underlying VMO.
-  ///
-  /// Will be set to null if the property has been deleted or could not be
-  ///  created in the VMO.
-  /// If so, all actions on this property will be no-ops and not throw.
-  VmoWriter _writer;
-
-  /// Creates a [_Property] with [name] under the [parentIndex].
-  _Property(String name, int parentIndex, this._writer)
-      : index = _writer.createProperty(parentIndex, name) {
-    if (index == invalidIndex) {
-      _writer = null;
-    }
-  }
-
-  /// Creates a _Property that never does anything.
-  ///
-  /// These are returned when calling create<*>Property on a deleted [Node].
-  _Property.deleted()
-      : _writer = null,
-        index = invalidIndex;
-
-  bool get _isDeleted => _writer == null;
-
-  /// Sets the value of this Property in the Inspect data.
-  void setValue(T value) {
-    _writer?.setProperty(index, value);
-  }
-
-  /// Delete this property from the VMO.
-  ///
-  /// After a property has been deleted, it should no longer be used, so callers
-  /// should clear their references to this property after calling delete.
-  /// Any calls on an already deleted property will be no-ops.
-  void delete() {
-    _writer?.deleteProperty(index);
-    _writer = null;
-  }
-}
-
-/// A VMO-backed key-value pair with a [String] key and [String] value.
-class StringProperty extends _Property<String> {
-  StringProperty._(String name, int parentIndex, VmoWriter writer)
-      : super(name, parentIndex, writer);
-
-  StringProperty._deleted() : super.deleted();
-}
-
-/// A VMO-backed key-value pair with a [String] key and [ByteData] value.
-class ByteDataProperty extends _Property<ByteData> {
-  ByteDataProperty._(String name, int parentIndex, VmoWriter writer)
-      : super(name, parentIndex, writer);
-
-  ByteDataProperty._deleted() : super.deleted();
-}
diff --git a/public/dart/fuchsia_inspect/lib/src/inspect/value.dart b/public/dart/fuchsia_inspect/lib/src/inspect/value.dart
new file mode 100644
index 0000000..da3835f
--- /dev/null
+++ b/public/dart/fuchsia_inspect/lib/src/inspect/value.dart
@@ -0,0 +1,113 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+part of 'inspect.dart';
+
+/// A VMO-backed key-value pair with a [String] key and a typed value.
+abstract class Value<T> {
+  /// The VMO index for this value.
+  /// @nodoc
+  @visibleForTesting
+  final int index;
+
+  /// The writer for the underlying VMO.
+  ///
+  /// Will be set to null if the [Value] has been deleted or could not be
+  /// created in the VMO.
+  /// If so, all actions on this [Value] should be no-ops and not throw.
+  VmoWriter _writer;
+
+  /// Creates a modifiable [Value].
+  Value._(this.index, this._writer) {
+    if (index == invalidIndex) {
+      _writer = null;
+    }
+  }
+
+  /// Creates a [Value] that never does anything.
+  ///
+  /// These are returned when calling methods on a deleted Node,
+  /// or if there is no space for a newly created value in underlying storage.
+  Value.deleted()
+      : _writer = null,
+        index = invalidIndex;
+
+  bool get _isDeleted => _writer == null;
+
+  /// Sets the value of this [Value].
+  void setValue(T value);
+
+  /// Deletes this value from underlying storage.
+  /// Calls on a deleted value have no effect and do not result in an error.
+  void delete() {
+    _writer?.deleteEntity(index);
+    _writer = null;
+  }
+}
+
+/// Sets value on "Property" type values - those which store a byte-vector.
+mixin Property<T> on Value<T> {
+  @override
+  void setValue(T value) {
+    _writer?.setProperty(index, value);
+  }
+}
+
+/// Operations on "Metric" type values - those which store a number.
+mixin Arithmetic<T extends num> on Value<T> {
+  /// Adds [delta] to the value of this metric.
+  void add(T delta) {
+    _writer?.addMetric(index, delta);
+  }
+
+  /// Subtracts [delta] from the value of this metric.
+  void subtract(T delta) {
+    _writer?.subMetric(index, delta);
+  }
+
+  @override
+  void setValue(T value) {
+    _writer?.setMetric(index, value);
+  }
+}
+
+/// A value holding an [int].
+///
+/// Only [Node.intValue()] can create this object.
+class IntValue extends Value<int> with Arithmetic<int> {
+  IntValue._(String name, int parentIndex, VmoWriter writer)
+      : super._(writer.createMetric(parentIndex, name, 0), writer);
+
+  IntValue._deleted() : super.deleted();
+}
+
+/// A value holding a [double].
+///
+/// Only [Node.doubleValue()] can create this object.
+class DoubleValue extends Value<double> with Arithmetic<double> {
+  DoubleValue._(String name, int parentIndex, VmoWriter writer)
+      : super._(writer.createMetric(parentIndex, name, 0.0), writer);
+
+  DoubleValue._deleted() : super.deleted();
+}
+
+/// A value holding a [String].
+///
+/// Only [Node.stringValue()] can create this object.
+class StringValue extends Value<String> with Property<String> {
+  StringValue._(String name, int parentIndex, VmoWriter writer)
+      : super._(writer.createProperty(parentIndex, name), writer);
+
+  StringValue._deleted() : super.deleted();
+}
+
+/// A value holding a [ByteData].
+///
+/// Only [Node.byteDataValue()] can create this object.
+class ByteDataValue extends Value<ByteData> with Property<ByteData> {
+  ByteDataValue._(String name, int parentIndex, VmoWriter writer)
+      : super._(writer.createProperty(parentIndex, name), writer);
+
+  ByteDataValue._deleted() : super.deleted();
+}
diff --git a/public/dart/fuchsia_inspect/lib/src/vmo/vmo_writer.dart b/public/dart/fuchsia_inspect/lib/src/vmo/vmo_writer.dart
index 3c6a64d..dec4408 100644
--- a/public/dart/fuchsia_inspect/lib/src/vmo/vmo_writer.dart
+++ b/public/dart/fuchsia_inspect/lib/src/vmo/vmo_writer.dart
@@ -75,20 +75,6 @@
     }
   }
 
-  /// Deletes the Node.
-  void deleteNode(int nodeIndex) {
-    _beginWork();
-    try {
-      if (nodeIndex < heapStartIndex) {
-        throw Exception('Invalid index {nodeIndex}');
-      }
-      var node = Block.read(_vmo, nodeIndex);
-      _deleteValue(node);
-    } finally {
-      _commit();
-    }
-  }
-
   /// Adds a named Property to a Node.
   int createProperty(int parent, String name) {
     _beginWork();
@@ -146,18 +132,6 @@
     }
   }
 
-  /// Deletes a Property.
-  void deleteProperty(int propertyIndex) {
-    _beginWork();
-    try {
-      var property = Block.read(_vmo, propertyIndex);
-      _freeExtents(property.propertyExtentIndex);
-      _deleteValue(property);
-    } finally {
-      _commit();
-    }
-  }
-
   /// Creates and assigns value.
   int createMetric<T extends num>(int parent, String name, T value) {
     _beginWork();
@@ -222,16 +196,6 @@
     }
   }
 
-  /// Deletes the Metric.
-  void deleteMetric(int metricIndex) {
-    _beginWork();
-    try {
-      _deleteValue(Block.read(_vmo, metricIndex));
-    } finally {
-      _commit();
-    }
-  }
-
   // Creates a new *_VALUE node inside the tree.
   Block _createValue(int parent, String name) {
     var block = _heap.allocateBlock();
@@ -249,19 +213,35 @@
     return block;
   }
 
-  /// Deletes a *_VALUE block.
+  /// Deletes a *_VALUE block (Node, Property, Metric).
   ///
   /// This always unparents the block and frees its NAME block.
-  /// The block itself is freed unless it's a Node with children; in that
-  /// case it's converted to a TOMBSTONE and will be deleted later, when its
-  /// last child is deleted and unparented.
-  void _deleteValue(Block value) {
-    _unparent(value);
-    _heap.freeBlock(Block.read(_vmo, value.nameIndex));
-    if (value.type == BlockType.nodeValue && value.childCount != 0) {
-      value.becomeTombstone();
-    } else {
-      _heap.freeBlock(value);
+  ///
+  /// Special cases:
+  ///  - If index < heapStartIndex, throw.
+  ///  - If block is a Node with children, make it a [BlockType.tombstone]
+  ///   instead of freeing. It will be deleted later, when its
+  ///   last child is deleted and unparented.
+  ///  - If block is a [BlockType.propertyValue], free its extents as well.
+  void deleteEntity(int index) {
+    if (index < heapStartIndex) {
+      throw Exception('Invalid index {nodeIndex}');
+    }
+    _beginWork();
+    try {
+      Block value = Block.read(_vmo, index);
+      _unparent(value);
+      _heap.freeBlock(Block.read(_vmo, value.nameIndex));
+      if (value.type == BlockType.nodeValue && value.childCount != 0) {
+        value.becomeTombstone();
+      } else {
+        if (value.type == BlockType.propertyValue) {
+          _freeExtents(value.propertyExtentIndex);
+        }
+        _heap.freeBlock(value);
+      }
+    } finally {
+      _commit();
     }
   }
 
diff --git a/public/dart/fuchsia_inspect/test/inspect/metric_test.dart b/public/dart/fuchsia_inspect/test/inspect/metric_test.dart
deleted file mode 100644
index a54c560..0000000
--- a/public/dart/fuchsia_inspect/test/inspect/metric_test.dart
+++ /dev/null
@@ -1,199 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// ignore_for_file: implementation_imports
-
-import 'package:fuchsia_inspect/inspect.dart';
-import 'package:fuchsia_inspect/src/inspect/internal/_inspect_impl.dart';
-import 'package:fuchsia_inspect/src/vmo/vmo_holder.dart';
-import 'package:fuchsia_inspect/src/vmo/vmo_writer.dart';
-import 'package:fuchsia_services/services.dart';
-import 'package:test/test.dart';
-
-import '../util.dart';
-
-void main() {
-  VmoHolder vmo;
-  Node node;
-
-  setUp(() {
-    var context = StartupContext.fromStartupInfo();
-    vmo = FakeVmo(512);
-    var writer = VmoWriter(vmo);
-    Inspect inspect = InspectImpl(context, writer);
-    node = inspect.root;
-  });
-
-  group('Int metrics', () {
-    test('are created with value 0', () {
-      var metric = node.createIntMetric('foo');
-
-      expect(readInt(vmo, metric), isZero);
-    });
-
-    test('are written to the VMO when the value is set', () {
-      var metric = node.createIntMetric('eggs')..setValue(12);
-
-      expect(readInt(vmo, metric), 12);
-    });
-
-    test('can be mutated', () {
-      var metric = node.createIntMetric('locusts')..setValue(10);
-      expect(readInt(vmo, metric), 10);
-
-      metric.setValue(1000);
-
-      expect(readInt(vmo, metric), 1000);
-    });
-
-    test('can add arbitrary values', () {
-      var metric = node.createIntMetric('bagels')..setValue(13);
-      expect(readInt(vmo, metric), 13);
-
-      metric.add(13);
-
-      expect(readInt(vmo, metric), 26);
-    });
-
-    test('can subtract arbitrary values', () {
-      var metric = node.createIntMetric('bagels')..setValue(13);
-      expect(readInt(vmo, metric), 13);
-
-      metric.subtract(6);
-
-      expect(readInt(vmo, metric), 7);
-    });
-
-    test('can be deleted', () {
-      var metric = node.createIntMetric('sheep')..delete();
-
-      expect(() => readInt(vmo, metric), throwsA(anything),
-          reason: 'cannot read VMO values from a deleted metric');
-    });
-
-    test('setting a value on an already deleted metric is a no-op', () {
-      var metric = node.createIntMetric('webpages')..delete();
-
-      expect(() => metric.setValue(404), returnsNormally);
-      expect(() => readInt(vmo, metric), throwsA(anything),
-          reason: 'cannot read VMO values from a deleted metric');
-    });
-
-    test('removing an already deleted metric is a no-op', () {
-      var metric = node.createIntMetric('nothing-here')..delete();
-
-      expect(() => metric.delete(), returnsNormally);
-    });
-  });
-
-  group('Double metrics', () {
-    test('are created with value 0', () {
-      var metric = node.createDoubleMetric('foo');
-
-      expect(readDouble(vmo, metric), isZero);
-    });
-
-    test('are written to the VMO when the value is set', () {
-      var metric = node.createDoubleMetric('foo')..setValue(2.5);
-
-      expect(readDouble(vmo, metric), 2.5);
-    });
-
-    test('can be mutated', () {
-      var metric = node.createDoubleMetric('bar')..setValue(3.0);
-      expect(readDouble(vmo, metric), 3.0);
-
-      metric.setValue(3.5);
-
-      expect(readDouble(vmo, metric), 3.5);
-    });
-
-    test('can add arbitrary values', () {
-      var metric = node.createDoubleMetric('cake')..setValue(1.5);
-      expect(readDouble(vmo, metric), 1.5);
-
-      metric.add(1.5);
-
-      expect(readDouble(vmo, metric), 3);
-    });
-
-    test('can subtract arbitrary values', () {
-      var metric = node.createDoubleMetric('cake')..setValue(5);
-      expect(readDouble(vmo, metric), 5);
-
-      metric.subtract(0.5);
-
-      expect(readDouble(vmo, metric), 4.5);
-    });
-
-    test('can be deleted', () {
-      var metric = node.createDoubleMetric('circumference')..delete();
-
-      expect(() => readDouble(vmo, metric), throwsA(anything),
-          reason: 'cannot read VMO values from a deleted metric');
-    });
-
-    test('setting a value on an already deleted metric is a no-op', () {
-      var metric = node.createDoubleMetric('pounds')..delete();
-
-      expect(() => metric.setValue(50.6), returnsNormally);
-      expect(() => readDouble(vmo, metric), throwsA(anything),
-          reason: 'cannot read VMO values from a deleted metric');
-    });
-
-    test('removing an already deleted metric is a no-op', () {
-      var metric = node.createDoubleMetric('nothing-here')..delete();
-
-      expect(() => metric.delete(), returnsNormally);
-    });
-  });
-
-  group('Metric creation', () {
-    test('IntMetrics created twice return the same object', () {
-      var childMetric = node.createIntMetric('banana');
-      var childMetric2 = node.createIntMetric('banana');
-
-      expect(childMetric, isNotNull);
-      expect(childMetric2, isNotNull);
-      expect(childMetric, same(childMetric2));
-    });
-
-    test('IntMetrics created after deletion return different objects', () {
-      var childMetric = node.createIntMetric('banana')..delete();
-      var childMetric2 = node.createIntMetric('banana');
-
-      expect(childMetric, isNotNull);
-      expect(childMetric2, isNotNull);
-      expect(childMetric, isNot(equals(childMetric2)));
-    });
-
-    test('DoubleMetrics created twice return the same object', () {
-      var childMetric = node.createDoubleMetric('banana');
-      var childMetric2 = node.createDoubleMetric('banana');
-
-      expect(childMetric, isNotNull);
-      expect(childMetric2, isNotNull);
-      expect(childMetric, same(childMetric2));
-    });
-
-    test('DoubleMetrics created after deletion return different objects', () {
-      var childMetric = node.createDoubleMetric('banana')..delete();
-      var childMetric2 = node.createDoubleMetric('banana');
-
-      expect(childMetric, isNotNull);
-      expect(childMetric2, isNotNull);
-      expect(childMetric, isNot(equals(childMetric2)));
-    });
-
-    test('Changing IntMetric to DoubleMetric throws', () {
-      node.createIntMetric('banana');
-      expect(() => node.createDoubleMetric('banana'), throwsA(anything));
-    });
-
-    test('Changing DoubleMetric to IntMetric throws', () {
-      node.createDoubleMetric('banana');
-      expect(() => node.createIntMetric('banana'), throwsA(anything));
-    });
-  });
-}
diff --git a/public/dart/fuchsia_inspect/test/inspect/node_test.dart b/public/dart/fuchsia_inspect/test/inspect/node_test.dart
index b289b6d..e21d3b7 100644
--- a/public/dart/fuchsia_inspect/test/inspect/node_test.dart
+++ b/public/dart/fuchsia_inspect/test/inspect/node_test.dart
@@ -27,15 +27,15 @@
   });
 
   test('Child nodes have unique indices from their parents', () {
-    var childNode = root.createChild('banana');
+    var childNode = root.child('banana');
 
     expect(childNode, isNotNull);
     expect(childNode.index, isNot(root.index));
   });
 
   test('Child nodes created twice return the same object', () {
-    var childNode = root.createChild('banana');
-    var childNode2 = root.createChild('banana');
+    var childNode = root.child('banana');
+    var childNode2 = root.child('banana');
 
     expect(childNode, isNotNull);
     expect(childNode2, isNotNull);
@@ -43,8 +43,8 @@
   });
 
   test('Nodes created after deletion return different objects', () {
-    var childNode = root.createChild('banana')..delete();
-    var childNode2 = root.createChild('banana');
+    var childNode = root.child('banana')..delete();
+    var childNode2 = root.child('banana');
 
     expect(childNode, isNotNull);
     expect(childNode2, isNotNull);
@@ -52,15 +52,15 @@
   });
 
   test('Child nodes have unique indices from their siblings', () {
-    var child1 = root.createChild('thing1');
-    var child2 = root.createChild('thing2');
+    var child1 = root.child('thing1');
+    var child2 = root.child('thing2');
 
     expect(child1.index, isNot(child2.index));
   });
 
   test('Deleting root node has no effect', () {
     root.delete();
-    var child = root.createChild('sheep');
+    var child = root.child('sheep');
     expect(() => readNameIndex(vmo, child), returnsNormally);
   });
 
@@ -68,11 +68,11 @@
     Node deletedNode;
 
     setUp(() {
-      deletedNode = root.createChild('sheep')..delete();
+      deletedNode = root.child('sheep')..delete();
     });
 
     test('can be deleted (more than once)', () {
-      var child = deletedNode.createChild('sheep')..delete();
+      var child = deletedNode.child('sheep')..delete();
       expect(() => readNameIndex(vmo, deletedNode), throwsA(anything),
           reason: 'cannot read VMO values from a deleted node');
       expect(() => deletedNode.delete(), returnsNormally);
@@ -83,47 +83,41 @@
 
     test('Creating a child on an already deleted node is a no-op', () {
       Node grandchild;
-      expect(
-          () => grandchild = deletedNode.createChild('404'), returnsNormally);
-      expect(() => grandchild.createChild('404'), returnsNormally);
+      expect(() => grandchild = deletedNode.child('404'), returnsNormally);
+      expect(() => grandchild.child('404'), returnsNormally);
       expect(() => readNameIndex(vmo, grandchild), throwsA(anything),
           reason: 'cannot read VMO values from a deleted node');
     });
 
-    test('Creating an IntMetric on an already deleted node is a no-op', () {
-      IntMetric metric;
-      expect(
-          () => metric = deletedNode.createIntMetric('404'), returnsNormally);
-      expect(() => metric.setValue(404), returnsNormally);
-      expect(() => readInt(vmo, metric), throwsA(anything),
+    test('Creating an IntValue on an already deleted node is a no-op', () {
+      IntValue value;
+      expect(() => value = deletedNode.intValue('404'), returnsNormally);
+      expect(() => value.setValue(404), returnsNormally);
+      expect(() => readInt(vmo, value), throwsA(anything),
           reason: 'cannot read VMO values from a deleted node');
     });
 
-    test('Creating a DoubleMetric on an already deleted node is a no-op', () {
-      DoubleMetric metric;
-      expect(() => metric = deletedNode.createDoubleMetric('404'),
-          returnsNormally);
-      expect(() => metric.setValue(404), returnsNormally);
-      expect(() => readDouble(vmo, metric), throwsA(anything),
+    test('Creating a DoubleValue on an already deleted node is a no-op', () {
+      DoubleValue value;
+      expect(() => value = deletedNode.doubleValue('404'), returnsNormally);
+      expect(() => value.setValue(404), returnsNormally);
+      expect(() => readDouble(vmo, value), throwsA(anything),
           reason: 'cannot read VMO values from a deleted node');
     });
 
-    test('Creating a StringProperty on an already deleted node is a no-op', () {
-      StringProperty property;
-      expect(() => property = deletedNode.createStringProperty('404'),
-          returnsNormally);
-      expect(() => property.setValue('404'), returnsNormally);
-      expect(() => readProperty(vmo, property.index), throwsA(anything),
+    test('Creating a StringValue on an already deleted node is a no-op', () {
+      StringValue value;
+      expect(() => value = deletedNode.stringValue('404'), returnsNormally);
+      expect(() => value.setValue('404'), returnsNormally);
+      expect(() => readProperty(vmo, value.index), throwsA(anything),
           reason: 'cannot read VMO values from a deleted property');
     });
 
-    test('Creating a ByteDataProperty on an already deleted node is a no-op',
-        () {
-      ByteDataProperty property;
-      expect(() => property = deletedNode.createByteDataProperty('404'),
-          returnsNormally);
-      expect(() => property.setValue(toByteData('fuchsia')), returnsNormally);
-      expect(() => readProperty(vmo, property.index), throwsA(anything),
+    test('Creating a ByteDataValue on an already deleted node is a no-op', () {
+      ByteDataValue value;
+      expect(() => value = deletedNode.byteDataValue('404'), returnsNormally);
+      expect(() => value.setValue(toByteData('fuchsia')), returnsNormally);
+      expect(() => readProperty(vmo, value.index), throwsA(anything),
           reason: 'cannot read VMO values from a deleted property');
     });
   });
@@ -132,42 +126,42 @@
     Node normalNode;
 
     setUp(() {
-      normalNode = root.createChild('sheep');
+      normalNode = root.child('sheep');
     });
 
     test('child Node of deleted Node is deleted', () {
-      var grandchild = normalNode.createChild('goats');
+      var grandchild = normalNode.child('goats');
       normalNode.delete();
       expect(() => readNameIndex(vmo, grandchild), throwsA(anything),
           reason: 'child Node of deleted Node should be deleted');
     });
 
-    test('child IntMetric of deleted Node is deleted', () {
-      var intMetric = normalNode.createIntMetric('llamas');
+    test('child IntValue of deleted Node is deleted', () {
+      var intValue = normalNode.intValue('llamas');
       normalNode.delete();
-      expect(() => readInt(vmo, intMetric), throwsA(anything),
-          reason: 'child IntMetric of deleted Node should be deleted');
+      expect(() => readInt(vmo, intValue), throwsA(anything),
+          reason: 'child IntValue of deleted Node should be deleted');
     });
 
-    test('child DoubleMetric of deleted Node is deleted', () {
-      var doubleMetric = normalNode.createDoubleMetric('emus');
+    test('child DoubleValue of deleted Node is deleted', () {
+      var doubleValue = normalNode.doubleValue('emus');
       normalNode.delete();
-      expect(() => readDouble(vmo, doubleMetric), throwsA(anything),
-          reason: 'child DoubleMetric of deleted Node should be deleted');
+      expect(() => readDouble(vmo, doubleValue), throwsA(anything),
+          reason: 'child DoubleValue of deleted Node should be deleted');
     });
 
-    test('child StringProperty of deleted Node is deleted', () {
-      var stringProperty = normalNode.createStringProperty('okapis');
+    test('child StringValue of deleted Node is deleted', () {
+      var stringValue = normalNode.stringValue('okapis');
       normalNode.delete();
-      expect(() => readProperty(vmo, stringProperty.index), throwsA(anything),
-          reason: 'child StringProperty of deleted Node should be deleted');
+      expect(() => readProperty(vmo, stringValue.index), throwsA(anything),
+          reason: 'child StringValue of deleted Node should be deleted');
     });
 
-    test('child ByteDataProperty of deleted Node is deleted', () {
-      var byteDataProperty = normalNode.createByteDataProperty('capybaras');
+    test('child ByteDataValue of deleted Node is deleted', () {
+      var byteDataValue = normalNode.byteDataValue('capybaras');
       normalNode.delete();
-      expect(() => readProperty(vmo, byteDataProperty.index), throwsA(anything),
-          reason: 'child ByteDataProperty of deleted Node should be deleted');
+      expect(() => readProperty(vmo, byteDataValue.index), throwsA(anything),
+          reason: 'child ByteDataValue of deleted Node should be deleted');
     });
   });
 
@@ -182,38 +176,38 @@
     });
 
     test('If no space, creation gives a deleted Node', () {
-      var missingNode = tinyRoot.createChild('missing');
-      expect(() => missingNode.createChild('more missing'), returnsNormally);
+      var missingNode = tinyRoot.child('missing');
+      expect(() => missingNode.child('more missing'), returnsNormally);
       expect(() => readNameIndex(vmo, missingNode), throwsA(anything),
+          reason: 'cannot read VMO values from a deleted node');
+    });
+
+    test('If no space, creation gives a deleted IntValue', () {
+      var missingValue = tinyRoot.intValue('missing');
+      expect(() => missingValue.setValue(1), returnsNormally);
+      expect(() => readInt(vmo, missingValue), throwsA(anything),
+          reason: 'cannot read VMO values from a deleted metric');
+    });
+
+    test('If no space, creation gives a deleted DoubleValue', () {
+      var missingValue = tinyRoot.doubleValue('missing');
+      expect(() => missingValue.setValue(1.0), returnsNormally);
+      expect(() => readDouble(vmo, missingValue), throwsA(anything),
+          reason: 'cannot read VMO values from a deleted metric');
+    });
+
+    test('If no space, creation gives a deleted StringValue', () {
+      var missingValue = tinyRoot.stringValue('missing');
+      expect(() => missingValue.setValue('something'), returnsNormally);
+      expect(() => readProperty(vmo, missingValue.index), throwsA(anything),
           reason: 'cannot read VMO values from a deleted property');
     });
 
-    test('If no space, creation gives a deleted IntMetric', () {
-      var missingMetric = tinyRoot.createIntMetric('missing');
-      expect(() => missingMetric.setValue(1), returnsNormally);
-      expect(() => readInt(vmo, missingMetric), throwsA(anything),
-          reason: 'cannot read VMO values from a deleted property');
-    });
-
-    test('If no space, creation gives a deleted DoubleMetric', () {
-      var missingMetric = tinyRoot.createDoubleMetric('missing');
-      expect(() => missingMetric.setValue(1.0), returnsNormally);
-      expect(() => readDouble(vmo, missingMetric), throwsA(anything),
-          reason: 'cannot read VMO values from a deleted property');
-    });
-
-    test('If no space, creation gives a deleted StringProperty', () {
-      var missingProperty = tinyRoot.createStringProperty('missing');
-      expect(() => missingProperty.setValue('something'), returnsNormally);
-      expect(() => readProperty(vmo, missingProperty.index), throwsA(anything),
-          reason: 'cannot read VMO values from a deleted property');
-    });
-
-    test('If no space, creation gives a deleted ByteDataProperty', () {
+    test('If no space, creation gives a deleted ByteDataValue', () {
       var bytes = toByteData('this will not set');
-      var missingProperty = tinyRoot.createByteDataProperty('missing');
-      expect(() => missingProperty.setValue(bytes), returnsNormally);
-      expect(() => readProperty(vmo, missingProperty.index), throwsA(anything),
+      var missingValue = tinyRoot.byteDataValue('missing');
+      expect(() => missingValue.setValue(bytes), returnsNormally);
+      expect(() => readProperty(vmo, missingValue.index), throwsA(anything),
           reason: 'cannot read VMO values from a deleted property');
     });
   });
diff --git a/public/dart/fuchsia_inspect/test/inspect/property_test.dart b/public/dart/fuchsia_inspect/test/inspect/property_test.dart
deleted file mode 100644
index 4f284b1..0000000
--- a/public/dart/fuchsia_inspect/test/inspect/property_test.dart
+++ /dev/null
@@ -1,173 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// ignore_for_file: implementation_imports
-
-import 'package:fuchsia_inspect/inspect.dart';
-import 'package:fuchsia_inspect/src/inspect/internal/_inspect_impl.dart';
-import 'package:fuchsia_inspect/src/vmo/util.dart';
-import 'package:fuchsia_inspect/src/vmo/vmo_holder.dart';
-import 'package:fuchsia_inspect/src/vmo/vmo_writer.dart';
-import 'package:fuchsia_services/services.dart';
-import 'package:test/test.dart';
-
-import '../util.dart';
-
-void main() {
-  VmoHolder vmo;
-  Node node;
-
-  setUp(() {
-    var context = StartupContext.fromStartupInfo();
-    vmo = FakeVmo(512);
-    var writer = VmoWriter(vmo);
-    Inspect inspect = InspectImpl(context, writer);
-    node = inspect.root;
-  });
-
-  group('String properties', () {
-    test('are written to the VMO when the value is set', () {
-      var property = node.createStringProperty('color')..setValue('fuchsia');
-
-      expect(readProperty(vmo, property.index),
-          equalsByteData(toByteData('fuchsia')));
-    });
-
-    test('can be mutated', () {
-      var property = node.createStringProperty('breakfast')
-        ..setValue('pancakes');
-
-      expect(readProperty(vmo, property.index),
-          equalsByteData(toByteData('pancakes')));
-
-      property.setValue('waffles');
-      expect(readProperty(vmo, property.index),
-          equalsByteData(toByteData('waffles')));
-    });
-
-    test('can be deleted', () {
-      var property = node.createStringProperty('scallops');
-      var index = property.index;
-
-      property.delete();
-
-      expect(() => readProperty(vmo, index), throwsA(anything),
-          reason: 'cannot read VMO values from a deleted property');
-    });
-
-    test('setting a value on an already deleted property is a no-op', () {
-      var property = node.createStringProperty('paella');
-      var index = property.index;
-      property.delete();
-
-      expect(() => property.setValue('this will not set'), returnsNormally);
-      expect(() => readProperty(vmo, index), throwsA(anything),
-          reason: 'cannot read VMO values from a deleted property');
-    });
-
-    test('removing an already deleted property is a no-op', () {
-      var property = node.createStringProperty('nothing-here')..delete();
-
-      expect(() => property.delete(), returnsNormally);
-    });
-  });
-
-  group('ByteData properties', () {
-    test('are written to the VMO when the value is set', () {
-      var bytes = toByteData('fuchsia');
-      var property = node.createByteDataProperty('color')..setValue(bytes);
-
-      expect(readProperty(vmo, property.index), equalsByteData(bytes));
-    });
-
-    test('can be mutated', () {
-      var pancakes = toByteData('pancakes');
-      var property = node.createByteDataProperty('breakfast')
-        ..setValue(pancakes);
-
-      expect(readProperty(vmo, property.index), equalsByteData(pancakes));
-
-      var waffles = toByteData('waffles');
-      property.setValue(waffles);
-      expect(readProperty(vmo, property.index), equalsByteData(waffles));
-    });
-
-    test('can be deleted', () {
-      var property = node.createByteDataProperty('scallops');
-      var index = property.index;
-
-      property.delete();
-
-      expect(() => readProperty(vmo, index), throwsA(anything),
-          reason: 'cannot read VMO values from a deleted property');
-    });
-
-    test('setting a value on an already deleted property is a no-op', () {
-      var property = node.createByteDataProperty('paella');
-      var index = property.index;
-      property.delete();
-
-      var bytes = toByteData('this will not set');
-      expect(() => property.setValue(bytes), returnsNormally);
-      expect(() => readProperty(vmo, index), throwsA(anything),
-          reason: 'cannot read VMO values from a deleted property');
-    });
-
-    test('removing an already deleted property is a no-op', () {
-      var property = node.createByteDataProperty('nothing-here')..delete();
-
-      expect(() => property.delete(), returnsNormally);
-    });
-  });
-
-  group('Property creation', () {
-    test('StringProperties created twice return the same object', () {
-      var childMetric = node.createStringProperty('banana');
-      var childMetric2 = node.createStringProperty('banana');
-
-      expect(childMetric, isNotNull);
-      expect(childMetric2, isNotNull);
-      expect(childMetric, same(childMetric2));
-    });
-
-    test('StringProperties created after deletion return different objects',
-        () {
-      var childMetric = node.createStringProperty('banana')..delete();
-      var childMetric2 = node.createStringProperty('banana');
-
-      expect(childMetric, isNotNull);
-      expect(childMetric2, isNotNull);
-      expect(childMetric, isNot(equals(childMetric2)));
-    });
-
-    test('ByteDataProperties created twice return the same object', () {
-      var childMetric = node.createByteDataProperty('banana');
-      var childMetric2 = node.createByteDataProperty('banana');
-
-      expect(childMetric, isNotNull);
-      expect(childMetric2, isNotNull);
-      expect(childMetric, same(childMetric2));
-    });
-
-    test('ByteDataProperties created after deletion return different objects',
-        () {
-      var childMetric = node.createByteDataProperty('banana')..delete();
-      var childMetric2 = node.createByteDataProperty('banana');
-
-      expect(childMetric, isNotNull);
-      expect(childMetric2, isNotNull);
-      expect(childMetric, isNot(equals(childMetric2)));
-    });
-
-    test('Changing StringProperty to ByteDataProperty throws', () {
-      node.createStringProperty('banana');
-      expect(() => node.createByteDataProperty('banana'), throwsA(anything));
-    });
-
-    test('Changing ByteDataProperty to StringProperty throws', () {
-      node.createByteDataProperty('banana');
-      expect(() => node.createStringProperty('banana'), throwsA(anything));
-    });
-  });
-}
diff --git a/public/dart/fuchsia_inspect/test/inspect/value_test.dart b/public/dart/fuchsia_inspect/test/inspect/value_test.dart
new file mode 100644
index 0000000..6fcad60
--- /dev/null
+++ b/public/dart/fuchsia_inspect/test/inspect/value_test.dart
@@ -0,0 +1,454 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// ignore_for_file: implementation_imports
+
+import 'package:fuchsia_inspect/inspect.dart';
+import 'package:fuchsia_inspect/src/inspect/internal/_inspect_impl.dart';
+import 'package:fuchsia_inspect/src/vmo/util.dart';
+import 'package:fuchsia_inspect/src/vmo/vmo_holder.dart';
+import 'package:fuchsia_inspect/src/vmo/vmo_writer.dart';
+import 'package:fuchsia_services/services.dart';
+import 'package:test/test.dart';
+
+import '../util.dart';
+
+void main() {
+  VmoHolder vmo;
+  Node node;
+
+  setUp(() {
+    var context = StartupContext.fromStartupInfo();
+    vmo = FakeVmo(512);
+    var writer = VmoWriter(vmo);
+    Inspect inspect = InspectImpl(context, writer);
+    node = inspect.root;
+  });
+
+  group('String values', () {
+    test('are written to the VMO when the value is set', () {
+      var value = node.stringValue('color')..setValue('fuchsia');
+
+      expect(readProperty(vmo, value.index),
+          equalsByteData(toByteData('fuchsia')));
+    });
+
+    test('can be mutated', () {
+      var value = node.stringValue('breakfast')..setValue('pancakes');
+
+      expect(readProperty(vmo, value.index),
+          equalsByteData(toByteData('pancakes')));
+
+      value.setValue('waffles');
+      expect(readProperty(vmo, value.index),
+          equalsByteData(toByteData('waffles')));
+    });
+
+    test('can be deleted', () {
+      var value = node.stringValue('scallops');
+      var index = value.index;
+
+      value.delete();
+
+      expect(() => readProperty(vmo, index),
+          throwsA(const TypeMatcher<StateError>()),
+          reason: 'cannot read VMO values from a deleted property');
+    });
+
+    test('setting a value on an already deleted value is a no-op', () {
+      var value = node.stringValue('paella');
+      var index = value.index;
+      value.delete();
+
+      expect(() => value.setValue('this will not set'), returnsNormally);
+      expect(() => readProperty(vmo, index),
+          throwsA(const TypeMatcher<StateError>()),
+          reason: 'cannot read VMO values from a deleted property');
+    });
+
+    test('removing an already deleted value is a no-op', () {
+      var value = node.stringValue('nothing-here')..delete();
+
+      expect(() => value.delete(), returnsNormally);
+    });
+  });
+
+  group('ByteData values', () {
+    test('are written to the VMO when the value is set', () {
+      var bytes = toByteData('fuchsia');
+      var value = node.byteDataValue('color')..setValue(bytes);
+
+      expect(readProperty(vmo, value.index), equalsByteData(bytes));
+    });
+
+    test('can be mutated', () {
+      var pancakes = toByteData('pancakes');
+      var value = node.byteDataValue('breakfast')..setValue(pancakes);
+
+      expect(readProperty(vmo, value.index), equalsByteData(pancakes));
+
+      var waffles = toByteData('waffles');
+      value.setValue(waffles);
+      expect(readProperty(vmo, value.index), equalsByteData(waffles));
+    });
+
+    test('can be deleted', () {
+      var value = node.byteDataValue('scallops');
+      var index = value.index;
+
+      value.delete();
+
+      expect(() => readProperty(vmo, index),
+          throwsA(const TypeMatcher<StateError>()),
+          reason: 'cannot read VMO values from a deleted property');
+    });
+
+    test('setting a value on an already deleted value is a no-op', () {
+      var value = node.byteDataValue('paella');
+      var index = value.index;
+      value.delete();
+
+      var bytes = toByteData('this will not set');
+      expect(() => value.setValue(bytes), returnsNormally);
+      expect(() => readProperty(vmo, index),
+          throwsA(const TypeMatcher<StateError>()),
+          reason: 'cannot read VMO values from a deleted property');
+    });
+
+    test('removing an already deleted value is a no-op', () {
+      var value = node.byteDataValue('nothing-here')..delete();
+
+      expect(() => value.delete(), returnsNormally);
+    });
+  });
+
+  group('Property creation (byte-vector Values)', () {
+    test('StringValues created twice return the same object', () {
+      var childValue = node.stringValue('banana');
+      var childValue2 = node.stringValue('banana');
+
+      expect(childValue, isNotNull);
+      expect(childValue2, isNotNull);
+      expect(childValue, same(childValue2));
+    });
+
+    test('StringValues created after deletion return different objects', () {
+      var childValue = node.stringValue('banana')..delete();
+      var childValue2 = node.stringValue('banana');
+
+      expect(childValue, isNotNull);
+      expect(childValue2, isNotNull);
+      expect(childValue, isNot(equals(childValue2)));
+    });
+
+    test('ByteDataValues created twice return the same object', () {
+      var childValue = node.byteDataValue('banana');
+      var childValue2 = node.byteDataValue('banana');
+
+      expect(childValue, isNotNull);
+      expect(childValue2, isNotNull);
+      expect(childValue, same(childValue2));
+    });
+
+    test('ByteDataValues created after deletion return different objects', () {
+      var childValue = node.byteDataValue('banana')..delete();
+      var childValue2 = node.byteDataValue('banana');
+
+      expect(childValue, isNotNull);
+      expect(childValue2, isNotNull);
+      expect(childValue, isNot(equals(childValue2)));
+    });
+
+    test('Changing StringValue to ByteDataValue throws', () {
+      node.stringValue('banana');
+      expect(() => node.byteDataValue('banana'),
+          throwsA(const TypeMatcher<InspectStateError>()));
+    });
+
+    test('Changing StringValue to IntValue throws', () {
+      node.stringValue('banana');
+      expect(() => node.intValue('banana'),
+          throwsA(const TypeMatcher<InspectStateError>()));
+    });
+
+    test('Changing StringValue to DoubleValue throws', () {
+      node.stringValue('banana');
+      expect(() => node.doubleValue('banana'),
+          throwsA(const TypeMatcher<InspectStateError>()));
+    });
+
+    test('Changing ByteDataValue to StringValue throws', () {
+      node.byteDataValue('banana');
+      expect(() => node.stringValue('banana'),
+          throwsA(const TypeMatcher<InspectStateError>()));
+    });
+
+    test('Changing ByteDataValue to IntValue throws', () {
+      node.byteDataValue('banana');
+      expect(() => node.intValue('banana'),
+          throwsA(const TypeMatcher<InspectStateError>()));
+    });
+
+    test('Changing ByteDataValue to DoubleValue throws', () {
+      node.byteDataValue('banana');
+      expect(() => node.doubleValue('banana'),
+          throwsA(const TypeMatcher<InspectStateError>()));
+    });
+
+    test('If no space, creation gives a deleted StringValue', () {
+      var tinyVmo = FakeVmo(64);
+      var writer = VmoWriter(tinyVmo);
+      var context = StartupContext.fromStartupInfo();
+      Inspect inspect = InspectImpl(context, writer);
+      var tinyRoot = inspect.root;
+      var missingValue = tinyRoot.stringValue('missing');
+      expect(() => missingValue.setValue('something'), returnsNormally);
+      expect(() => readProperty(vmo, missingValue.index),
+          throwsA(const TypeMatcher<StateError>()),
+          reason: 'cannot read VMO values from a deleted property');
+    });
+
+    test('If no space, creation gives a deleted ByteDataValue', () {
+      var tinyVmo = FakeVmo(64);
+      var writer = VmoWriter(tinyVmo);
+      var context = StartupContext.fromStartupInfo();
+      Inspect inspect = InspectImpl(context, writer);
+      var tinyRoot = inspect.root;
+      var bytes = toByteData('this will not set');
+      var missingValue = tinyRoot.byteDataValue('missing');
+      expect(() => missingValue.setValue(bytes), returnsNormally);
+      expect(() => readProperty(vmo, missingValue.index),
+          throwsA(const TypeMatcher<StateError>()),
+          reason: 'cannot read VMO values from a deleted property');
+    });
+  });
+
+  group('Int Values', () {
+    test('are created with value 0', () {
+      var value = node.intValue('foo');
+
+      expect(readInt(vmo, value), isZero);
+    });
+
+    test('are written to the VMO when the value is set', () {
+      var value = node.intValue('eggs')..setValue(12);
+
+      expect(readInt(vmo, value), 12);
+    });
+
+    test('can be mutated', () {
+      var value = node.intValue('locusts')..setValue(10);
+      expect(readInt(vmo, value), 10);
+
+      value.setValue(1000);
+
+      expect(readInt(vmo, value), 1000);
+    });
+
+    test('can add arbitrary values', () {
+      var value = node.intValue('bagels')..setValue(13);
+      expect(readInt(vmo, value), 13);
+
+      value.add(13);
+
+      expect(readInt(vmo, value), 26);
+    });
+
+    test('can subtract arbitrary values', () {
+      var value = node.intValue('bagels')..setValue(13);
+      expect(readInt(vmo, value), 13);
+
+      value.subtract(6);
+
+      expect(readInt(vmo, value), 7);
+    });
+
+    test('can be deleted', () {
+      var value = node.intValue('sheep')..delete();
+
+      expect(
+          () => readInt(vmo, value), throwsA(const TypeMatcher<StateError>()),
+          reason: 'cannot read VMO values from a deleted value');
+    });
+
+    test('setting a value on an already deleted value is a no-op', () {
+      var value = node.intValue('webpages')..delete();
+
+      expect(() => value.setValue(404), returnsNormally);
+      expect(
+          () => readInt(vmo, value), throwsA(const TypeMatcher<StateError>()),
+          reason: 'cannot read VMO values from a deleted value');
+    });
+
+    test('removing an already deleted value is a no-op', () {
+      var value = node.intValue('nothing-here')..delete();
+
+      expect(() => value.delete(), returnsNormally);
+    });
+  });
+
+  group('DoubleValues', () {
+    test('are created with value 0', () {
+      var value = node.doubleValue('foo');
+
+      expect(readDouble(vmo, value), isZero);
+    });
+
+    test('are written to the VMO when the value is set', () {
+      var value = node.doubleValue('foo')..setValue(2.5);
+
+      expect(readDouble(vmo, value), 2.5);
+    });
+
+    test('can be mutated', () {
+      var value = node.doubleValue('bar')..setValue(3.0);
+      expect(readDouble(vmo, value), 3.0);
+
+      value.setValue(3.5);
+
+      expect(readDouble(vmo, value), 3.5);
+    });
+
+    test('can add arbitrary values', () {
+      var value = node.doubleValue('cake')..setValue(1.5);
+      expect(readDouble(vmo, value), 1.5);
+
+      value.add(1.5);
+
+      expect(readDouble(vmo, value), 3);
+    });
+
+    test('can subtract arbitrary values', () {
+      var value = node.doubleValue('cake')..setValue(5);
+      expect(readDouble(vmo, value), 5);
+
+      value.subtract(0.5);
+
+      expect(readDouble(vmo, value), 4.5);
+    });
+
+    test('can be deleted', () {
+      var value = node.doubleValue('circumference')..delete();
+
+      expect(() => readDouble(vmo, value),
+          throwsA(const TypeMatcher<StateError>()),
+          reason: 'cannot read VMO values from a deleted value');
+    });
+
+    test('setting a value on an already deleted value is a no-op', () {
+      var value = node.doubleValue('pounds')..delete();
+
+      expect(() => value.setValue(50.6), returnsNormally);
+      expect(() => readDouble(vmo, value),
+          throwsA(const TypeMatcher<StateError>()),
+          reason: 'cannot read VMO values from a deleted value');
+    });
+
+    test('removing an already deleted value is a no-op', () {
+      var value = node.doubleValue('nothing-here')..delete();
+
+      expect(() => value.delete(), returnsNormally);
+    });
+  });
+
+  group('value creation', () {
+    test('IntValues created twice return the same object', () {
+      var childValue = node.intValue('banana');
+      var childValue2 = node.intValue('banana');
+
+      expect(childValue, isNotNull);
+      expect(childValue2, isNotNull);
+      expect(childValue, same(childValue2));
+    });
+
+    test('IntValues created after deletion return different objects', () {
+      var childValue = node.intValue('banana')..delete();
+      var childValue2 = node.intValue('banana');
+
+      expect(childValue, isNotNull);
+      expect(childValue2, isNotNull);
+      expect(childValue, isNot(equals(childValue2)));
+    });
+
+    test('DoubleValues created twice return the same object', () {
+      var childValue = node.doubleValue('banana');
+      var childValue2 = node.doubleValue('banana');
+
+      expect(childValue, isNotNull);
+      expect(childValue2, isNotNull);
+      expect(childValue, same(childValue2));
+    });
+
+    test('DoubleValues created after deletion return different objects', () {
+      var childValue = node.doubleValue('banana')..delete();
+      var childValue2 = node.doubleValue('banana');
+
+      expect(childValue, isNotNull);
+      expect(childValue2, isNotNull);
+      expect(childValue, isNot(equals(childValue2)));
+    });
+
+    test('Changing IntValue to DoubleValue throws', () {
+      node.intValue('banana');
+      expect(() => node.doubleValue('banana'),
+          throwsA(const TypeMatcher<InspectStateError>()));
+    });
+
+    test('Changing IntValue to StringValue throws', () {
+      node.intValue('banana');
+      expect(() => node.stringValue('banana'),
+          throwsA(const TypeMatcher<InspectStateError>()));
+    });
+
+    test('Changing IntValue to ByteDataValue throws', () {
+      node.intValue('banana');
+      expect(() => node.byteDataValue('banana'),
+          throwsA(const TypeMatcher<InspectStateError>()));
+    });
+
+    test('Changing DoubleValue to IntValue throws', () {
+      node.doubleValue('banana');
+      expect(() => node.intValue('banana'),
+          throwsA(const TypeMatcher<InspectStateError>()));
+    });
+
+    test('Changing DoubleValue to StringValue throws', () {
+      node.doubleValue('banana');
+      expect(() => node.stringValue('banana'),
+          throwsA(const TypeMatcher<InspectStateError>()));
+    });
+
+    test('Changing DoubleValue to ByteDataValue throws', () {
+      node.doubleValue('banana');
+      expect(() => node.byteDataValue('banana'),
+          throwsA(const TypeMatcher<InspectStateError>()));
+    });
+
+    test('If no space, creation gives a deleted IntValue', () {
+      var tinyVmo = FakeVmo(64);
+      var writer = VmoWriter(tinyVmo);
+      var context = StartupContext.fromStartupInfo();
+      Inspect inspect = InspectImpl(context, writer);
+      var tinyRoot = inspect.root;
+      var missingValue = tinyRoot.intValue('missing');
+      expect(() => missingValue.setValue(1), returnsNormally);
+      expect(() => readInt(vmo, missingValue),
+          throwsA(const TypeMatcher<StateError>()),
+          reason: 'cannot read VMO values from a deleted property');
+    });
+
+    test('If no space, creation gives a deleted DoubleValue', () {
+      var tinyVmo = FakeVmo(64);
+      var writer = VmoWriter(tinyVmo);
+      var context = StartupContext.fromStartupInfo();
+      Inspect inspect = InspectImpl(context, writer);
+      var tinyRoot = inspect.root;
+      var missingValue = tinyRoot.doubleValue('missing');
+      expect(() => missingValue.setValue(1.0), returnsNormally);
+      expect(() => readDouble(vmo, missingValue),
+          throwsA(const TypeMatcher<StateError>()),
+          reason: 'cannot read VMO values from a deleted property');
+    });
+  });
+}
diff --git a/public/dart/fuchsia_inspect/test/util.dart b/public/dart/fuchsia_inspect/test/util.dart
index 5fd1b18..68aa058 100644
--- a/public/dart/fuchsia_inspect/test/util.dart
+++ b/public/dart/fuchsia_inspect/test/util.dart
@@ -189,13 +189,13 @@
 int readNameIndex(FakeVmo vmo, Node node) =>
     Block.read(vmo, node.index).nameIndex;
 
-/// Returns the int value of [metric] in [vmo].
-int readInt(FakeVmo vmo, IntMetric metric) =>
-    Block.read(vmo, metric.index).intValue;
+/// Returns the int value of [value] in [vmo].
+int readInt(FakeVmo vmo, IntValue value) =>
+    Block.read(vmo, value.index).intValue;
 
-/// Returns the double value of [metric] in [vmo].
-double readDouble(FakeVmo vmo, DoubleMetric metric) =>
-    Block.read(vmo, metric.index).doubleValue;
+/// Returns the double value of [value] in [vmo].
+double readDouble(FakeVmo vmo, DoubleValue value) =>
+    Block.read(vmo, value.index).doubleValue;
 
 /// A matcher that matches ByteData values as unit8 lists.
 Matcher equalsByteData(ByteData data) => _EqualsByteData(data);
diff --git a/public/dart/fuchsia_inspect/test/vmo/vmo_writer_test.dart b/public/dart/fuchsia_inspect/test/vmo/vmo_writer_test.dart
index 11183b6..7903c02 100644
--- a/public/dart/fuchsia_inspect/test/vmo/vmo_writer_test.dart
+++ b/public/dart/fuchsia_inspect/test/vmo/vmo_writer_test.dart
@@ -76,7 +76,7 @@
         Test(_nameFor(vmo, child), toByteData('child')),
         Test(writer.rootNode, 1)
       ]);
-      writer.deleteNode(child);
+      writer.deleteEntity(child);
       // Deleting a node without children should free it and its name.
       // root node should have 0 children.
       checker.check(-2, [Test(writer.rootNode, 0)]);
@@ -99,7 +99,7 @@
       checker.check(0, [Test(intMetric, -1)]);
       writer.setMetric(intMetric, 2);
       checker.check(0, [Test(intMetric, 2)]);
-      writer.deleteMetric(intMetric);
+      writer.deleteEntity(intMetric);
       checker.check(-2, [Test(writer.rootNode, 0)]);
     });
 
@@ -121,7 +121,7 @@
       checker.check(0, [Test(doubleMetric, -0.5)]);
       writer.setMetric(doubleMetric, 1.5);
       checker.check(0, [Test(doubleMetric, 1.5)]);
-      writer.deleteMetric(doubleMetric);
+      writer.deleteEntity(doubleMetric);
       checker.check(-2, [Test(writer.rootNode, 0)]);
     });
 
@@ -139,7 +139,7 @@
       final bytes = ByteData(8)..setFloat64(0, 1.23);
       writer.setProperty(property, bytes);
       checker.check(1, [Test(_extentFor(vmo, property), bytes)]);
-      writer.deleteProperty(property);
+      writer.deleteEntity(property);
       // Property, its extent, and its name should be freed. Its parent should
       // have one fewer children (0 in this case).
       checker.check(-3, [Test(writer.rootNode, 0)]);
@@ -156,13 +156,13 @@
       checker.check(2, [Test(writer.rootNode, 1), Test(parent, 1)]);
       final metric = writer.createMetric(child, 'metric', 1);
       checker.check(2, [Test(child, 1), Test(parent, 1)]);
-      writer.deleteNode(child);
+      writer.deleteEntity(child);
       // Now child should be a tombstone; only its name should be freed.
       checker.check(-1, [Test(child, 1), Test(parent, 0)]);
-      writer.deleteNode(parent);
+      writer.deleteEntity(parent);
       // Parent, plus its name, should be freed; root should have no children.
       checker.check(-2, [Test(writer.rootNode, 0)]);
-      writer.deleteNode(metric);
+      writer.deleteEntity(metric);
       // Metric, its name, and child should be freed.
       checker.check(-3, []);
       // Make sure we can still create nodes on the root