IPersistedOperationsService
The persisted-operation management surface. The operations.persistedOperations GraphQL fields call it, and the dashboard calls it, so an upload, deactivation or restore is accepted or refused the same way, with the same codes, from either. Use it from admin tooling that should behave exactly as the API does.
Registered as a singleton by UsePersistedOperations and by AddPersistedOperationStore.
Interface
public interface IPersistedOperationsService
{
Task<PersistedOperationsPage> ListAsync(PersistedOperationFilter? filter, int skip, int take, CancellationToken ct);
Task<PersistedOperationDto?> GetAsync(string id, string? tenantKey, CancellationToken ct);
Task<IReadOnlyList<PersistedOperationHistoryDto>> GetHistoryAsync(string id, string? tenantKey, int skip, int take, CancellationToken ct);
Task<UploadPersistedOperationPayload> UploadAsync(UploadPersistedOperationInput input, CancellationToken ct);
Task<DeactivatePersistedOperationPayload> DeactivateAsync(DeactivatePersistedOperationInput input, CancellationToken ct);
Task<RestorePersistedOperationPayload> RestoreAsync(RestorePersistedOperationInput input, CancellationToken ct);
}Behavior
- The writes never throw for a refused change. The payload carries
Success = falseandErrors, each with a stableCode:INVALID_INPUT,NOT_FOUND,PARSE_FAILED,SCHEMA_VALIDATION_FAILEDorSHAPE_DIFF_VIOLATION. The codes are the ones in the management mutations' error payload. UploadAsyncgoes through IPersistedOperationStore.UpsertAsync: schema validation, exactly one operation per document, the shape-diff guardrail, history, cache invalidation and broadcast.DeactivateAsyncrequires a reason and acts on an active operation; an unknown or already deactivated id isNOT_FOUND.RestoreAsyncacts on any existing operation, active or deactivated.GetAsyncreturns active and deactivated operations, and null when there is none.ListAsyncreturns the most recently updated first.GetHistoryAsyncreturns the most recent change first. For both,takeoutside 1 to 200 reads as 50 and a negativeskipreads as 0.
Example
var service = serviceProvider.GetRequiredService<IPersistedOperationsService>();
var result = await service.DeactivateAsync(
new DeactivatePersistedOperationInput("userProfile_v1", Reason: "replaced by userProfile_v2"),
cancellationToken
);
if (!result.Success)
throw new InvalidOperationException(string.Join("; ", result.Errors.Select(e => $"{e.Code}: {e.Message}")));