From prism-devtools
Use as reference guide for strangler pattern implementation. Provides patterns and best practices for legacy migrations.
npx claudepluginhub resolve-io/.prismThis skill uses the workspace's default tool permissions.
**Purpose:** Strategic guide for implementing the strangler pattern to migrate controllers from express-web-api to actions.api.
Enables AI agents to execute x402 payments with per-task budgets, spending controls, and non-custodial wallets via MCP tools. Use when agents pay for APIs, services, or other agents.
Provides patterns for autonomous Claude Code loops: sequential pipelines, agentic REPLs, PR cycles, de-sloppify cleanups, and RFC-driven multi-agent DAGs. For continuous dev workflows without intervention.
Applies NestJS patterns for modules, controllers, providers, DTO validation, guards, interceptors, config, and production TypeScript backends with project structure and bootstrap examples.
Purpose: Strategic guide for implementing the strangler pattern to migrate controllers from express-web-api to actions.api.
Related Documents:
Use strangler pattern for:
Skip strangler pattern for:
// Express-Web-API Controller - Generic pattern
public async Task<HttpResponseMessage> YourControllerMethod([FromBody] YourRequestModel requestModel)
{
var tenant = Features.ResolveTenant(Request.Headers);
if (await FeatureResolverSingleton.GetIsFeatureEnabledAsync(Features.YourFeatureFlag, tenant))
return CreateResponseMessage(await strangledService.Value.YourMethod(requestModel));
return CreateResponseMessage(legacyService.Value.YourMethod(requestModel, EyeShareToken));
}
Real Implementation Example (WorkflowController):
// Express-Web-API Controller - Real example from WorkflowController
public async Task<HttpResponseMessage> StartWorkflowExecution([FromBody] EyeShareWorkflowDesignerModel workflowDesigner)
{
var tenant = Features.ResolveTenant(Request.Headers);
if (await FeatureResolverSingleton.GetIsFeatureEnabledAsync(Features.WorkflowStrangle, tenant))
return CreateResponseMessage(await strangledService.Value.StartWorkflowExecution(workflowDesigner));
return CreateResponseMessage(legacyService.Value.StartWorkflowExecution(workflowDesigner, EyeShareToken));
}
// Generic pattern for any controller
public class YourController : ApiBaseController
{
private Lazy<YourLegacyService> legacyService;
private Lazy<YourStrangledService> strangledService; // Handles actions.api communication
public YourController()
{
legacyService = new Lazy<YourLegacyService>(() => {
var token = TokenManager.GetTokenInfo();
return new YourLegacyService(token, new DalService(token));
});
strangledService = new Lazy<YourStrangledService>(() => new YourStrangledService(ControllerContext));
}
}
Real Implementation Example (WorkflowController):
public class WorkflowController : ApiBaseController
{
private Lazy<EyeShareWorkflowService> legacyService;
private Lazy<WorkflowService> strangledService; // Handles actions.api communication
public WorkflowController()
{
legacyService = new Lazy<EyeShareWorkflowService>(() => {
var token = TokenManager.GetTokenInfo();
return new EyeShareWorkflowService(token, new DalService(token));
});
strangledService = new Lazy<WorkflowService>(() => new WorkflowService(ControllerContext));
}
}
// Actions.API - Generic pattern (no strangler terminology)
app.MapPost("/api/YourController/yourMethod",
async (YourRequest request, IMediator mediator) =>
{
var result = await mediator.Send(request);
return Results.Ok(result);
}).RequireAuthorization();
// Generic request/response models
public record YourRequest : IRequest<YourResponse>
{
public YourDataModel Data { get; init; }
}
public class YourRequestHandler : IRequestHandler<YourRequest, YourResponse>
{
public async Task<YourResponse> Handle(YourRequest request, CancellationToken cancellationToken)
{
return await _service.YourMethodAsync(request.Data);
}
}
Real Implementation Example (WorkflowController):
// Actions.API - No strangler terminology
app.MapPost("/api/Workflow/startExecution",
async (StartWorkflowExecutionRequest request, IMediator mediator) =>
{
var result = await mediator.Send(request);
return Results.Ok(result);
}).RequireAuthorization();
public record StartWorkflowExecutionRequest : IRequest<WorkflowExecutionResponse>
{
public EyeShareWorkflowDesignerModel WorkflowDesigner { get; init; }
}
public class StartWorkflowExecutionHandler : IRequestHandler<StartWorkflowExecutionRequest, WorkflowExecutionResponse>
{
public async Task<WorkflowExecutionResponse> Handle(StartWorkflowExecutionRequest request, CancellationToken cancellationToken)
{
return await _service.StartWorkflowExecutionAsync(request.WorkflowDesigner);
}
}
The express-web-api uses a specific authentication flow that differs from standard OAuth2:
# Authenticate to express-web-api
$authResponse = Invoke-RestMethod -Uri "http://localhost:52928/api/Auth/token" -Method Post -Body @{
grant_type = "password"
client_id = "RDTest"
username = "SuperAdmin"
password = "R3solv3!"
} -ContentType "application/x-www-form-urlencoded"
# Extract token - IMPORTANT: uses 'token' field, not 'access_token'
$token = $authResponse.token
# Use in API calls
$headers = @{
'Authorization' = "Bearer $token"
'Content-Type' = 'application/json'
}
# Test endpoint
$response = Invoke-RestMethod -Uri "http://localhost:52928/Api/{controller}/{method}" -Method Get -Headers $headers
Key Authentication Details:
http://localhost:52928/api/Auth/tokenapplication/x-www-form-urlencodedtoken (not access_token)Bearer {token}✅ Feature flag routing works between legacy and new systems ✅ Response behavior identical to captured baseline ✅ All tests pass in actions.api integration suite ✅ No regressions in existing functionality ✅ Performance maintains or exceeds baseline ✅ Rollback capability tested and verified
For Implementation:
For Validation:
For Process: