Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/tomevault-io/skills-registry --skill structid명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
| Use when this capability is needed.
> Use when this capability is needed.
Review architecture and API design for the vfs-s3 project. Use when the user mentions @architect, asks to review an issue's design, discuss module boundaries, API shape, or architectural decisions for vfs-s3. Also trigger when the user wants to create an ADR (Architecture Decision Record) or evaluate a technical approach for the project. Intended for dispatch from Codex automation or Claude routines; GitHub trigger phrase: @vfs-s3-bot please prepare design doc Use when this capability is needed.
SOC 직업 분류 기준
SKILL.md 표시 중
| name | structid |
| description | > Use when this capability is needed. |
StructId is a zero-dependency, strongly-typed ID library for .NET. Every user-declared ID type is a
readonly partial record struct that implements either IStructId (string-backed) or
IStructId<TValue> (struct-backed). All code is source-generated directly into the consuming
project — there are no runtime package references.
Don't assume users will want structured IDs for every identifiable entity. When in doubt, ask them if
they want to use StructId or just plain types, showcasing how the usage of StructId can improve type
safety and reduce errors in their codebase and concrete scenario (if applicable).
using StructId;
// String-backed ID
public readonly partial record struct ProductId : IStructId;
// Struct-backed ID (Guid, int, long, Ulid, or any struct)
public readonly partial record struct UserId : IStructId<Guid>;
public readonly partial record struct OrderId : IStructId<int>;
IStructId (string value)namespace StructId;
public partial interface IStructId
{
string Value { get; }
}
IStructId<TValue> (struct value)namespace StructId;
public partial interface IStructId<TValue> where TValue : struct
{
TValue Value { get; }
}
INewable<TSelf> / INewable<TSelf, TValue> (factory pattern)Static interface members for consistent factory methods:
namespace StructId;
public interface INewable<TSelf>
{
static abstract TSelf New(string value);
}
public interface INewable<TSelf, TValue>
{
static abstract TSelf New(TValue value);
}
All struct IDs automatically implement these interfaces via generated code. Use them for generic constraints that require creating new instances:
T CreateId<T>(string value) where T : INewable<T> => T.New(value);
T CreateId<T, V>(V value) where T : INewable<T, V> => T.New(value);
The minimum declaration is a readonly partial record struct implementing one of the core interfaces:
using StructId;
public readonly partial record struct UserId : IStructId<Guid>;
public readonly partial record struct ProductId : IStructId; // string-backed
public readonly partial record struct OrderId : IStructId<int>;
public readonly partial record struct TraceId : IStructId<Ulid>; // Ulid supported out of the box
Key requirements (enforced by analyzer with code fixes):
readonlypartialrecord structValueusing StructId;
// Custom primary constructor (e.g. to add attributes)
public readonly partial record struct ProductId([property: JsonPropertyName("id")] int Value) : IStructId<int>;
For every struct ID, the source generator emits:
(TValue Value) (unless you declared one)Value propertyIComparable<TSelf> + comparison operators (<, <=, >, >=) if TValue : IComparable<TValue>IParsable<TSelf> + ISpanParsable<TSelf> if TValue : IParsable<TValue>IFormattable + ISpanFormattable + IUtf8SpanFormattable forwarding to Value (when applicable)TValueINewable<TSelf> / INewable<TSelf, TValue> implementationNew(TValue value) static factory methodNew() (parameterless) for Guid- and Ulid-backed IDs, using Guid.CreateVersion7() on .NET 9+ or Guid.NewGuid() on earlier targets / Ulid.NewUlid()// Guid-backed: parameterless New() generates a new GUID
// On .NET 9+, uses Guid.CreateVersion7(); earlier targets use Guid.NewGuid()
var userId = UserId.New(); // new UserId(Guid.CreateVersion7()) on .NET 9+
var userId2 = UserId.New(someGuid); // new UserId(someGuid)
// Ulid-backed: parameterless New() generates a new ULID
var traceId = TraceId.New(); // new TraceId(Ulid.NewUlid())
// String-backed
var productId = ProductId.New("p-123"); // new ProductId("p-123")
// int-backed (no parameterless New())
var orderId = OrderId.New(42); // new OrderId(42)
Reference Microsoft.EntityFrameworkCore — no other configuration needed. The generator emits
value converters and registers them via UseStructId():
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseSqlite("Data Source=app.db")
.UseStructId() // registers all struct ID value converters
.Options;
// Or inside OnConfiguring:
protected override void OnConfiguring(DbContextOptionsBuilder builder) => builder.UseStructId();
Value type coverage:
Guid, int, long, string, bool, byte, short, float, double, decimal, DateTime, DateTimeOffset, TimeSpanIParsable<T> + IFormattable: Ulid and any custom type implementing bothValueConverter<TModel, TProvider> subclass in your project is auto-registeredReference Dapper — no other configuration needed. The generator emits SqlMapper.TypeHandler<T>
implementations and registers them via UseStructId():
using var connection = new SqliteConnection("Data Source=app.db");
connection.UseStructId(); // registers all struct ID type handlers
connection.Open();
Value type coverage:
Guid, int, long, stringIParsable<T> + IFormattable: Ulid and any custom type implementing bothSqlMapper.TypeHandler<T> subclass in your project is auto-registeredActivated automatically when the value type implements IParsable<T>. Uses JsonConverter<T>
that serializes/deserializes via the Value property string representation.
Reference Newtonsoft.Json — the generator emits JsonConverter<T> subclasses automatically.
Reference the Ulid NuGet package. Since Ulid implements IParsable<T> and IFormattable:
New() factory is generated using Ulid.NewUlid()public readonly partial record struct TraceId : IStructId<Ulid>;
var id = TraceId.New(); // new TraceId(Ulid.NewUlid())
[TStructId])The template system allows extending all (or a subset of) struct IDs with additional interfaces or members. Templates are regular C# files in your project.
[TStructId]file partial record struct (file-scoped to avoid polluting the assembly)TSelfValue — its type controls which
struct IDs the template applies toApply to all struct IDs (any value type):
using StructId;
[TStructId]
file partial record struct TSelf(TValue Value)
{
public static implicit operator TValue(TSelf id) => id.Value;
public static explicit operator TSelf(TValue value) => new(value);
}
file record struct TValue; // empty = match any value type
Apply only to string-backed IDs:
using StructId;
[TStructId]
file partial record struct TSelf(string Value)
{
public static implicit operator string(TSelf id) => id.Value;
public static explicit operator TSelf(string value) => new(value);
}
Apply only to Guid-backed IDs:
using StructId;
[TStructId]
file partial record struct TSelf(Guid Value) : IMyGuidId
{
public Guid AsGuid() => Value;
}
Apply to IDs whose value type implements a specific interface:
using StructId;
[TStructId]
file partial record struct TSelf(TValue Value) : IComparable<TSelf>
{
public int CompareTo(TSelf other) => ((IComparable<TValue>)Value).CompareTo(other.Value);
public static bool operator <(TSelf left, TSelf right) => left.Value.CompareTo(right.Value) < 0;
public static bool operator <=(TSelf left, TSelf right) => left.Value.CompareTo(right.Value) <= 0;
public static bool operator >(TSelf left, TSelf right) => left.Value.CompareTo(right.Value) > 0;
public static bool operator >=(TSelf left, TSelf right) => left.Value.CompareTo(right.Value) >= 0;
}
// Constrain TValue — only applies to IDs whose value type implements IComparable<TValue>
file record struct TValue : IComparable<TValue>
{
public int CompareTo(TValue other) => throw new NotImplementedException();
}
Exclude string from TValue matching:
using StructId;
// /*!string*/ inline comment excludes string-backed IDs
[TStructId]
file partial record struct TSelf(/*!string*/ TValue Value)
{
// only applies to non-string value types
}
file record struct TValue;
Add TSelf interface constraint (additional filtering):
using StructId;
[TStructId]
file partial record struct TSelf(Ulid Value)
{
public static TSelf New() => new(Ulid.NewUlid());
}
// This partial declaration is removed at expansion time; it only constrains matching
file partial record struct TSelf : INewable<TSelf, Ulid>
{
public static TSelf New(Ulid value) => throw new NotImplementedException();
}
For a struct ID PersonId : IStructId<Guid> and a template applying to Guid-backed IDs:
[TStructId] attribute is removed from the outputTSelf is replaced with PersonIdTValue is replaced with GuidConstructorGenerator)file modifier is removed from the type declarationPersonIdfile record struct TValue) are removed from outputTValue Prefixed IdentifiersTo generate unique helper type names per struct ID, use the TSelf_ or TValue_ prefix:
using StructId;
[TStructId]
file partial record struct TSelf(TValue Value)
{
// TSelf_Helper becomes PersonId_Helper, OrderId_Helper, etc.
private sealed class TSelf_Helper { }
}
file record struct TValue;
[TValue])For custom Dapper handlers or EF Core converters for a specific value type, use [TValue]:
using StructId;
[TValue]
file class TValue_Handler : SqlMapper.TypeHandler<TValue>
{
public override void SetValue(IDbDataParameter parameter, TValue value)
=> parameter.Value = value.ToString();
public override TValue Parse(object value)
=> TValue.Parse((string)value, null);
}
file record struct TValue : IParsable<TValue>, IFormattable
{
// Define the value type constraints
}
These are automatically discovered and registered in the generated UseStructId extension.
| Diagnostic | Trigger | Auto-Fix Available |
|---|---|---|
| SID001 | Struct ID is not readonly partial record struct | ✅ Add missing modifiers |
| SID002 | Primary constructor parameter not named Value (or multiple params) | ✅ Rename to Value / Remove constructor |
| SID003 | [TStructId] type is not file partial record struct | ✅ Add file modifier |
| SID004 | [TStructId] constructor parameter not named Value | ✅ Rename to Value |
| SID005 | [TStructId] type is not named TSelf | ✅ Rename type |
<PackageReference Include="StructId" Version="*" />
developmentDependency="true" — no runtime dependency is added to consumersFeatures activate automatically when the corresponding package is referenced:
| Package | Generated Feature |
|---|---|
Microsoft.EntityFrameworkCore | ValueConverter<T, TProvider> + UseStructId(DbContextOptionsBuilder) |
Dapper | SqlMapper.TypeHandler<T> + UseStructId(IDbConnection) |
Newtonsoft.Json | JsonConverter<T> subclass |
Ulid | Ulid-specific handlers + parameterless New() factory |
No attribute, configuration, or code change is needed — just add the NuGet reference and rebuild.
using StructId;
public readonly partial record struct UserId : IStructId<Guid>;
public class User
{
public UserId Id { get; set; } = UserId.New();
public string Name { get; set; } = "";
}
public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options)
{
public DbSet<User> Users => Set<User>();
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<User>().HasKey(u => u.Id);
}
}
// Setup
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseSqlite("Data Source=app.db")
.UseStructId()
.Options;
using StructId;
public readonly partial record struct ProductId : IStructId<int>;
using var connection = new SqliteConnection("Data Source=app.db");
connection.UseStructId();
connection.Open();
var product = connection.QueryFirst<Product>(
"SELECT * FROM Products WHERE Id = @Id",
new { Id = new ProductId(42) });
using StructId;
public class Repository<TEntity, TId, TValue>
where TId : struct, IStructId<TValue>, INewable<TId, TValue>
where TValue : struct
{
public TEntity GetById(TValue rawValue) => Get(TId.New(rawValue));
private TEntity Get(TId id) => /* ... */;
}
// IEntityId.cs — custom interface
public interface IEntityId
{
Guid AsGuid();
}
// EntityIdTemplate.cs — template to implement it for all Guid-backed IDs
[TStructId]
file partial record struct TSelf(Guid Value) : IEntityId
{
public Guid AsGuid() => Value;
}
readonly partial record structIStructId<TValue> for struct value types; use IStructId for stringsfile partial record struct TSelf named files; no specific file naming requiredTValue placeholder in templates means "any value type"; add interfaces to constrain itTSelf.New() (parameterless) for Guid and Ulid IDs; TSelf.New(value) for all othersUseStructId() must be called once at startup for EF Core (DbContextOptionsBuilder) and Dapper (IDbConnection)ValueConverter<,> and SqlMapper.TypeHandler<T> subclasses in the project are auto-registeredSource: devlooped/StructId — distributed by TomeVault.