Add CultMesh document catalogs
This commit is contained in:
parent
60a3fa9f82
commit
df079f2cab
4 changed files with 192 additions and 0 deletions
|
|
@ -1120,6 +1120,22 @@ namespace GameCult.Mesh
|
|||
return Document<TDocument>(node, key, verse.Context, documentId, sources, routeHint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a schema-aware catalog over typed document handles.
|
||||
/// </summary>
|
||||
public static CultMeshDocumentCatalog Documents(IEnumerable<ICultMeshDocumentHandle> documents)
|
||||
{
|
||||
return new CultMeshDocumentCatalog(documents);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a schema-aware catalog over typed document handles.
|
||||
/// </summary>
|
||||
public static CultMeshDocumentCatalog Documents(params ICultMeshDocumentHandle[] documents)
|
||||
{
|
||||
return new CultMeshDocumentCatalog(documents);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes a typed live feed surface for tooling, UI, and diagnostics.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -1031,6 +1031,130 @@ namespace GameCult.Mesh
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Schema-aware lookup table for typed CultMesh document handles.
|
||||
/// </summary>
|
||||
public sealed class CultMeshDocumentCatalog
|
||||
{
|
||||
private readonly IReadOnlyList<ICultMeshDocumentHandle> _documents;
|
||||
private readonly IReadOnlyDictionary<Type, ICultMeshDocumentHandle> _documentsByType;
|
||||
private readonly IReadOnlyDictionary<string, ICultMeshDocumentHandle> _documentsBySchema;
|
||||
|
||||
/// <summary>Creates a document catalog from known handles.</summary>
|
||||
public CultMeshDocumentCatalog(IEnumerable<ICultMeshDocumentHandle> documents)
|
||||
{
|
||||
if (documents == null) throw new ArgumentNullException(nameof(documents));
|
||||
|
||||
var documentList = documents
|
||||
.Where(document => document != null)
|
||||
.ToArray();
|
||||
var byType = new Dictionary<Type, ICultMeshDocumentHandle>();
|
||||
var bySchema = new Dictionary<string, ICultMeshDocumentHandle>(StringComparer.Ordinal);
|
||||
foreach (var document in documentList)
|
||||
{
|
||||
byType[document.DocumentType] = document;
|
||||
if (!string.IsNullOrWhiteSpace(document.SchemaVersion))
|
||||
bySchema[document.SchemaVersion] = document;
|
||||
if (!string.IsNullOrWhiteSpace(document.SchemaName))
|
||||
bySchema[document.SchemaName] = document;
|
||||
}
|
||||
|
||||
_documents = documentList;
|
||||
_documentsByType = byType;
|
||||
_documentsBySchema = bySchema;
|
||||
}
|
||||
|
||||
/// <summary>Gets the handles in this catalog.</summary>
|
||||
public IReadOnlyList<ICultMeshDocumentHandle> Documents => _documents;
|
||||
|
||||
/// <summary>Looks up a document handle by shared schema name or schema version.</summary>
|
||||
public bool TryGetDocumentBySchema(
|
||||
string schema,
|
||||
out ICultMeshDocumentHandle document)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(schema) &&
|
||||
_documentsBySchema.TryGetValue(schema, out document!))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
document = null!;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Looks up a document handle by shared schema name or schema version.</summary>
|
||||
public ICultMeshDocumentHandle DocumentBySchema(string schema)
|
||||
{
|
||||
if (TryGetDocumentBySchema(schema, out var document))
|
||||
return document;
|
||||
|
||||
throw new NotSupportedException(
|
||||
$"CultMesh document catalog does not expose a document for schema '{schema}'.");
|
||||
}
|
||||
|
||||
/// <summary>Looks up a typed document handle by CLR type or same-schema alias.</summary>
|
||||
public bool TryGetDocument<TDocument>(
|
||||
out CultMeshDocumentHandle<TDocument> document)
|
||||
where TDocument : class
|
||||
{
|
||||
if (_documentsByType.TryGetValue(typeof(TDocument), out var untypedDocument) &&
|
||||
untypedDocument is CultMeshDocumentHandle<TDocument> typedDocument)
|
||||
{
|
||||
document = typedDocument;
|
||||
return true;
|
||||
}
|
||||
|
||||
var descriptor = CultDocumentRegistry.Shared.GetRequired<TDocument>();
|
||||
if (_documentsBySchema.TryGetValue(descriptor.SchemaVersion, out var schemaDocument) ||
|
||||
_documentsBySchema.TryGetValue(descriptor.SchemaName, out schemaDocument!))
|
||||
{
|
||||
if (schemaDocument is CultMeshDocumentHandle<TDocument> schemaTypedDocument)
|
||||
{
|
||||
document = schemaTypedDocument;
|
||||
return true;
|
||||
}
|
||||
|
||||
document = schemaDocument.AsSchemaAlias<TDocument>();
|
||||
return true;
|
||||
}
|
||||
|
||||
document = null!;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Looks up a typed document handle by CLR type or same-schema alias.</summary>
|
||||
public CultMeshDocumentHandle<TDocument> Document<TDocument>()
|
||||
where TDocument : class
|
||||
{
|
||||
if (TryGetDocument<TDocument>(out var document))
|
||||
return document;
|
||||
|
||||
throw new NotSupportedException(
|
||||
$"CultMesh document catalog does not expose a document for {typeof(TDocument).FullName}.");
|
||||
}
|
||||
|
||||
/// <summary>Reads one typed document snapshot by CLR type or same-schema alias.</summary>
|
||||
public Task<TDocument> LatestAsync<TDocument>()
|
||||
where TDocument : class
|
||||
{
|
||||
return Document<TDocument>().LatestAsync();
|
||||
}
|
||||
|
||||
/// <summary>Watches one typed document by CLR type or same-schema alias.</summary>
|
||||
public Observable<TDocument> Watch<TDocument>()
|
||||
where TDocument : class
|
||||
{
|
||||
return Document<TDocument>().Watch();
|
||||
}
|
||||
|
||||
/// <summary>Subscribes to one typed document by CLR type or same-schema alias.</summary>
|
||||
public IDisposable Watch<TDocument>(Action<TDocument> onNext)
|
||||
where TDocument : class
|
||||
{
|
||||
return Document<TDocument>().Watch(onNext);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inspectable metadata for a typed live feed surface.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -170,6 +170,12 @@ portable machinery that makes it feel native.
|
|||
with `AsSchemaAlias<TAlias>()`; CultMesh verifies the shared
|
||||
`[CultDocument(schemaName, schemaVersion)]` identity and uses the shared
|
||||
CultCache MessagePack codec for read/watch/replace conversion.
|
||||
- `CultMeshDocumentCatalog` is the schema-aware lookup edge for a set of
|
||||
document handles. It indexes handles by CLR document type, schema name, and
|
||||
schema version, and resolves same-schema CLR aliases by delegating to
|
||||
`CultMeshDocumentHandle<TDocument>.AsSchemaAlias<TAlias>()`. Domain facades
|
||||
can expose `Document<T>()`, `DocumentBySchema(...)`, `LatestAsync<T>()`, and
|
||||
`Watch<T>()` without owning schema dictionaries or alias serializers.
|
||||
- `CultMeshProjectionRecipe<TParameters, TResult>` names a reusable projection
|
||||
from typed source state into derived state. It records source handles,
|
||||
route hints, and projection execution, and can be exposed as a typed query
|
||||
|
|
@ -254,6 +260,19 @@ using var subscription = cockpit.Watch(next => RenderCockpit(next));
|
|||
var daemonAlias = cockpit.AsSchemaAlias<DaemonCockpitState>();
|
||||
```
|
||||
|
||||
A domain facade can collect handles into one schema-aware catalog:
|
||||
|
||||
```csharp
|
||||
var documents = CultMesh.Documents(
|
||||
currentDocking,
|
||||
stationRefit,
|
||||
zoneContacts);
|
||||
|
||||
var refit = await documents.LatestAsync<StationRefitDocument>();
|
||||
var uiDocking = documents.Document<CurrentDockingUiDocument>();
|
||||
var bySchema = documents.DocumentBySchema("gamecult.aetheria.station_refit.v1");
|
||||
```
|
||||
|
||||
```ts
|
||||
const queryVerse = verse.withRoute("shared-memory", statePath);
|
||||
const commandVerse = verse
|
||||
|
|
|
|||
|
|
@ -698,6 +698,39 @@ public sealed class CultMeshStreamingTests
|
|||
.WithMessage("*tests.mesh_other*tests.mesh_note*");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task DocumentCatalog_IndexesHandlesByTypeAndSchemaAlias()
|
||||
{
|
||||
var current = new MeshNoteDocument
|
||||
{
|
||||
Schema = "tests.mesh_note.v1",
|
||||
Text = "catalog-primary",
|
||||
Revision = 3
|
||||
};
|
||||
var handle = CultMesh.Document(
|
||||
"mesh.note.current",
|
||||
CultMesh.Verse("starbridge", "unity-pilot"),
|
||||
_ => Task.FromResult(current),
|
||||
_ => new Subject<MeshNoteDocument>());
|
||||
var catalog = CultMesh.Documents(handle);
|
||||
|
||||
catalog.Documents.Should().ContainSingle().Which.Should().BeSameAs(handle);
|
||||
catalog.TryGetDocument<MeshNoteDocument>(out var exact).Should().BeTrue();
|
||||
exact.Should().BeSameAs(handle);
|
||||
catalog.TryGetDocumentBySchema("tests.mesh_note.v1", out var byVersion).Should().BeTrue();
|
||||
byVersion.Should().BeSameAs(handle);
|
||||
catalog.DocumentBySchema("tests.mesh_note").Should().BeSameAs(handle);
|
||||
|
||||
catalog.TryGetDocument<MeshNoteAliasDocument>(out var alias).Should().BeTrue();
|
||||
alias.DocumentId.Should().Be(handle.DocumentId);
|
||||
alias.SchemaName.Should().Be(handle.SchemaName);
|
||||
alias.SchemaVersion.Should().Be(handle.SchemaVersion);
|
||||
|
||||
var aliasSnapshot = await catalog.LatestAsync<MeshNoteAliasDocument>();
|
||||
aliasSnapshot.Text.Should().Be("catalog-primary");
|
||||
aliasSnapshot.Revision.Should().Be(3);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task DocumentHandle_ReadsWatchesAndReplacesCultCacheRecords()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue