Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions src/FluentCMS.Core/Builtin/Tools/ChangeSetTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,4 @@ public DirectoryTarget ToDirectoryTarget()
{
return _changeSetId is { } id ? DirectoryTarget.Existing(id) : DirectoryTarget.Session(_sessionId!, _opening!);
}

// The set the write went into, read after it to answer the call.
public Task<Result<ChangeSet>> ReadAsync(IChangeSetDirectory directory, CancellationToken cancellationToken)
{
return _changeSetId is { } id ? directory.GetAsync(id, cancellationToken) : directory.GetSessionSetAsync(_sessionId!, cancellationToken);
}
}
25 changes: 16 additions & 9 deletions src/FluentCMS.Core/Builtin/Tools/ProposedWrite.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,39 @@ internal static class ProposedWrite
public static async Task<ToolResult> RunAsync(
ToolInvocation call,
ChangeSetTarget target,
Func<IContentTypeDirectory, DirectoryTarget, Task<Result<ContentType>>> write,
Func<IContentTypeDirectory, DirectoryTarget, Task<Result<ContentTypeWrite>>> write,
CancellationToken cancellationToken)
{
var services = call.Services;

var type = await write(services.GetRequiredService<IContentTypeDirectory>(), target.ToDirectoryTarget());
if (!type.IsSuccess)
var written = await write(services.GetRequiredService<IContentTypeDirectory>(), target.ToDirectoryTarget());
if (!written.IsSuccess)
{
return ToolResult.Failure(type.Errors, Hint(type.Errors));
return ToolResult.Failure(written.Errors, Hint(written.Errors));
}

var set = await target.ReadAsync(services.GetRequiredService<IChangeSetDirectory>(), cancellationToken);
// The set the write joined, read by its id: one submitted since still answers, in its new state, and a session's next set
// opened since is not mistaken for it.
var type = written.Value.ContentType;
var set = await services.GetRequiredService<IChangeSetDirectory>().GetAsync(written.Value.ChangeSetId, cancellationToken);
if (!set.IsSuccess)
{
return ToolResult.Failure(set.Errors);
}

// The set says what the write became: a revision of a type with nothing live is recorded as its create.
var recorded = set.Value.Operations.Single(operation => operation.ItemKind == ItemKind.Schema && operation.ItemId == type.Value.Id);
var recorded = set.Value.Operations.Single(operation => operation.ItemKind == ItemKind.Schema && operation.ItemId == type.Id);
var open = set.Value.State == ChangeSetState.Open;

return ToolResult.Success(new JsonObject
{
["contentType"] = ToolJson.ContentType(type.Value),
["contentType"] = ToolJson.ContentType(type),
["changeSet"] = ToolJson.ChangeSet(set.Value),
["next"] = $"The {ToolJson.Name(recorded.Kind)} is in change set {set.Value.Id}, which stays open for more "
+ "operations. Call submit_change_set when it is complete; checks run and a person reviews it before it is published.",
["next"] = open
? $"The {ToolJson.Name(recorded.Kind)} is in change set {set.Value.Id}, which stays open for more "
+ "operations. Call submit_change_set when it is complete; checks run and a person reviews it before it is published."
: $"The {ToolJson.Name(recorded.Kind)} is in change set {set.Value.Id}, which was submitted since; read it with "
+ "get_change_set, and write further changes into a new set.",
});
}

Expand Down
4 changes: 2 additions & 2 deletions src/FluentCMS.Core/Builtin/Tools/SchemaTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ private static async Task<ToolResult> CreateAsync(ToolInvocation call, Cancellat
return await ProposedWrite.RunAsync(
call,
target!,
(directory, changeSet) => directory.CreateAsync(new NewContentType(name!, slug!, parsed!), changeSet, cancellationToken),
(directory, changeSet) => directory.CreateInChangeSetAsync(new NewContentType(name!, slug!, parsed!), changeSet, cancellationToken),
cancellationToken);
}

Expand All @@ -166,7 +166,7 @@ private static async Task<ToolResult> ReviseAsync(ToolInvocation call, Cancellat
return await ProposedWrite.RunAsync(
call,
target!,
(directory, changeSet) => directory.ReviseAsync(slug!, baseVersion!.Value, parsed!, changeSet, cancellationToken),
(directory, changeSet) => directory.ReviseInChangeSetAsync(slug!, baseVersion!.Value, parsed!, changeSet, cancellationToken),
cancellationToken);
}

Expand Down
13 changes: 13 additions & 0 deletions src/FluentCMS.Core/Schema/IContentTypeDirectory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,17 @@ Task<Result<ContentType>> ReviseAsync(
ContentTypeDefinition definition,
ChangeSetTarget changeSet,
CancellationToken cancellationToken = default);

// As CreateAsync and ReviseAsync, with the change set the write joined, so a caller such as a tool answers from that set instead of
// looking it up again by session, which a submit or the session's next set could change in between.
Task<Result<ContentTypeWrite>> CreateInChangeSetAsync(NewContentType contentType, ChangeSetTarget changeSet, CancellationToken cancellationToken = default);

Task<Result<ContentTypeWrite>> ReviseInChangeSetAsync(
string slug,
int baseVersion,
ContentTypeDefinition definition,
ChangeSetTarget changeSet,
CancellationToken cancellationToken = default);
}

public sealed record ContentTypeWrite(ContentType ContentType, Guid ChangeSetId);
66 changes: 61 additions & 5 deletions src/FluentCMS.Infrastructure/Schema/ContentTypeDirectory.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Runtime.CompilerServices;
using FluentCMS.Core.Agents;
using FluentCMS.Core.Audit;
using FluentCMS.Core.ChangeSets;
Expand Down Expand Up @@ -54,7 +55,51 @@ public Task<Result<ContentType>> GetVersionAsync(string slug, int version, Cance
return ReadAsync(slug, _ => version, cancellationToken);
}

public async Task<Result<ContentType>> CreateAsync(NewContentType contentType, ChangeSetTarget changeSet, CancellationToken cancellationToken = default)
public Task<Result<ContentType>> CreateAsync(NewContentType contentType, ChangeSetTarget changeSet, CancellationToken cancellationToken = default)
{
return CreateAsync(contentType, changeSet, joined: null, cancellationToken);
}

public async Task<Result<ContentTypeWrite>> CreateInChangeSetAsync(NewContentType contentType, ChangeSetTarget changeSet, CancellationToken cancellationToken = default)
{
var joined = new StrongBox<Guid>();
return Written(await CreateAsync(contentType, changeSet, joined, cancellationToken), joined);
}

public Task<Result<ContentType>> ReviseAsync(
string slug,
int baseVersion,
ContentTypeDefinition definition,
ChangeSetTarget changeSet,
CancellationToken cancellationToken = default)
{
return ReviseAsync(slug, baseVersion, definition, changeSet, joined: null, cancellationToken);
}

public async Task<Result<ContentTypeWrite>> ReviseInChangeSetAsync(
string slug,
int baseVersion,
ContentTypeDefinition definition,
ChangeSetTarget changeSet,
CancellationToken cancellationToken = default)
{
var joined = new StrongBox<Guid>();
return Written(await ReviseAsync(slug, baseVersion, definition, changeSet, joined, cancellationToken), joined);
}

private static Result<ContentTypeWrite> Written(Result<ContentType> written, StrongBox<Guid> joined)
{
return written.IsSuccess
? Result<ContentTypeWrite>.Success(new ContentTypeWrite(written.Value, joined.Value))
: Result<ContentTypeWrite>.Failure(written.Errors);
}

// The set the write joined is put in joined once it has committed.
private async Task<Result<ContentType>> CreateAsync(
NewContentType contentType,
ChangeSetTarget changeSet,
StrongBox<Guid>? joined,
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(contentType);
ArgumentNullException.ThrowIfNull(changeSet);
Expand Down Expand Up @@ -113,15 +158,16 @@ await auditLog.AppendAsync(
cancellationToken);
await changeSets.RecordAsync(pending.Value, null, cancellationToken);

return await CommitAsync(scope, type, CommitConflict(changeSet, pending.Value, SlugTaken(type.Slug)), cancellationToken);
return Joined(await CommitAsync(scope, type, CommitConflict(changeSet, pending.Value, SlugTaken(type.Slug)), cancellationToken), pending.Value, joined);
}

public async Task<Result<ContentType>> ReviseAsync(
private async Task<Result<ContentType>> ReviseAsync(
string slug,
int baseVersion,
ContentTypeDefinition definition,
ChangeSetTarget changeSet,
CancellationToken cancellationToken = default)
StrongBox<Guid>? joined,
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(slug);
ArgumentNullException.ThrowIfNull(definition);
Expand Down Expand Up @@ -241,7 +287,17 @@ await auditLog.AppendAsync(

await changeSets.RecordAsync(pending.Value, based.Id, cancellationToken);

return await CommitAsync(scope, type, CommitConflict(changeSet, pending.Value, RevisedMeanwhile(row)), cancellationToken);
return Joined(await CommitAsync(scope, type, CommitConflict(changeSet, pending.Value, RevisedMeanwhile(row)), cancellationToken), pending.Value, joined);
}

private static Result<ContentType> Joined(Result<ContentType> committed, ChangeSetDirectory.PendingOperation pending, StrongBox<Guid>? joined)
{
if (committed.IsSuccess && joined is not null)
{
joined.Value = pending.ChangeSet.Id;
}

return committed;
}

private async Task<Result<ContentType>> ReadAsync(string slug, Func<ContentTypeRow, int?> version, CancellationToken cancellationToken)
Expand Down
Loading
Loading