diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/AbsolutePathAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/AbsolutePathAttribute.cs
new file mode 100644
index 00000000..16d42d07
--- /dev/null
+++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/AbsolutePathAttribute.cs
@@ -0,0 +1,22 @@
+using JetBrains.Annotations;
+using Microsoft.Extensions.DependencyInjection;
+using NexusMods.MnemonicDB.Abstractions.ValueSerializers;
+using NexusMods.Paths;
+
+namespace NexusMods.MnemonicDB.Abstractions.Attributes;
+
+///
+/// An attribute that holds an .
+///
+[PublicAPI]
+public sealed class AbsolutePathAttribute(string ns, string name) : ScalarAttribute(ns, name)
+{
+ ///
+ protected override string ToLowLevel(AbsolutePath value) => value.ToString();
+
+ ///
+ protected override AbsolutePath FromLowLevel(string value, AttributeResolver resolver)
+ {
+ return resolver.ServiceProvider.GetRequiredService().FromUnsanitizedFullPath(value);
+ }
+}
diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/BackReferenceAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/BackReferenceAttribute.cs
index c33a5627..6cc15e09 100644
--- a/src/NexusMods.MnemonicDB.Abstractions/Attributes/BackReferenceAttribute.cs
+++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/BackReferenceAttribute.cs
@@ -1,4 +1,5 @@
-using NexusMods.MnemonicDB.Abstractions.Models;
+using JetBrains.Annotations;
+using NexusMods.MnemonicDB.Abstractions.Models;
// ReSharper disable UnusedTypeParameter
#pragma warning disable CS9113 // Parameter is unread.
@@ -8,7 +9,5 @@ namespace NexusMods.MnemonicDB.Abstractions.Attributes;
/// A meta attribute the expresses a backwards reference some other
/// model has
///
-public class BackReferenceAttribute(ReferenceAttribute referenceAttribute)
-where TOtherModel : IModelDefinition
-{
-}
+[PublicAPI]
+public sealed class BackReferenceAttribute(ReferenceAttribute referenceAttribute) where TOtherModel : IModelDefinition { }
diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/BooleanAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/BooleanAttribute.cs
new file mode 100644
index 00000000..5431b9af
--- /dev/null
+++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/BooleanAttribute.cs
@@ -0,0 +1,17 @@
+using JetBrains.Annotations;
+using NexusMods.MnemonicDB.Abstractions.ValueSerializers;
+
+namespace NexusMods.MnemonicDB.Abstractions.Attributes;
+
+///
+/// An attribute that holds a boolean value.
+///
+[PublicAPI]
+public sealed class BooleanAttribute(string ns, string name) : ScalarAttribute(ns, name)
+{
+ ///
+ protected override byte ToLowLevel(bool value) => value ? (byte)1 : (byte)0;
+
+ ///
+ protected override bool FromLowLevel(byte value, AttributeResolver resolver) => value == 1;
+}
diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/CardinalityAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/CardinalityAttribute.cs
index c5549a82..69a2e2c3 100644
--- a/src/NexusMods.MnemonicDB.Abstractions/Attributes/CardinalityAttribute.cs
+++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/CardinalityAttribute.cs
@@ -1,4 +1,4 @@
-using NexusMods.MnemonicDB.Abstractions.ElementComparers;
+using JetBrains.Annotations;
using NexusMods.MnemonicDB.Abstractions.ValueSerializers;
namespace NexusMods.MnemonicDB.Abstractions.Attributes;
@@ -6,6 +6,7 @@ namespace NexusMods.MnemonicDB.Abstractions.Attributes;
///
/// Used to mark the cardinality of an attribute in the database
///
+[PublicAPI]
public sealed class CardinalityAttribute(string ns, string name) : ScalarAttribute(ns, name)
{
///
diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/CollectionAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/CollectionAttribute.cs
index ca8d0500..fca17541 100644
--- a/src/NexusMods.MnemonicDB.Abstractions/Attributes/CollectionAttribute.cs
+++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/CollectionAttribute.cs
@@ -1,4 +1,5 @@
-using NexusMods.MnemonicDB.Abstractions.IndexSegments;
+using JetBrains.Annotations;
+using NexusMods.MnemonicDB.Abstractions.IndexSegments;
using NexusMods.MnemonicDB.Abstractions.Models;
namespace NexusMods.MnemonicDB.Abstractions.Attributes;
@@ -6,6 +7,7 @@ namespace NexusMods.MnemonicDB.Abstractions.Attributes;
///
/// An attribute that represents a collection of values
///
+[PublicAPI]
public abstract class CollectionAttribute(string ns, string name)
: Attribute(ns, name, cardinality: Cardinality.Many)
where TSerializer : IValueSerializer
diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/EnumAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/EnumAttribute.cs
new file mode 100644
index 00000000..9d267d4c
--- /dev/null
+++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/EnumAttribute.cs
@@ -0,0 +1,53 @@
+using System;
+using JetBrains.Annotations;
+using NexusMods.MnemonicDB.Abstractions.ValueSerializers;
+
+namespace NexusMods.MnemonicDB.Abstractions.Attributes;
+
+///
+/// An attribute that holds an enum value backed by an int32 value.
+///
+[PublicAPI]
+public sealed class EnumAttribute(string ns, string name) : ScalarAttribute(ns, name)
+ where T : Enum
+{
+ ///
+ protected override int ToLowLevel(T value)
+ {
+ // NOTE(halgari): Looks like an allocation, but the cast to object is removed by the JIT since the type of
+ // T is a compile-time constant. Verified via sharpLab.io:
+ // https://sharplab.io/#v2:EYLgtghglgdgNAFxBAzmAPgAQEwEYCwAUJgAwAEAygBYQBOADgDITAB0ASgK4wJRgCmAbiJEA2gCkoCAOL8Y/WlADGACgQBPevwD2AMxUBZdQFEYnMAEoLAXTKYAzHexkAwgB4AKgD4yAdyoK/GQeZCBkpuZkAN5EZHHxDmSwCGQGKiEAbhAANpz8FtGx8cWYAOxkKskWKtrAAFb8SggWWblCRXEAvkTdhESJcpFGEWDRZABi2tpkALxkJHBkAEJ0s2S4ZJ2CQA=
+ return (int)(object)value;
+ }
+
+ ///
+ protected override T FromLowLevel(int value, AttributeResolver resolver)
+ {
+ // Same as ToLowLevel, the cast to object is removed by the JIT
+ return (T)(object)value;
+ }
+}
+
+///
+/// An attribute that holds an enum value backed by a byte value.
+///
+[PublicAPI]
+public sealed class EnumByteAttribute(string ns, string name) : ScalarAttribute(ns, name)
+ where T : Enum
+{
+ ///
+ protected override byte ToLowLevel(T value)
+ {
+ // NOTE(halgari): Looks like an allocation, but the cast to object is removed by the JIT since the type of
+ // T is a compile-time constant. Verified via sharpLab.io:
+ // https://sharplab.io/#v2:EYLgtghglgdgNAFxBAzmAPgAQEwEYCwAUJgAwAEAygBYQBOADgDITAB0ASgK4wJRgCmAbiJEA2gCkoCAOL8Y/WlADGACgQBPevwD2AMxUBZdQFEYnMAEoLAXTKYAzHexkAwgB4AKgD4yAdyoK/GQeZCBkpuZkAN5EZHHxDmSwCGQGKiEAbhAANpz8FtGx8cWYAOxkKskWKtrAAFb8SggWWblCRXEAvkTdhESJcpFGEWDRZABi2tpkALxkJHBkAEJ0s2S4ZJ2CQA=
+ return (byte)(object)value;
+ }
+
+ ///
+ protected override T FromLowLevel(byte value, AttributeResolver resolver)
+ {
+ // Same as ToLowLevel, the cast to object is removed by the JIT
+ return (T)(object)value;
+ }
+}
diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/Float32Attribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/Float32Attribute.cs
new file mode 100644
index 00000000..ca5798e9
--- /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 00000000..2c5b55ab
--- /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 00000000..d658ed1b
--- /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/Int16Attribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/Int16Attribute.cs
new file mode 100644
index 00000000..83272038
--- /dev/null
+++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/Int16Attribute.cs
@@ -0,0 +1,17 @@
+using JetBrains.Annotations;
+using NexusMods.MnemonicDB.Abstractions.ValueSerializers;
+
+namespace NexusMods.MnemonicDB.Abstractions.Attributes;
+
+///
+/// An attribute that holds an int16 value.
+///
+[PublicAPI]
+public sealed class Int16Attribute(string ns, string name) : ScalarAttribute(ns, name)
+{
+ ///
+ protected override short ToLowLevel(short value) => value;
+
+ ///
+ protected override short FromLowLevel(short value, AttributeResolver resolver) => value;
+}
diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/Int32Attribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/Int32Attribute.cs
new file mode 100644
index 00000000..bc58c1af
--- /dev/null
+++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/Int32Attribute.cs
@@ -0,0 +1,17 @@
+using JetBrains.Annotations;
+using NexusMods.MnemonicDB.Abstractions.ValueSerializers;
+
+namespace NexusMods.MnemonicDB.Abstractions.Attributes;
+
+///
+/// An attribute that holds an int32 value.
+///
+[PublicAPI]
+public sealed class Int32Attribute(string ns, string name) : ScalarAttribute(ns, name)
+{
+ ///
+ protected override int ToLowLevel(int value) => value;
+
+ ///
+ protected override int FromLowLevel(int value, AttributeResolver resolver) => value;
+}
diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/Int64Attribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/Int64Attribute.cs
new file mode 100644
index 00000000..0eed6908
--- /dev/null
+++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/Int64Attribute.cs
@@ -0,0 +1,17 @@
+using JetBrains.Annotations;
+using NexusMods.MnemonicDB.Abstractions.ValueSerializers;
+
+namespace NexusMods.MnemonicDB.Abstractions.Attributes;
+
+///
+/// An attribute that holds an uint64 value.
+///
+[PublicAPI]
+public sealed class Int64Attribute(string ns, string name) : ScalarAttribute(ns, name)
+{
+ ///
+ protected override long ToLowLevel(long value) => value;
+
+ ///
+ protected override long FromLowLevel(long value, AttributeResolver resolver) => value;
+}
diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/MarkerAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/MarkerAttribute.cs
index 6122ef31..79d2be46 100644
--- a/src/NexusMods.MnemonicDB.Abstractions/Attributes/MarkerAttribute.cs
+++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/MarkerAttribute.cs
@@ -1,5 +1,5 @@
-using NexusMods.MnemonicDB.Abstractions.ElementComparers;
-using NexusMods.MnemonicDB.Abstractions.Models;
+using JetBrains.Annotations;
+using NexusMods.MnemonicDB.Abstractions.ElementComparers;
using NexusMods.MnemonicDB.Abstractions.ValueSerializers;
namespace NexusMods.MnemonicDB.Abstractions.Attributes;
@@ -8,29 +8,12 @@ namespace NexusMods.MnemonicDB.Abstractions.Attributes;
/// An attribute that doesn't have a value, but is used to dispatch logic or to mark an entity
/// as being of a certain type.
///
-///
-///
-public class MarkerAttribute(string ns, string name) : Attribute(ns, name)
+[PublicAPI]
+public sealed class MarkerAttribute(string ns, string name) : ScalarAttribute(ns, name)
{
///
protected override Null ToLowLevel(Null value) => value;
///
protected override Null FromLowLevel(Null value, AttributeResolver resolver) => value;
-
- ///
- /// Returns true if the entity contains the attribute.
- ///
- public bool Contains(IHasIdAndIndexSegment entity)
- {
- var segment = entity.IndexSegment;
- var dbId = entity.Db.Connection.AttributeCache.GetAttributeId(Id);
- for (var i = 0; i < segment.Count; i++)
- {
- var datom = segment[i];
- if (datom.A != dbId) continue;
- return true;
- }
- return false;
- }
}
diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/ReferenceAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/ReferenceAttribute.cs
index fc7c8a45..4d873db6 100644
--- a/src/NexusMods.MnemonicDB.Abstractions/Attributes/ReferenceAttribute.cs
+++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/ReferenceAttribute.cs
@@ -1,4 +1,5 @@
-using NexusMods.MnemonicDB.Abstractions.Models;
+using JetBrains.Annotations;
+using NexusMods.MnemonicDB.Abstractions.Models;
using NexusMods.MnemonicDB.Abstractions.ValueSerializers;
namespace NexusMods.MnemonicDB.Abstractions.Attributes;
@@ -6,6 +7,7 @@ namespace NexusMods.MnemonicDB.Abstractions.Attributes;
///
/// An attribute that references another entity.
///
+[PublicAPI]
public class ReferenceAttribute(string ns, string name) : ScalarAttribute(ns, name)
{
///
@@ -18,5 +20,6 @@ public class ReferenceAttribute(string ns, string name) : ScalarAttribute
/// A typesafe reference attribute, that references entities of type T.
///
+[PublicAPI]
public sealed class ReferenceAttribute(string ns, string name) : ReferenceAttribute(ns, name)
where T : IModelDefinition;
diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/ReferencesAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/ReferencesAttribute.cs
index 29e16e28..af4a9504 100644
--- a/src/NexusMods.MnemonicDB.Abstractions/Attributes/ReferencesAttribute.cs
+++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/ReferencesAttribute.cs
@@ -1,4 +1,4 @@
-using NexusMods.MnemonicDB.Abstractions.ElementComparers;
+using JetBrains.Annotations;
using NexusMods.MnemonicDB.Abstractions.Models;
using NexusMods.MnemonicDB.Abstractions.ValueSerializers;
@@ -7,6 +7,7 @@ namespace NexusMods.MnemonicDB.Abstractions.Attributes;
///
/// Represents a collection of references to other entities.
///
+[PublicAPI]
public class ReferencesAttribute(string ns, string name) : CollectionAttribute(ns, name)
{
///
@@ -19,5 +20,6 @@ public class ReferencesAttribute(string ns, string name) : CollectionAttribute
/// A typesafe reference attribute, that references entities of type T.
///
+[PublicAPI]
public sealed class ReferencesAttribute(string ns, string name) : ReferencesAttribute(ns, name)
where T : IModelDefinition;
diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/RelativePathAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/RelativePathAttribute.cs
new file mode 100644
index 00000000..f3f622ef
--- /dev/null
+++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/RelativePathAttribute.cs
@@ -0,0 +1,22 @@
+using JetBrains.Annotations;
+using Microsoft.Extensions.DependencyInjection;
+using NexusMods.MnemonicDB.Abstractions.ValueSerializers;
+using NexusMods.Paths;
+
+namespace NexusMods.MnemonicDB.Abstractions.Attributes;
+
+///
+/// An attribute that holds an .
+///
+[PublicAPI]
+public sealed class RelativePathAttribute(string ns, string name) : ScalarAttribute(ns, name)
+{
+ ///
+ protected override string ToLowLevel(RelativePath value) => value.ToString();
+
+ ///
+ protected override RelativePath FromLowLevel(string value, AttributeResolver resolver)
+ {
+ return new RelativePath(value);
+ }
+}
diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/ScalarAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/ScalarAttribute.cs
index 00a7d263..28abfe98 100644
--- a/src/NexusMods.MnemonicDB.Abstractions/Attributes/ScalarAttribute.cs
+++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/ScalarAttribute.cs
@@ -1,6 +1,6 @@
using System.Collections.Generic;
using DynamicData.Kernel;
-using NexusMods.MnemonicDB.Abstractions.ElementComparers;
+using JetBrains.Annotations;
using NexusMods.MnemonicDB.Abstractions.Models;
namespace NexusMods.MnemonicDB.Abstractions.Attributes;
@@ -8,6 +8,7 @@ namespace NexusMods.MnemonicDB.Abstractions.Attributes;
///
/// An attribute that represents a scalar value, where there is a 1:1 ratio between the attribute and the value.
///
+[PublicAPI]
public abstract class ScalarAttribute(string ns, string name) :
Attribute(ns, name)
where TSerializer : IValueSerializer
@@ -22,6 +23,14 @@ public bool IsOptional
init => DeclaredOptional = value;
}
+ ///
+ /// True whether the index segment contains this attribute.
+ ///
+ public bool Contains(T entity) where T : IHasIdAndIndexSegment
+ {
+ return entity.IndexSegment.Contains(this);
+ }
+
///
/// Gets the value of the attribute from the entity.
///
diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/SizeAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/SizeAttribute.cs
new file mode 100644
index 00000000..0010725f
--- /dev/null
+++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/SizeAttribute.cs
@@ -0,0 +1,18 @@
+using JetBrains.Annotations;
+using NexusMods.MnemonicDB.Abstractions.ValueSerializers;
+using NexusMods.Paths;
+
+namespace NexusMods.MnemonicDB.Abstractions.Attributes;
+
+///
+/// An attribute that holds an .
+///
+[PublicAPI]
+public sealed class SizeAttribute(string ns, string name) : ScalarAttribute(ns, name)
+{
+ ///
+ protected override ulong ToLowLevel(Size value) => value.Value;
+
+ ///
+ protected override Size FromLowLevel(ulong value, AttributeResolver resolver) => Size.From(value);
+}
diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/StringAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/StringAttribute.cs
index e7481e9c..daab05ca 100644
--- a/src/NexusMods.MnemonicDB.Abstractions/Attributes/StringAttribute.cs
+++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/StringAttribute.cs
@@ -1,10 +1,12 @@
-using NexusMods.MnemonicDB.Abstractions.ValueSerializers;
+using JetBrains.Annotations;
+using NexusMods.MnemonicDB.Abstractions.ValueSerializers;
namespace NexusMods.MnemonicDB.Abstractions.Attributes;
///
/// An attribute that represents a string.
///
+[PublicAPI]
public sealed class StringAttribute(string ns, string name) : ScalarAttribute(ns, name)
{
///
diff --git a/tests/NexusMods.MnemonicDB.TestModel/Attributes/StringsAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/StringsAttribute.cs
similarity index 61%
rename from tests/NexusMods.MnemonicDB.TestModel/Attributes/StringsAttribute.cs
rename to src/NexusMods.MnemonicDB.Abstractions/Attributes/StringsAttribute.cs
index 9dd586f3..1bd33b1d 100644
--- a/tests/NexusMods.MnemonicDB.TestModel/Attributes/StringsAttribute.cs
+++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/StringsAttribute.cs
@@ -1,13 +1,17 @@
-using NexusMods.MnemonicDB.Abstractions;
-using NexusMods.MnemonicDB.Abstractions.Attributes;
-using NexusMods.MnemonicDB.Abstractions.ElementComparers;
+using JetBrains.Annotations;
using NexusMods.MnemonicDB.Abstractions.ValueSerializers;
-namespace NexusMods.MnemonicDB.TestModel.Attributes;
+namespace NexusMods.MnemonicDB.Abstractions.Attributes;
+///
+/// An attribute that holds a collection of strings.
+///
+[PublicAPI]
public sealed class StringsAttribute(string ns, string name) : CollectionAttribute(ns, name)
{
+ ///
protected override string ToLowLevel(string value) => value;
+ ///
protected override string FromLowLevel(string value, AttributeResolver resolver) => value;
}
diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/SymbolAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/SymbolAttribute.cs
index 4025a438..7506bf7a 100644
--- a/src/NexusMods.MnemonicDB.Abstractions/Attributes/SymbolAttribute.cs
+++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/SymbolAttribute.cs
@@ -1,11 +1,13 @@
-using NexusMods.MnemonicDB.Abstractions.ValueSerializers;
+using JetBrains.Annotations;
+using NexusMods.MnemonicDB.Abstractions.ValueSerializers;
namespace NexusMods.MnemonicDB.Abstractions.Attributes;
///
/// An attribute that encodes a symbol.
///
-public class SymbolAttribute(string ns, string name) : ScalarAttribute(ns, name)
+[PublicAPI]
+public sealed class SymbolAttribute(string ns, string name) : ScalarAttribute(ns, name)
{
///
protected override string ToLowLevel(Symbol value) => value.Id;
diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/TimestampAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/TimestampAttribute.cs
index 796b1ecd..48698209 100644
--- a/src/NexusMods.MnemonicDB.Abstractions/Attributes/TimestampAttribute.cs
+++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/TimestampAttribute.cs
@@ -1,6 +1,5 @@
using System;
-using NexusMods.MnemonicDB.Abstractions.ElementComparers;
-using NexusMods.MnemonicDB.Abstractions.Models;
+using JetBrains.Annotations;
using NexusMods.MnemonicDB.Abstractions.ValueSerializers;
namespace NexusMods.MnemonicDB.Abstractions.Attributes;
@@ -8,7 +7,8 @@ namespace NexusMods.MnemonicDB.Abstractions.Attributes;
///
/// An attribute that holds a timestamp
///
-public class TimestampAttribute(string ns, string name) : ScalarAttribute(ns, name)
+[PublicAPI]
+public sealed class TimestampAttribute(string ns, string name) : ScalarAttribute(ns, name)
{
///
protected override long ToLowLevel(DateTimeOffset value) => value.ToUnixTimeMilliseconds();
diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/TxIdAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/TxIdAttribute.cs
new file mode 100644
index 00000000..2047a7c8
--- /dev/null
+++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/TxIdAttribute.cs
@@ -0,0 +1,17 @@
+using JetBrains.Annotations;
+using NexusMods.MnemonicDB.Abstractions.ValueSerializers;
+
+namespace NexusMods.MnemonicDB.Abstractions.Attributes;
+
+///
+/// An attribute that holds a .
+///
+[PublicAPI]
+public sealed class TxIdAttribute(string ns, string name) : ScalarAttribute(ns, name)
+{
+ ///
+ protected override ulong ToLowLevel(TxId value) => value.Value;
+
+ ///
+ protected override TxId FromLowLevel(ulong value, AttributeResolver resolver) => TxId.From(value);
+}
diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/UInt128Attribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/UInt128Attribute.cs
new file mode 100644
index 00000000..510a76cb
--- /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/Attributes/UInt16Attribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/UInt16Attribute.cs
new file mode 100644
index 00000000..1859091b
--- /dev/null
+++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/UInt16Attribute.cs
@@ -0,0 +1,17 @@
+using JetBrains.Annotations;
+using NexusMods.MnemonicDB.Abstractions.ValueSerializers;
+
+namespace NexusMods.MnemonicDB.Abstractions.Attributes;
+
+///
+/// An attribute that holds an uint16 value.
+///
+[PublicAPI]
+public sealed class UInt16Attribute(string ns, string name) : ScalarAttribute(ns, name)
+{
+ ///
+ protected override ushort ToLowLevel(ushort value) => value;
+
+ ///
+ protected override ushort FromLowLevel(ushort value, AttributeResolver resolver) => value;
+}
diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/UInt32Attribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/UInt32Attribute.cs
new file mode 100644
index 00000000..6e67682f
--- /dev/null
+++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/UInt32Attribute.cs
@@ -0,0 +1,17 @@
+using JetBrains.Annotations;
+using NexusMods.MnemonicDB.Abstractions.ValueSerializers;
+
+namespace NexusMods.MnemonicDB.Abstractions.Attributes;
+
+///
+/// An attribute that holds an uint32 value.
+///
+[PublicAPI]
+public sealed class UInt32Attribute(string ns, string name) : ScalarAttribute(ns, name)
+{
+ ///
+ protected override uint ToLowLevel(uint value) => value;
+
+ ///
+ protected override uint FromLowLevel(uint value, AttributeResolver resolver) => value;
+}
diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/ULongAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/UInt64Attribute.cs
similarity index 63%
rename from src/NexusMods.MnemonicDB.Abstractions/Attributes/ULongAttribute.cs
rename to src/NexusMods.MnemonicDB.Abstractions/Attributes/UInt64Attribute.cs
index ef4cc88c..2ab1edca 100644
--- a/src/NexusMods.MnemonicDB.Abstractions/Attributes/ULongAttribute.cs
+++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/UInt64Attribute.cs
@@ -1,11 +1,13 @@
+using JetBrains.Annotations;
using NexusMods.MnemonicDB.Abstractions.ValueSerializers;
namespace NexusMods.MnemonicDB.Abstractions.Attributes;
///
-/// UInt64 attribute (ulong)
+/// An attribute that holds an uint64 value.
///
-public sealed class ULongAttribute(string ns, string name) : ScalarAttribute(ns, name)
+[PublicAPI]
+public sealed class UInt64Attribute(string ns, string name) : ScalarAttribute(ns, name)
{
///
protected override ulong ToLowLevel(ulong value) => value;
diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/UriAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/UriAttribute.cs
new file mode 100644
index 00000000..88a30428
--- /dev/null
+++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/UriAttribute.cs
@@ -0,0 +1,18 @@
+using System;
+using JetBrains.Annotations;
+using NexusMods.MnemonicDB.Abstractions.ValueSerializers;
+
+namespace NexusMods.MnemonicDB.Abstractions.Attributes;
+
+///
+/// An attribute that holds a .
+///
+[PublicAPI]
+public sealed class UriAttribute(string ns, string name) : ScalarAttribute(ns, name)
+{
+ ///
+ protected override string ToLowLevel(Uri value) => value.ToString();
+
+ ///
+ protected override Uri FromLowLevel(string value, AttributeResolver resolver) => new(value);
+}
diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/ValuesTagAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/ValuesTagAttribute.cs
index ff70daac..0bb24c71 100644
--- a/src/NexusMods.MnemonicDB.Abstractions/Attributes/ValuesTagAttribute.cs
+++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/ValuesTagAttribute.cs
@@ -1,4 +1,5 @@
-using NexusMods.MnemonicDB.Abstractions.ElementComparers;
+using JetBrains.Annotations;
+using NexusMods.MnemonicDB.Abstractions.ElementComparers;
using NexusMods.MnemonicDB.Abstractions.ValueSerializers;
namespace NexusMods.MnemonicDB.Abstractions.Attributes;
@@ -6,6 +7,7 @@ namespace NexusMods.MnemonicDB.Abstractions.Attributes;
///
/// An attribute that represents a value tag value
///
+[PublicAPI]
public sealed class ValuesTagAttribute(string ns, string name) : ScalarAttribute(ns, name)
{
///
diff --git a/src/NexusMods.MnemonicDB.Abstractions/BuiltInEntities/Transaction.cs b/src/NexusMods.MnemonicDB.Abstractions/BuiltInEntities/Transaction.cs
index da7e31d6..955f5b68 100644
--- a/src/NexusMods.MnemonicDB.Abstractions/BuiltInEntities/Transaction.cs
+++ b/src/NexusMods.MnemonicDB.Abstractions/BuiltInEntities/Transaction.cs
@@ -20,5 +20,5 @@ public partial class Transaction : IModelDefinition
///
/// If this database is associated with a excision, this attribute will contain the number of datoms that were excised.
///
- public static readonly ULongAttribute ExcisedDatoms = new(Namespace, "ExcisedDatoms") { IsOptional = true };
+ public static readonly UInt64Attribute ExcisedDatoms = new(Namespace, "ExcisedDatoms") { IsOptional = true };
}
diff --git a/src/NexusMods.MnemonicDB.Abstractions/EntityExtensions.cs b/src/NexusMods.MnemonicDB.Abstractions/EntityExtensions.cs
new file mode 100644
index 00000000..d33207e2
--- /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 895a37a9..4ab2d6c7 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;
+ }
}
diff --git a/tests/NexusMods.MnemonicDB.TestModel/ArchiveFile.cs b/tests/NexusMods.MnemonicDB.TestModel/ArchiveFile.cs
index c65ead72..d45cd35b 100644
--- a/tests/NexusMods.MnemonicDB.TestModel/ArchiveFile.cs
+++ b/tests/NexusMods.MnemonicDB.TestModel/ArchiveFile.cs
@@ -1,4 +1,5 @@
-using NexusMods.MnemonicDB.Abstractions.Models;
+using NexusMods.MnemonicDB.Abstractions.Attributes;
+using NexusMods.MnemonicDB.Abstractions.Models;
using NexusMods.MnemonicDB.TestModel.Attributes;
namespace NexusMods.MnemonicDB.TestModel;
diff --git a/tests/NexusMods.MnemonicDB.TestModel/Attributes/AbsolutePathAttribute.cs b/tests/NexusMods.MnemonicDB.TestModel/Attributes/AbsolutePathAttribute.cs
deleted file mode 100644
index e0bb4e99..00000000
--- a/tests/NexusMods.MnemonicDB.TestModel/Attributes/AbsolutePathAttribute.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using Microsoft.Extensions.DependencyInjection;
-using NexusMods.MnemonicDB.Abstractions;
-using NexusMods.MnemonicDB.Abstractions.Attributes;
-using NexusMods.MnemonicDB.Abstractions.ElementComparers;
-using NexusMods.MnemonicDB.Abstractions.ValueSerializers;
-using NexusMods.Paths;
-
-namespace NexusMods.MnemonicDB.TestModel.Attributes;
-
-public sealed class AbsolutePathAttribute(string ns, string name) : ScalarAttribute(ns, name)
-{
- protected override string ToLowLevel(AbsolutePath value)
- => value.ToString();
-
- protected override AbsolutePath FromLowLevel(string value, AttributeResolver resolver)
- => resolver.ServiceProvider.GetRequiredService().FromUnsanitizedFullPath(value);
-}
diff --git a/tests/NexusMods.MnemonicDB.TestModel/Attributes/RelativePathAttribute.cs b/tests/NexusMods.MnemonicDB.TestModel/Attributes/RelativePathAttribute.cs
deleted file mode 100644
index c040c513..00000000
--- a/tests/NexusMods.MnemonicDB.TestModel/Attributes/RelativePathAttribute.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using NexusMods.MnemonicDB.Abstractions;
-using NexusMods.MnemonicDB.Abstractions.Attributes;
-using NexusMods.MnemonicDB.Abstractions.ElementComparers;
-using NexusMods.MnemonicDB.Abstractions.ValueSerializers;
-using NexusMods.Paths;
-
-namespace NexusMods.MnemonicDB.TestModel.Attributes;
-
-public sealed class RelativePathAttribute(string ns, string name) :
- ScalarAttribute(ns, name)
-{
- protected override string ToLowLevel(RelativePath value) => value.Path;
-
- protected override RelativePath FromLowLevel(string value, AttributeResolver resolver) => new(value);
-}
diff --git a/tests/NexusMods.MnemonicDB.TestModel/Attributes/SizeAttribute.cs b/tests/NexusMods.MnemonicDB.TestModel/Attributes/SizeAttribute.cs
deleted file mode 100644
index e6edff0d..00000000
--- a/tests/NexusMods.MnemonicDB.TestModel/Attributes/SizeAttribute.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using NexusMods.MnemonicDB.Abstractions;
-using NexusMods.MnemonicDB.Abstractions.Attributes;
-using NexusMods.MnemonicDB.Abstractions.ElementComparers;
-using NexusMods.MnemonicDB.Abstractions.ValueSerializers;
-using NexusMods.Paths;
-
-namespace NexusMods.MnemonicDB.TestModel.Attributes;
-
-public sealed class SizeAttribute(string ns, string name) : ScalarAttribute(ns, name) {
- protected override ulong ToLowLevel(Size value) => value.Value;
-
- protected override Size FromLowLevel(ulong value, AttributeResolver resolver)
- => Size.From(value);
-}
diff --git a/tests/NexusMods.MnemonicDB.TestModel/Attributes/UriAttribute.cs b/tests/NexusMods.MnemonicDB.TestModel/Attributes/UriAttribute.cs
deleted file mode 100644
index 8052d8fa..00000000
--- a/tests/NexusMods.MnemonicDB.TestModel/Attributes/UriAttribute.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using NexusMods.MnemonicDB.Abstractions;
-using NexusMods.MnemonicDB.Abstractions.Attributes;
-using NexusMods.MnemonicDB.Abstractions.ElementComparers;
-using NexusMods.MnemonicDB.Abstractions.ValueSerializers;
-
-namespace NexusMods.MnemonicDB.TestModel.Attributes;
-
-public sealed class UriAttribute(string ns, string name) : ScalarAttribute(ns, name)
-{
- protected override string ToLowLevel(Uri value)
- => value.ToString();
-
- protected override Uri FromLowLevel(string lowLevelValue, AttributeResolver resolver)
- => new(lowLevelValue);
-}
diff --git a/tests/NexusMods.MnemonicDB.Tests/MigrationTests.cs b/tests/NexusMods.MnemonicDB.Tests/MigrationTests.cs
index d67457f9..b9424a18 100644
--- a/tests/NexusMods.MnemonicDB.Tests/MigrationTests.cs
+++ b/tests/NexusMods.MnemonicDB.Tests/MigrationTests.cs
@@ -101,7 +101,7 @@ public async Task CanConvertValues()
var aid = cache.GetAttributeId(Mod.Description.Id);
cache.IsIndexed(aid).Should().BeFalse();
- var withIndex = new ULongAttribute(Mod.Description.Id.Namespace, Mod.Description.Id.Name) { IsIndexed = true, IsOptional = false };
+ var withIndex = new UInt64Attribute(Mod.Description.Id.Namespace, Mod.Description.Id.Name) { IsIndexed = true, IsOptional = false };
var prevTxId = Connection.Db.BasisTxId;
await Connection.UpdateSchema(withIndex);
@@ -117,7 +117,7 @@ public async Task CanConvertValues()
public async Task ConvertingValuesIncorrectlyFails()
{
await AddData();
- var withIndex = new ULongAttribute(Mod.Source.Id.Namespace, Mod.Source.Id.Name) { IsIndexed = true, IsOptional = false };
+ var withIndex = new UInt64Attribute(Mod.Source.Id.Namespace, Mod.Source.Id.Name) { IsIndexed = true, IsOptional = false };
var act = async () => await Connection.UpdateSchema(withIndex);
await act.Should().ThrowAsync("Converting values for attribute Mod.Source from String to ULong where the source is a URI should fail");
}