diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/Float32Attribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/Float32Attribute.cs
new file mode 100644
index 0000000..ca5798e
--- /dev/null
+++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/Float32Attribute.cs
@@ -0,0 +1,17 @@
+using JetBrains.Annotations;
+using NexusMods.MnemonicDB.Abstractions.ValueSerializers;
+
+namespace NexusMods.MnemonicDB.Abstractions.Attributes;
+
+///
+/// An attribute that holds a float32 value.
+///
+[PublicAPI]
+public sealed class Float32Attribute(string ns, string name) : ScalarAttribute(ns, name)
+{
+ ///
+ protected override float ToLowLevel(float value) => value;
+
+ ///
+ protected override float FromLowLevel(float value, AttributeResolver resolver) => value;
+}
diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/Float64Attribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/Float64Attribute.cs
new file mode 100644
index 0000000..2c5b55a
--- /dev/null
+++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/Float64Attribute.cs
@@ -0,0 +1,17 @@
+using JetBrains.Annotations;
+using NexusMods.MnemonicDB.Abstractions.ValueSerializers;
+
+namespace NexusMods.MnemonicDB.Abstractions.Attributes;
+
+///
+/// An attribute that holds a float64 value.
+///
+[PublicAPI]
+public sealed class Float64Attribute(string ns, string name) : ScalarAttribute(ns, name)
+{
+ ///
+ protected override double ToLowLevel(double value) => value;
+
+ ///
+ protected override double FromLowLevel(double value, AttributeResolver resolver) => value;
+}
diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/Int128Attribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/Int128Attribute.cs
new file mode 100644
index 0000000..d658ed1
--- /dev/null
+++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/Int128Attribute.cs
@@ -0,0 +1,18 @@
+using System;
+using JetBrains.Annotations;
+using NexusMods.MnemonicDB.Abstractions.ValueSerializers;
+
+namespace NexusMods.MnemonicDB.Abstractions.Attributes;
+
+///
+/// An attribute that holds an int128 value.
+///
+[PublicAPI]
+public sealed class Int128Attribute(string ns, string name) : ScalarAttribute(ns, name)
+{
+ ///
+ protected override Int128 ToLowLevel(Int128 value) => value;
+
+ ///
+ protected override Int128 FromLowLevel(Int128 value, AttributeResolver resolver) => value;
+}
diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/UInt128Attribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/UInt128Attribute.cs
new file mode 100644
index 0000000..510a76c
--- /dev/null
+++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/UInt128Attribute.cs
@@ -0,0 +1,18 @@
+using System;
+using JetBrains.Annotations;
+using NexusMods.MnemonicDB.Abstractions.ValueSerializers;
+
+namespace NexusMods.MnemonicDB.Abstractions.Attributes;
+
+///
+/// An attribute that holds an uint128 value.
+///
+[PublicAPI]
+public sealed class UInt128Attribute(string ns, string name) : ScalarAttribute(ns, name)
+{
+ ///
+ protected override UInt128 ToLowLevel(UInt128 value) => value;
+
+ ///
+ protected override UInt128 FromLowLevel(UInt128 value, AttributeResolver resolver) => value;
+}
diff --git a/src/NexusMods.MnemonicDB.Abstractions/EntityExtensions.cs b/src/NexusMods.MnemonicDB.Abstractions/EntityExtensions.cs
new file mode 100644
index 0000000..d33207e
--- /dev/null
+++ b/src/NexusMods.MnemonicDB.Abstractions/EntityExtensions.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Linq;
+using JetBrains.Annotations;
+using NexusMods.MnemonicDB.Abstractions.BuiltInEntities;
+using NexusMods.MnemonicDB.Abstractions.Models;
+
+namespace NexusMods.MnemonicDB.Abstractions;
+
+///
+/// Extension methods for entities.
+///
+[PublicAPI]
+public static class EntityExtensions
+{
+ ///
+ /// Gets the timestamp of the transaction that created the model.
+ ///
+ public static DateTimeOffset GetCreatedAt(this T model, DateTimeOffset defaultValue = default)
+ where T : IReadOnlyModel
+ {
+ if (model.Count == 0) return defaultValue;
+ var minTx = model.Min(m => m.T);
+
+ var tx = new Transaction.ReadOnly(model.Db, EntityId.From(minTx.Value));
+ return Transaction.Timestamp.Get(tx);
+ }
+}
diff --git a/src/NexusMods.MnemonicDB.Abstractions/EntityId.cs b/src/NexusMods.MnemonicDB.Abstractions/EntityId.cs
index 895a37a..4ab2d6c 100644
--- a/src/NexusMods.MnemonicDB.Abstractions/EntityId.cs
+++ b/src/NexusMods.MnemonicDB.Abstractions/EntityId.cs
@@ -1,4 +1,7 @@
-using TransparentValueObjects;
+using System;
+using System.Globalization;
+using JetBrains.Annotations;
+using TransparentValueObjects;
namespace NexusMods.MnemonicDB.Abstractions;
@@ -6,6 +9,7 @@ namespace NexusMods.MnemonicDB.Abstractions;
/// A unique identifier for an entity.
///
[ValueObject]
+[PublicAPI]
public readonly partial struct EntityId : IAugmentWith
{
///
@@ -38,4 +42,24 @@ public override string ToString()
{
return "EId:" + Value.ToString("X");
}
+
+ ///
+ /// Tries to parse a hex string as an entity ID.
+ ///
+ public static bool TryParseFromHex(ReadOnlySpan input, out EntityId id)
+ {
+ const string prefix = "EId:";
+
+ if (input.StartsWith(prefix))
+ input = input[prefix.Length..];
+
+ if (ulong.TryParse(input, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result))
+ {
+ id = EntityId.From(result);
+ return true;
+ }
+
+ id = default;
+ return false;
+ }
}