diff --git a/Elsa.sln b/Elsa.sln
index 3bd4eac22..34eb44ccf 100644
--- a/Elsa.sln
+++ b/Elsa.sln
@@ -403,6 +403,20 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "oracle-setup", "oracle-setu
docker\oracle-setup\setup.sql = docker\oracle-setup\setup.sql
EndProjectSection
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "connections", "connections", "{9DF81143-2F39-4CFB-9DA3-902279010622}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Connections.Models", "src\modules\Elsa.Connections.Models\Elsa.Connections.Models.csproj", "{75E08B0E-A41A-425C-9DCA-155505CE41CD}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Connections.Api", "src\modules\Elsa.Connections.Api\Elsa.Connections.Api.csproj", "{BD120D97-4862-469E-B2CF-4109F7F21C20}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Connections.Core", "src\modules\Elsa.Connections.Core\Elsa.Connections.Core.csproj", "{8A3443F3-D40E-44B9-9805-B4CB7A3F022C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Connections.Persistence", "src\modules\Elsa.Connections.Persistence\Elsa.Connections.Persistence.csproj", "{FA2B2B44-7E3E-444B-A157-7526B376E9CA}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Connections.Persistence.EntityFrameworkCore", "src\modules\Elsa.Connections.Persistence.EntityFrameworkCore\Elsa.Connections.Persistence.EntityFrameworkCore.csproj", "{DF0CA7E2-F5DF-40ED-91B6-748767084FC7}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite", "src\modules\Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite\Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite.csproj", "{DB20B168-CD90-41F2-BE08-3D8C6F142459}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -859,6 +873,30 @@ Global
{A51F9683-DA9F-45E7-82DE-1E261ACD6D68}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A51F9683-DA9F-45E7-82DE-1E261ACD6D68}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A51F9683-DA9F-45E7-82DE-1E261ACD6D68}.Release|Any CPU.Build.0 = Release|Any CPU
+ {75E08B0E-A41A-425C-9DCA-155505CE41CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {75E08B0E-A41A-425C-9DCA-155505CE41CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {75E08B0E-A41A-425C-9DCA-155505CE41CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {75E08B0E-A41A-425C-9DCA-155505CE41CD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BD120D97-4862-469E-B2CF-4109F7F21C20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BD120D97-4862-469E-B2CF-4109F7F21C20}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BD120D97-4862-469E-B2CF-4109F7F21C20}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BD120D97-4862-469E-B2CF-4109F7F21C20}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8A3443F3-D40E-44B9-9805-B4CB7A3F022C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8A3443F3-D40E-44B9-9805-B4CB7A3F022C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8A3443F3-D40E-44B9-9805-B4CB7A3F022C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8A3443F3-D40E-44B9-9805-B4CB7A3F022C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FA2B2B44-7E3E-444B-A157-7526B376E9CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FA2B2B44-7E3E-444B-A157-7526B376E9CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FA2B2B44-7E3E-444B-A157-7526B376E9CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FA2B2B44-7E3E-444B-A157-7526B376E9CA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DF0CA7E2-F5DF-40ED-91B6-748767084FC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DF0CA7E2-F5DF-40ED-91B6-748767084FC7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DF0CA7E2-F5DF-40ED-91B6-748767084FC7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DF0CA7E2-F5DF-40ED-91B6-748767084FC7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DB20B168-CD90-41F2-BE08-3D8C6F142459}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DB20B168-CD90-41F2-BE08-3D8C6F142459}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DB20B168-CD90-41F2-BE08-3D8C6F142459}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DB20B168-CD90-41F2-BE08-3D8C6F142459}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1011,6 +1049,13 @@ Global
{FA5E857F-B173-4B5D-8049-B817A210DEF5} = {A0DC5F8E-5D7F-4E8A-A5DF-B1FC31F7336E}
{A51F9683-DA9F-45E7-82DE-1E261ACD6D68} = {A0DC5F8E-5D7F-4E8A-A5DF-B1FC31F7336E}
{66E2E2CF-967F-4564-89E8-F46FA973C99B} = {986E5482-0482-448C-B9E4-EC67A9474B85}
+ {9DF81143-2F39-4CFB-9DA3-902279010622} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
+ {75E08B0E-A41A-425C-9DCA-155505CE41CD} = {9DF81143-2F39-4CFB-9DA3-902279010622}
+ {BD120D97-4862-469E-B2CF-4109F7F21C20} = {9DF81143-2F39-4CFB-9DA3-902279010622}
+ {8A3443F3-D40E-44B9-9805-B4CB7A3F022C} = {9DF81143-2F39-4CFB-9DA3-902279010622}
+ {FA2B2B44-7E3E-444B-A157-7526B376E9CA} = {9DF81143-2F39-4CFB-9DA3-902279010622}
+ {DF0CA7E2-F5DF-40ED-91B6-748767084FC7} = {9DF81143-2F39-4CFB-9DA3-902279010622}
+ {DB20B168-CD90-41F2-BE08-3D8C6F142459} = {9DF81143-2F39-4CFB-9DA3-902279010622}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D4B5CEAA-7D70-4FCB-A68E-B03FBE5E0E5E}
diff --git a/src/apps/Elsa.ServerAndStudio.Web/Elsa.ServerAndStudio.Web.csproj b/src/apps/Elsa.ServerAndStudio.Web/Elsa.ServerAndStudio.Web.csproj
index 7cfa13de3..7df79071e 100644
--- a/src/apps/Elsa.ServerAndStudio.Web/Elsa.ServerAndStudio.Web.csproj
+++ b/src/apps/Elsa.ServerAndStudio.Web/Elsa.ServerAndStudio.Web.csproj
@@ -13,6 +13,10 @@
+
+
+
+
diff --git a/src/apps/Elsa.ServerAndStudio.Web/Program.cs b/src/apps/Elsa.ServerAndStudio.Web/Program.cs
index 6d4aa8895..807f711a0 100644
--- a/src/apps/Elsa.ServerAndStudio.Web/Program.cs
+++ b/src/apps/Elsa.ServerAndStudio.Web/Program.cs
@@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.Sqlite;
using Proto.Persistence.Sqlite;
using WebhooksCore.Options;
+using Elsa.Connections.Middleware;
const bool useMassTransit = true;
const bool useProtoActor = true;
@@ -193,6 +194,11 @@ services
if (distributedCachingTransport == DistributedCachingTransport.MassTransit) distributedCaching.UseMassTransit();
});
}
+
+ elsa.UseConnections(
+ configure=> configure.AddConnectionsFrom());
+ elsa.UseConnectionPersistence(ef=> ef.UseEntityFrameworkCore(f=>f.UseSqlite()));
+ elsa.UseConnectionsApi();
});
services.Configure(options => configuration.GetSection("Webhooks").Bind(options));
diff --git a/src/modules/Elsa.Connections.Api/Constants.cs b/src/modules/Elsa.Connections.Api/Constants.cs
new file mode 100644
index 000000000..732c2da99
--- /dev/null
+++ b/src/modules/Elsa.Connections.Api/Constants.cs
@@ -0,0 +1,5 @@
+namespace Elsa.Connections.Api;
+public class Constants
+{
+ public const string PermissionsNamespace = "connections";
+}
diff --git a/src/modules/Elsa.Connections.Api/Elsa.Connections.Api.csproj b/src/modules/Elsa.Connections.Api/Elsa.Connections.Api.csproj
new file mode 100644
index 000000000..fbf376330
--- /dev/null
+++ b/src/modules/Elsa.Connections.Api/Elsa.Connections.Api.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Provides Activity Connection API endpoints
+ elsa module activity connection abstraction api
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/Elsa.Connections.Api/Endpoints/ActivityConnectionDescriptor/Get/Endpoint.cs b/src/modules/Elsa.Connections.Api/Endpoints/ActivityConnectionDescriptor/Get/Endpoint.cs
new file mode 100644
index 000000000..7bf4aa6eb
--- /dev/null
+++ b/src/modules/Elsa.Connections.Api/Endpoints/ActivityConnectionDescriptor/Get/Endpoint.cs
@@ -0,0 +1,29 @@
+using Elsa.Abstractions;
+using Elsa.Connections.Contracts;
+using Elsa.Workflows.Models;
+
+namespace Elsa.Connections.Api.Endpoints.ActivityConnectionDescriptor.Get;
+
+public class Endpoint(IConnectionDescriptorRegistry store) : ElsaEndpointWithoutRequest>
+{
+ public override void Configure()
+ {
+ Get("/connection-configuration/input-descriptor/{ActivityType}");
+ ConfigurePermissions($"{Constants.PermissionsNamespace}/descriptor:read");
+ }
+
+ public override async Task HandleAsync(CancellationToken ct)
+ {
+ var type = Route("ActivityType");
+
+ if (string.IsNullOrWhiteSpace(type))
+ {
+ AddError("ActivityType is required");
+ await SendErrorsAsync(cancellation: ct);
+ return;
+ }
+
+ var config = await store.GetConnectionDescriptorAsync(type, ct);
+ await SendOkAsync(config, ct);
+ }
+}
diff --git a/src/modules/Elsa.Connections.Api/Endpoints/ActivityConnectionDescriptor/List/Endpoint.cs b/src/modules/Elsa.Connections.Api/Endpoints/ActivityConnectionDescriptor/List/Endpoint.cs
new file mode 100644
index 000000000..62e35582b
--- /dev/null
+++ b/src/modules/Elsa.Connections.Api/Endpoints/ActivityConnectionDescriptor/List/Endpoint.cs
@@ -0,0 +1,22 @@
+using Elsa.Abstractions;
+using Elsa.Common.Models;
+using Elsa.Connections.Contracts;
+using Elsa.Connections.Models;
+using Elsa.Models;
+
+namespace Elsa.Connections.Api.Endpoints.ActivityConnectionDescriptor.List;
+
+public class List(IConnectionDescriptorRegistry registry) : ElsaEndpointWithoutRequest>
+{
+ public override void Configure()
+ {
+ Get("/connection-configuration/descriptors");
+ ConfigurePermissions($"{Constants.PermissionsNamespace}/descriptor:read");
+ }
+
+ public override Task> ExecuteAsync(CancellationToken ct)
+ {
+ var descriptors = registry.ListAll().ToList();
+ return Task.FromResult(new PagedListResponse(Page.Of(descriptors, descriptors.Count)) );
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Connections.Api/Endpoints/Add/Endpoint.cs b/src/modules/Elsa.Connections.Api/Endpoints/Add/Endpoint.cs
new file mode 100644
index 000000000..8f7cf39c5
--- /dev/null
+++ b/src/modules/Elsa.Connections.Api/Endpoints/Add/Endpoint.cs
@@ -0,0 +1,53 @@
+using Elsa.Abstractions;
+using Elsa.Connections.Models;
+using Elsa.Connections.Persistence.Contracts;
+using Elsa.Connections.Persistence.Filters;
+using Elsa.Workflows;
+using Elsa.Connections.Persistence.Entities;
+using Elsa.Connections.Api.Extensions;
+
+namespace Elsa.Connections.Api.Endpoints.Add;
+
+public class Endpoint(IConnectionStore store, IIdentityGenerator identityGenerator) : ElsaEndpoint
+{
+ public override void Configure()
+ {
+ Post("/connection-configuration");
+ ConfigurePermissions($"{Constants.PermissionsNamespace}:write");
+ }
+
+ public override async Task ExecuteAsync(ConnectionInputModel model, CancellationToken ct)
+ {
+ var isNameUnique = await IsNameUniqueAsync(model.Name, ct);
+
+ if (!isNameUnique)
+ {
+ AddError("A connection already exists with the specified name");
+ await SendErrorsAsync(cancellation: ct);
+ return null!;
+ }
+
+ var newEntity = new ConnectionDefinition
+ {
+ Id = identityGenerator.GenerateId(),
+ Name = model.Name,
+ Description = model.Description,
+ ConnectionType = model.ConnectionType,
+ ConnectionConfiguration = model.ConnectionConfiguration,
+ };
+ await store.AddAsync(newEntity, ct);
+ await SendOkAsync(ct);
+
+ return newEntity.ToModel();
+ }
+
+ private async Task IsNameUniqueAsync(string name, CancellationToken ct)
+ {
+ var filter = new ConnectionDefinitionFilter
+ {
+ Name = name
+ };
+ return await store.FindAsync(filter, ct) == null;
+ }
+}
+
diff --git a/src/modules/Elsa.Connections.Api/Endpoints/Delete/Endpoint.cs b/src/modules/Elsa.Connections.Api/Endpoints/Delete/Endpoint.cs
new file mode 100644
index 000000000..d8fda228e
--- /dev/null
+++ b/src/modules/Elsa.Connections.Api/Endpoints/Delete/Endpoint.cs
@@ -0,0 +1,27 @@
+using Elsa.Abstractions;
+using Elsa.Connections.Persistence.Contracts;
+
+namespace Elsa.Connections.Api.Endpoints.Delete;
+
+public class Endpoint(IConnectionStore store) : ElsaEndpoint
+{
+ public override void Configure()
+ {
+ Delete("/connection-configuration/{id}");
+ ConfigurePermissions($"{Constants.PermissionsNamespace}:delete");
+ }
+
+ public override async Task HandleAsync(Request req, CancellationToken ct)
+ {
+ var entity = await store.GetAsync(req.Id, ct);
+
+ if (entity == null)
+ {
+ await SendNotFoundAsync(ct);
+ return;
+ }
+
+ await store.DeleteAsync(entity, ct);
+ await SendOkAsync(ct);
+ }
+}
diff --git a/src/modules/Elsa.Connections.Api/Endpoints/Delete/Request.cs b/src/modules/Elsa.Connections.Api/Endpoints/Delete/Request.cs
new file mode 100644
index 000000000..6e33ffacf
--- /dev/null
+++ b/src/modules/Elsa.Connections.Api/Endpoints/Delete/Request.cs
@@ -0,0 +1,8 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Elsa.Connections.Api.Endpoints.Delete;
+
+public class Request
+{
+ [Required] public string Id { get; set; } = null!;
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Connections.Api/Endpoints/Get/Endpoint.cs b/src/modules/Elsa.Connections.Api/Endpoints/Get/Endpoint.cs
new file mode 100644
index 000000000..bbf6ab953
--- /dev/null
+++ b/src/modules/Elsa.Connections.Api/Endpoints/Get/Endpoint.cs
@@ -0,0 +1,32 @@
+using Elsa.Abstractions;
+using Elsa.Connections.Models;
+using Elsa.Connections.Persistence.Contracts;
+using Elsa.Connections.Api.Extensions;
+
+namespace Elsa.Connections.Api.Endpoints.Get;
+
+public class Endpoint(IConnectionStore store) : ElsaEndpoint
+{
+ public override void Configure()
+ {
+ Get("/connection-configuration/{id}");
+ ConfigurePermissions($"{Constants.PermissionsNamespace}:read");
+ }
+
+ public override async Task ExecuteAsync(Request req, CancellationToken ct)
+ {
+ var entity = await store.FindAsync(
+ new()
+ {
+ Id = req.Id
+ }, ct);
+
+ if (entity == null)
+ {
+ await SendNotFoundAsync(ct);
+ return null!;
+ }
+
+ return entity.ToModel();
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Connections.Api/Endpoints/Get/Request.cs b/src/modules/Elsa.Connections.Api/Endpoints/Get/Request.cs
new file mode 100644
index 000000000..933b0c2fb
--- /dev/null
+++ b/src/modules/Elsa.Connections.Api/Endpoints/Get/Request.cs
@@ -0,0 +1,8 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Elsa.Connections.Api.Endpoints.Get;
+
+public class Request
+{
+ [Required] public string Id { get; set; } = null!;
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Connections.Api/Endpoints/List/Endpoint.cs b/src/modules/Elsa.Connections.Api/Endpoints/List/Endpoint.cs
new file mode 100644
index 000000000..9f791b69e
--- /dev/null
+++ b/src/modules/Elsa.Connections.Api/Endpoints/List/Endpoint.cs
@@ -0,0 +1,24 @@
+using Elsa.Abstractions;
+using Elsa.Common.Models;
+using Elsa.Connections.Api.Extensions;
+using Elsa.Connections.Models;
+using Elsa.Connections.Persistence.Contracts;
+using Elsa.Models;
+
+namespace Elsa.Connections.Api.Endpoints.List;
+
+public class Endpoint(IConnectionStore store) : ElsaEndpointWithoutRequest>
+{
+ public override void Configure()
+ {
+ Get("/connection-configuration");
+ ConfigurePermissions($"{Constants.PermissionsNamespace}:read");
+ }
+
+ public override async Task> ExecuteAsync(CancellationToken ct)
+ {
+ var entities = await store.ListAsync(ct);
+ var models = entities.Select(x => x.ToModel()).ToList();
+ return new(Page.Of(models, models.Count()));
+ }
+}
diff --git a/src/modules/Elsa.Connections.Api/Endpoints/Update/Endpoint.cs b/src/modules/Elsa.Connections.Api/Endpoints/Update/Endpoint.cs
new file mode 100644
index 000000000..ffba22422
--- /dev/null
+++ b/src/modules/Elsa.Connections.Api/Endpoints/Update/Endpoint.cs
@@ -0,0 +1,55 @@
+using Elsa.Abstractions;
+using Elsa.Connections.Models;
+using Elsa.Connections.Persistence.Contracts;
+using Elsa.Connections.Persistence.Filters;
+using Elsa.Connections.Api.Extensions;
+
+namespace Elsa.Connections.Api.Endpoints.Update;
+
+public class Endpoint(IConnectionStore store) : ElsaEndpoint
+{
+ public override void Configure()
+ {
+ Put("/connection-configuration/{id}");
+ ConfigurePermissions($"{Constants.PermissionsNamespace}:write");
+ }
+
+ public override async Task ExecuteAsync(ConnectionInputModel model, CancellationToken ct)
+ {
+ var id = Route("id")!;
+ var entity = await store.GetAsync(id, ct);
+
+ if (entity == null)
+ {
+ await SendNotFoundAsync(ct);
+ return null!;
+ }
+
+ var isNameDuplicate = await IsNameDuplicateAsync(model.Name, id, ct);
+
+ if (isNameDuplicate)
+ {
+ AddError("Another connection already exist with the specified name");
+ await SendErrorsAsync(cancellation: ct);
+ return entity.ToModel();
+ }
+
+ entity.Name = model.Name;
+ entity.Description = model.Description;
+ entity.ConnectionConfiguration = model.ConnectionConfiguration;
+
+ await store.UpdateAsync(entity, ct);
+ return entity.ToModel();
+ }
+
+ private async Task IsNameDuplicateAsync(string name, string id, CancellationToken cancellationToken)
+ {
+ var entities = await store.FindAsync(new()
+ {
+ NotId = id,
+ Name = name
+ }, cancellationToken);
+
+ return entities != null;
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Connections.Api/Extensions/ConnectionDefinitionExtensions.cs b/src/modules/Elsa.Connections.Api/Extensions/ConnectionDefinitionExtensions.cs
new file mode 100644
index 000000000..fef90c617
--- /dev/null
+++ b/src/modules/Elsa.Connections.Api/Extensions/ConnectionDefinitionExtensions.cs
@@ -0,0 +1,19 @@
+using Elsa.Connections.Models;
+using Elsa.Connections.Persistence.Entities;
+
+namespace Elsa.Connections.Api.Extensions;
+
+public static class ConnectionDefinitionExtensions
+{
+ public static ConnectionModel ToModel(this ConnectionDefinition entity)
+ {
+ return new()
+ {
+ Id = entity.Id,
+ Name = entity.Name,
+ Description = entity.Description,
+ ConnectionType = entity.ConnectionType,
+ ConnectionConfiguration = entity.ConnectionConfiguration,
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Connections.Api/Extensions/ModuleExtensions.cs b/src/modules/Elsa.Connections.Api/Extensions/ModuleExtensions.cs
new file mode 100644
index 000000000..10cc54185
--- /dev/null
+++ b/src/modules/Elsa.Connections.Api/Extensions/ModuleExtensions.cs
@@ -0,0 +1,19 @@
+using Elsa.Connections.Api.Features;
+using Elsa.Features.Services;
+
+// ReSharper disable once CheckNamespace
+namespace Elsa.Extensions;
+
+///
+/// Extends with methods to Connection API endpoints.
+///
+public static class ModuleExtensions
+{
+ ///
+ /// Installs the Connections API feature.
+ ///
+ public static IModule UseConnectionsApi(this IModule module, Action? configure = null)
+ {
+ return module.Use(configure);
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Connections.Api/Features/ConnectionsApiFeature.cs b/src/modules/Elsa.Connections.Api/Features/ConnectionsApiFeature.cs
new file mode 100644
index 000000000..d3fc3c7a4
--- /dev/null
+++ b/src/modules/Elsa.Connections.Api/Features/ConnectionsApiFeature.cs
@@ -0,0 +1,22 @@
+using Elsa.Connections.Persistence.Features;
+using Elsa.Extensions;
+using Elsa.Features.Abstractions;
+using Elsa.Features.Attributes;
+using Elsa.Features.Services;
+using JetBrains.Annotations;
+
+namespace Elsa.Connections.Api.Features;
+
+///
+/// A feature that installs API endpoints to interact connections.
+///
+[DependsOn(typeof(ConnectionPersistenceFeature))]
+[UsedImplicitly]
+public class ConnectionsApiFeature(IModule module) : FeatureBase(module)
+{
+ ///
+ public override void Configure()
+ {
+ Module.AddFastEndpointsAssembly();
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Connections.Api/FodyWeavers.xml b/src/modules/Elsa.Connections.Api/FodyWeavers.xml
new file mode 100644
index 000000000..ac6b5b292
--- /dev/null
+++ b/src/modules/Elsa.Connections.Api/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/src/modules/Elsa.Connections.Core/Attributes/ConnectionActivityAttribute.cs b/src/modules/Elsa.Connections.Core/Attributes/ConnectionActivityAttribute.cs
new file mode 100644
index 000000000..9156be919
--- /dev/null
+++ b/src/modules/Elsa.Connections.Core/Attributes/ConnectionActivityAttribute.cs
@@ -0,0 +1,10 @@
+namespace Elsa.Connections.Attributes;
+
+[AttributeUsage(AttributeTargets.Class)]
+public class ConnectionActivityAttribute(string type) : Attribute
+{
+ ///
+ /// The TypeName of the connection
+ ///
+ public string Type { get; set; } = type;
+}
diff --git a/src/modules/Elsa.Connections.Core/Attributes/ConnectionPropertyAttribute.cs b/src/modules/Elsa.Connections.Core/Attributes/ConnectionPropertyAttribute.cs
new file mode 100644
index 000000000..98c6d79c3
--- /dev/null
+++ b/src/modules/Elsa.Connections.Core/Attributes/ConnectionPropertyAttribute.cs
@@ -0,0 +1,8 @@
+namespace Elsa.Connections.Attributes;
+
+public class ConnectionPropertyAttribute(string ns, string displayName, string? description = null) : Attribute
+{
+ public string? Namespace { get; set; } = ns;
+ public string? Description { get; set; } = description;
+ public string? DisplayName { get; set; } = displayName;
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Connections.Core/Attributes/ConnectionTypeAttribute.cs b/src/modules/Elsa.Connections.Core/Attributes/ConnectionTypeAttribute.cs
new file mode 100644
index 000000000..e000eb742
--- /dev/null
+++ b/src/modules/Elsa.Connections.Core/Attributes/ConnectionTypeAttribute.cs
@@ -0,0 +1,13 @@
+namespace Elsa.Connections.Attributes;
+
+public class ConnectionTypeAttribute : Attribute
+{
+ public ConnectionTypeAttribute(Type type)
+ {
+ Type = type;
+ }
+ ///
+ /// The TypeName of the connection
+ ///
+ public Type Type { get; set; }
+}
diff --git a/src/modules/Elsa.Connections.Core/Attributes/NoLogAttribute.cs b/src/modules/Elsa.Connections.Core/Attributes/NoLogAttribute.cs
new file mode 100644
index 000000000..80c4275c9
--- /dev/null
+++ b/src/modules/Elsa.Connections.Core/Attributes/NoLogAttribute.cs
@@ -0,0 +1,5 @@
+namespace Elsa.Connections.Attributes;
+
+public class NoLogAttribute : Attribute
+{
+}
diff --git a/src/modules/Elsa.Connections.Core/Contracts/IConnectionDescriptorRegistry.cs b/src/modules/Elsa.Connections.Core/Contracts/IConnectionDescriptorRegistry.cs
new file mode 100644
index 000000000..61e525987
--- /dev/null
+++ b/src/modules/Elsa.Connections.Core/Contracts/IConnectionDescriptorRegistry.cs
@@ -0,0 +1,40 @@
+using Elsa.Connections.Models;
+using Elsa.Workflows.Models;
+
+namespace Elsa.Connections.Contracts;
+
+///
+/// Store all connection descriptors available to the system
+///
+public interface IConnectionDescriptorRegistry
+{
+ ///
+ /// Adds a connection descriptor to the registry
+ ///
+ /// The type of the connection
+ /// The type desiptor of the connection
+ void Add(Type connectionType, ConnectionDescriptor connectionDescriptor);
+
+ ///
+ /// Removes an activity descriptor from the registry.
+ ///
+ /// The type of the connection.
+ ///
+ void Remove(Type connectionType, ActivityDescriptor descriptor);
+
+ ///
+ /// Returns all connection descriptors in the registry.
+ ///
+ /// All connection descriptors in the registry.
+ IEnumerable ListAll();
+
+ Type? Get(string type);
+
+ ///
+ /// Get the Input Descriptors for an connection
+ ///
+ /// The type of the connectn
+ ///
+ ///
+ Task> GetConnectionDescriptorAsync(string activityType, CancellationToken cancellationToken = default);
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Connections.Core/Contracts/IConnectionDescriptorRepository.cs b/src/modules/Elsa.Connections.Core/Contracts/IConnectionDescriptorRepository.cs
new file mode 100644
index 000000000..ffb11ba74
--- /dev/null
+++ b/src/modules/Elsa.Connections.Core/Contracts/IConnectionDescriptorRepository.cs
@@ -0,0 +1,6 @@
+namespace Elsa.Connections.Contracts;
+
+public interface IConnectionDescriptorRegistryEX
+{
+
+}
diff --git a/src/modules/Elsa.Connections.Core/Contracts/IConnectionProperty.cs b/src/modules/Elsa.Connections.Core/Contracts/IConnectionProperty.cs
new file mode 100644
index 000000000..fd89db4df
--- /dev/null
+++ b/src/modules/Elsa.Connections.Core/Contracts/IConnectionProperty.cs
@@ -0,0 +1,11 @@
+using System.Text.Json.Serialization;
+
+namespace Elsa.Connections.Contracts;
+interface IConnectionProperty
+{
+ public string ConnectionName { get; set; }
+
+ [JsonIgnore]
+ public object Properties { get; set; }
+}
+
diff --git a/src/modules/Elsa.Connections.Core/Converters/Int32Converter.cs b/src/modules/Elsa.Connections.Core/Converters/Int32Converter.cs
new file mode 100644
index 000000000..adb992078
--- /dev/null
+++ b/src/modules/Elsa.Connections.Core/Converters/Int32Converter.cs
@@ -0,0 +1,29 @@
+using System.Text.Json;
+
+namespace Elsa.Connections.Converters;
+
+public class Int32Converter : System.Text.Json.Serialization.JsonConverter
+{
+ public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (reader.TokenType == JsonTokenType.String)
+ {
+ var stringValue = reader.GetString();
+ if (int.TryParse(stringValue, out var value))
+ {
+ return value;
+ }
+ }
+ else if (reader.TokenType == JsonTokenType.Number)
+ {
+ return reader.GetInt32();
+ }
+
+ throw new JsonException();
+ }
+
+ public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options)
+ {
+ writer.WriteNumberValue(value);
+ }
+}
diff --git a/src/modules/Elsa.Connections.Core/Elsa.Connections.Core.csproj b/src/modules/Elsa.Connections.Core/Elsa.Connections.Core.csproj
new file mode 100644
index 000000000..b92c7e94f
--- /dev/null
+++ b/src/modules/Elsa.Connections.Core/Elsa.Connections.Core.csproj
@@ -0,0 +1,17 @@
+
+
+
+ Provides an connection framework to use connection abtraction in activities
+ elsa module connection abstratction
+ Elsa.Connections
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/Elsa.Connections.Core/Extensions/LogConnectionExtensions.cs b/src/modules/Elsa.Connections.Core/Extensions/LogConnectionExtensions.cs
new file mode 100644
index 000000000..a11192cb8
--- /dev/null
+++ b/src/modules/Elsa.Connections.Core/Extensions/LogConnectionExtensions.cs
@@ -0,0 +1,24 @@
+// ReSharper disable once CheckNamespace
+
+using Microsoft.Extensions.Logging;
+
+namespace Elsa.Connections.Core.Extensions;
+
+///
+///
+///
+public static partial class LogConnectionExtensions
+{
+ [LoggerMessage(
+ Level = LogLevel.Warning,
+ Message = "Connection {name} was not found"
+ )]
+ public static partial void LogConnectionNotFound(ILogger logger, string name);
+
+ [LoggerMessage(
+ Level = LogLevel.Warning,
+ Message = "Connection is null"
+ )]
+ public static partial void LogConnectionIsNull(ILogger logger);
+
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Connections.Core/Extensions/ModuleExtensions.cs b/src/modules/Elsa.Connections.Core/Extensions/ModuleExtensions.cs
new file mode 100644
index 000000000..d68959b7a
--- /dev/null
+++ b/src/modules/Elsa.Connections.Core/Extensions/ModuleExtensions.cs
@@ -0,0 +1,33 @@
+using Elsa.Connections.Features;
+using Elsa.Connections.Middleware;
+using Elsa.Features.Services;
+using Elsa.Workflows;
+using Elsa.Workflows.Pipelines.ActivityExecution;
+
+// ReSharper disable once CheckNamespace
+namespace Elsa.Extensions;
+
+///
+/// Extends with methods to install Semantic Kernel API endpoints.
+///
+public static class ModuleExtensions
+{
+ ///
+ /// Installs the Semantic Kernel API feature.
+ ///
+ public static IModule UseConnections(this IModule module, Action? configure = null)
+ {
+ return module.Use(configure);
+ }
+}
+
+///
+/// Adds extension methods to .
+///
+public static class ConnectionMiddlewareExtensions
+{
+ ///
+ /// Installs the component in the activity execution pipeline.
+ ///
+ public static IActivityExecutionPipelineBuilder UseConnectionMiddleware(this IActivityExecutionPipelineBuilder pipelineBuilder) => pipelineBuilder.UseMiddleware();
+}
diff --git a/src/modules/Elsa.Connections.Core/Features/ConnectionsFeatures.cs b/src/modules/Elsa.Connections.Core/Features/ConnectionsFeatures.cs
new file mode 100644
index 000000000..8fc227e7a
--- /dev/null
+++ b/src/modules/Elsa.Connections.Core/Features/ConnectionsFeatures.cs
@@ -0,0 +1,73 @@
+using System.Reflection;
+using Elsa.Connections.Attributes;
+using Elsa.Connections.Contracts;
+using Elsa.Connections.Filters;
+using Elsa.Connections.ServiceProvider;
+using Elsa.Connections.Services;
+using Elsa.Connections.UIHints;
+using Elsa.Extensions;
+using Elsa.Features.Abstractions;
+using Elsa.Features.Attributes;
+using Elsa.Features.Services;
+using Elsa.Workflows;
+using Elsa.Workflows.Pipelines.ActivityExecution;
+using Elsa.Workflows.Features;
+using JetBrains.Annotations;
+using Microsoft.Extensions.DependencyInjection;
+using Elsa.Connections.Middleware;
+using Elsa.Connections.Models;
+
+namespace Elsa.Connections.Features;
+
+///
+/// A feature that installs API endpoints to interact with skilled agents.
+///
+[DependsOn(typeof(WorkflowsFeature))]
+[UsedImplicitly]
+public class ConnectionsFeatures(IModule module) : FeatureBase(module)
+{
+
+ ///
+ /// A set of connection types to make available to the system.
+ ///
+ public HashSet ConnectionTypes { get; } = [];
+
+ ///
+ public override void Apply()
+ {
+ // Obfuscate Connection Properties.
+ Services.AddActivityStateFilter();
+ // Activity property options providers.
+ Services.AddScoped();
+
+ Services.AddSingleton();
+
+ //UIHints
+ Services.AddScoped();
+
+ Services.Configure(options =>
+ {
+ foreach (var connectionType in ConnectionTypes.Distinct())
+ options.ConnectionTypes.Add(connectionType);
+ });
+ }
+ public override void Configure()
+ {
+ //TODO: Need to insert the middleware just before the BackgroundActivityInvoker
+ //How to be sure that it is inserted before?
+ var workflowFeature = Module.Configure()
+ .WithDefaultActivityExecutionPipeline(pipeline => pipeline.Insert(3));
+ base.Configure();
+ }
+ public ConnectionsFeatures AddConnectionsFrom()
+ {
+ var connectionTypes = typeof(TMarker).Assembly.GetExportedTypes()
+ .Where(x => x.GetCustomAttribute() != null )
+ .ToList();
+
+ ConnectionTypes.AddRange(connectionTypes);
+
+ return this;
+ }
+
+}
diff --git a/src/modules/Elsa.Connections.Core/Filters/PropertyAttributeFilter.cs b/src/modules/Elsa.Connections.Core/Filters/PropertyAttributeFilter.cs
new file mode 100644
index 000000000..14773afa7
--- /dev/null
+++ b/src/modules/Elsa.Connections.Core/Filters/PropertyAttributeFilter.cs
@@ -0,0 +1,25 @@
+using Elsa.Connections.Attributes;
+using Elsa.Workflows;
+
+namespace Elsa.Connections.Filters;
+
+public class PropertyAttributeFilter : ActivityStateFilterBase
+{
+ protected override ActivityStateFilterResult OnExecute(ActivityStateFilterContext context)
+ {
+ var inputDescriptor = context.InputDescriptor;
+
+ if (Attribute.IsDefined(inputDescriptor.PropertyInfo, typeof(NoLogAttribute)))
+ {
+ var contextValue = context.Value.GetProperty("connectionName").GetString();
+
+ if (contextValue == null)
+ return ActivityStateFilterResult.Pass();
+
+ var maskedValue = $"**** see connection information for {contextValue} ****";
+ return Filtered(maskedValue);
+ }
+
+ return ActivityStateFilterResult.Pass();
+ }
+}
diff --git a/src/modules/Elsa.Connections.Core/FodyWeavers.xml b/src/modules/Elsa.Connections.Core/FodyWeavers.xml
new file mode 100644
index 000000000..ac6b5b292
--- /dev/null
+++ b/src/modules/Elsa.Connections.Core/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/src/modules/Elsa.Connections.Core/Middleware/ConnectionMiddleware.cs b/src/modules/Elsa.Connections.Core/Middleware/ConnectionMiddleware.cs
new file mode 100644
index 000000000..736470521
--- /dev/null
+++ b/src/modules/Elsa.Connections.Core/Middleware/ConnectionMiddleware.cs
@@ -0,0 +1,71 @@
+using System.Text.Json;
+using Elsa.Connections.Attributes;
+using Elsa.Connections.Models;
+using Elsa.Extensions;
+using Elsa.Workflows;
+using Elsa.Workflows.Pipelines.ActivityExecution;
+using JetBrains.Annotations;
+using Microsoft.Extensions.Logging;
+using Elsa.Connections.Core.Extensions;
+using Elsa.Connections.Converters;
+using Elsa.Connections.Persistence.Contracts;
+
+namespace Elsa.Connections.Middleware;
+
+///
+/// An activity execution middleware component that injects connection properties from Connection Name.
+///
+[UsedImplicitly]
+public class ConnectionMiddleware(ActivityMiddlewareDelegate next
+ , IConnectionStore connectionStore
+ ,ILogger logger)
+ : IActivityExecutionMiddleware
+{
+ private static JsonSerializerOptions? _serializerOptions;
+
+ private static JsonSerializerOptions SerializerOptions =>
+ _serializerOptions ??= new JsonSerializerOptions
+ {
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+ }.WithConverters(new Int32Converter());
+
+ ///
+ public async ValueTask InvokeAsync(ActivityExecutionContext context)
+ {
+ var activityDescriptor = context.ActivityDescriptor;
+
+ if (activityDescriptor.Attributes.Any(attr => attr.GetType() == typeof(ConnectionActivityAttribute)))
+ {
+ var inputDescriptors = activityDescriptor.Inputs.Where(x => x.PropertyInfo?.PropertyType.GetGenericTypeDefinition() == typeof(ConnectionProperties<>)).ToList();
+
+ if (inputDescriptors.Count > 0)
+ {
+ var input = inputDescriptors[0];
+ var propertyType = input.PropertyInfo?.PropertyType.GetGenericArguments()[0];
+
+ dynamic inputValue = input.ValueGetter(context.Activity)!;
+ var connectionName = (string)inputValue.ConnectionName;
+
+ if (connectionName == null)
+ LogConnectionExtensions.LogConnectionIsNull(logger);
+ else
+ {
+ //Get connection from store, if exist,
+ var connectionConfiguration = await connectionStore.FindAsync(new Persistence.Filters.ConnectionDefinitionFilter() { Name = connectionName });
+ if (connectionConfiguration != null)
+ {
+ dynamic deserializedjson = JsonSerializer.Deserialize(connectionConfiguration.ConnectionConfiguration, propertyType, SerializerOptions);
+
+ inputValue.Properties = deserializedjson;
+
+ input.ValueSetter(context.Activity, inputValue);
+ }
+ else
+ LogConnectionExtensions.LogConnectionNotFound(logger, connectionName);
+ }
+ }
+ }
+
+ await next(context);
+ }
+}
diff --git a/src/modules/Elsa.Connections.Core/Models/ConnectionOptions.cs b/src/modules/Elsa.Connections.Core/Models/ConnectionOptions.cs
new file mode 100644
index 000000000..bf9dc94d3
--- /dev/null
+++ b/src/modules/Elsa.Connections.Core/Models/ConnectionOptions.cs
@@ -0,0 +1,9 @@
+namespace Elsa.Connections.Models;
+
+public class ConnectionOptions
+{
+ ///
+ /// A collection of connection types that are available to the system.
+ ///
+ public HashSet ConnectionTypes { get; set; } = new();
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Connections.Core/ServiceProvider/ConnectionOptionsProvider.cs b/src/modules/Elsa.Connections.Core/ServiceProvider/ConnectionOptionsProvider.cs
new file mode 100644
index 000000000..71657877e
--- /dev/null
+++ b/src/modules/Elsa.Connections.Core/ServiceProvider/ConnectionOptionsProvider.cs
@@ -0,0 +1,40 @@
+using System.Reflection;
+using Elsa.Connections.Attributes;
+using Elsa.Connections.Persistence.Contracts;
+using Elsa.Workflows.UIHints.Dropdown;
+
+namespace Elsa.Connections.ServiceProvider;
+
+public class ConnectionOptionsProvider(IConnectionStore store) : DropDownOptionsProviderBase
+{
+ public new async ValueTask> GetUIPropertiesAsync(PropertyInfo propertyInfo, object? context, CancellationToken cancellationToken = default)
+ {
+ var options = await base.GetUIPropertiesAsync(propertyInfo, context, cancellationToken);
+ options.Add("Refresh", true);
+ return options;
+ }
+
+ protected override async ValueTask> GetItemsAsync(PropertyInfo propertyInfo, object? context, CancellationToken cancellationToken)
+ {
+ var connection = new List();
+ var connectionType = propertyInfo.GetCustomAttribute()?.Type;
+
+ if (connectionType == null)
+ return connection;
+
+ var connections = await store.FindManyAsync(new() { Type = connectionType.ToString() }, cancellationToken);
+
+ foreach(var conn in connections)
+ connection.Add(new(conn.Name, conn.Name));
+
+ return connection;
+ }
+
+ protected override IDictionary GetUIPropertyAdditionalOptions()
+ {
+ return new Dictionary
+ {
+ ["Refresh"] = true
+ };
+ }
+}
diff --git a/src/modules/Elsa.Connections.Core/Services/ConnectionRegistry.cs b/src/modules/Elsa.Connections.Core/Services/ConnectionRegistry.cs
new file mode 100644
index 000000000..6c94e1b59
--- /dev/null
+++ b/src/modules/Elsa.Connections.Core/Services/ConnectionRegistry.cs
@@ -0,0 +1,72 @@
+using System.Collections.Concurrent;
+using System.Reflection;
+using Elsa.Connections.Attributes;
+using Elsa.Connections.Contracts;
+using Elsa.Connections.Models;
+using Elsa.Workflows;
+using Elsa.Workflows.Models;
+using Microsoft.Extensions.Options;
+
+namespace Elsa.Connections.Services;
+
+public class ConnectionRegistry(IOptions options, IActivityDescriber describer) : IConnectionDescriptorRegistry
+{
+ private readonly ConcurrentDictionary _connectionDescriptors = new();
+ private readonly ConnectionOptions _options = options.Value;
+
+ public void Add(Type connectionType, ConnectionDescriptor connectionDescriptor)
+ {
+ var descriptor = DescribeConnection(connectionType);
+
+ _connectionDescriptors.TryAdd(connectionType, descriptor);
+ }
+
+ public IEnumerable ListAll()
+ {
+ foreach (var connectionType in _options.ConnectionTypes)
+ yield return DescribeConnection(connectionType);
+ }
+
+ public void Remove(Type connectionType, ActivityDescriptor descriptor)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Type? Get(string type)
+ {
+ return _options.ConnectionTypes.FirstOrDefault(c => c.ToString() == type);
+ }
+
+ public async Task> GetConnectionDescriptorAsync(string activityType, CancellationToken cancellationToken = default)
+ {
+ var propertyType = Get(activityType);
+
+ if (propertyType == null)
+ return [];
+
+ var connectionInputProperty = describer.GetInputProperties(propertyType);
+ var connectionInputDescriptor = await DescribeInputPropertiesAsync(connectionInputProperty, cancellationToken);
+
+ return connectionInputDescriptor;
+ }
+
+ private async Task> DescribeInputPropertiesAsync(IEnumerable properties, CancellationToken cancellationToken = default)
+ {
+ return await Task.WhenAll(properties.Select(async x => await describer.DescribeInputPropertyAsync(x, cancellationToken)));
+ }
+
+ private ConnectionDescriptor DescribeConnection(Type connectionType)
+ {
+ var connectionAttribute = connectionType.GetCustomAttribute();
+
+ if (connectionAttribute == null)
+ throw new ArgumentNullException($"{connectionType} is not a valid connection, make sure [ConnectionPropertyAttribute] is set ");
+
+ return new(
+ connectionType.ToString(),
+ connectionAttribute.Description,
+ connectionAttribute.Namespace,
+ connectionAttribute.DisplayName
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Connections.Core/UIHints/ConnectionDropDownUIHintHandler.cs b/src/modules/Elsa.Connections.Core/UIHints/ConnectionDropDownUIHintHandler.cs
new file mode 100644
index 000000000..060ad752b
--- /dev/null
+++ b/src/modules/Elsa.Connections.Core/UIHints/ConnectionDropDownUIHintHandler.cs
@@ -0,0 +1,21 @@
+using System.Reflection;
+using Elsa.Workflows;
+using Elsa.Workflows.UIHints;
+using Elsa.Workflows.UIHints.Dropdown;
+
+namespace Elsa.Connections.UIHints;
+
+///
+/// UIHint Handler that use DropDown for Connection Property
+///
+public class ConnectionDropDownUIHintHandler : IUIHintHandler
+{
+ ///
+ public string UIHint => $"connection-{InputUIHints.DropDown}";
+
+ ///
+ public ValueTask> GetPropertyUIHandlersAsync(PropertyInfo propertyInfo, CancellationToken cancellationToken)
+ {
+ return new([typeof(StaticDropDownOptionsProvider)]);
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Connections.Models/ConnectionDescriptor.cs b/src/modules/Elsa.Connections.Models/ConnectionDescriptor.cs
new file mode 100644
index 000000000..b79c1a516
--- /dev/null
+++ b/src/modules/Elsa.Connections.Models/ConnectionDescriptor.cs
@@ -0,0 +1,3 @@
+namespace Elsa.Connections.Models;
+
+public record ConnectionDescriptor(string Type, string? Description, string? Namespace, string? ProviderName);
diff --git a/src/modules/Elsa.Connections.Models/ConnectionInputModel.cs b/src/modules/Elsa.Connections.Models/ConnectionInputModel.cs
new file mode 100644
index 000000000..497cdb1a8
--- /dev/null
+++ b/src/modules/Elsa.Connections.Models/ConnectionInputModel.cs
@@ -0,0 +1,27 @@
+using System.ComponentModel.DataAnnotations;
+using System.Text.Json.Nodes;
+
+namespace Elsa.Connections.Models;
+
+public class ConnectionInputModel
+{
+ ///
+ /// The Name of the Connection
+ ///
+ [Required] public string Name { get; set; } = null!;
+
+ ///
+ /// The Description of the Connection
+ ///
+ public string? Description { get; set; }
+
+ ///
+ /// The Configuration of the Connection in a Json Format
+ ///
+ public JsonObject ConnectionConfiguration { get; set; } = new();
+
+ ///
+ /// The Connection Type Name
+ ///
+ [Required] public string ConnectionType { get; set; } = null!;
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Connections.Models/ConnectionModel.cs b/src/modules/Elsa.Connections.Models/ConnectionModel.cs
new file mode 100644
index 000000000..6caa8959f
--- /dev/null
+++ b/src/modules/Elsa.Connections.Models/ConnectionModel.cs
@@ -0,0 +1,11 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Elsa.Connections.Models;
+
+public class ConnectionModel : ConnectionInputModel
+{
+ ///
+ /// The Id of the Connection
+ ///
+ [Required] public string Id { get; set; } = null!;
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Connections.Models/ConnectionProperties.cs b/src/modules/Elsa.Connections.Models/ConnectionProperties.cs
new file mode 100644
index 000000000..ebed4e6d7
--- /dev/null
+++ b/src/modules/Elsa.Connections.Models/ConnectionProperties.cs
@@ -0,0 +1,20 @@
+using System.Text.Json.Serialization;
+
+namespace Elsa.Connections.Models;
+
+public class ConnectionProperties where T : class, new()
+{
+ ///
+ /// Creates a new instance of the class.
+ ///
+ [JsonConstructor]
+ public ConnectionProperties()
+ {
+ }
+
+ public string? ConnectionName { get; set; }
+
+ [JsonIgnore]
+ public T Properties { get; set; } = new();
+}
+
diff --git a/src/modules/Elsa.Connections.Models/Elsa.Connections.Models.csproj b/src/modules/Elsa.Connections.Models/Elsa.Connections.Models.csproj
new file mode 100644
index 000000000..1bd63d620
--- /dev/null
+++ b/src/modules/Elsa.Connections.Models/Elsa.Connections.Models.csproj
@@ -0,0 +1,9 @@
+
+
+
+ Provides models that can be shared between the API and API clients
+ elsa module connections api
+ Elsa.Connections
+
+
+
diff --git a/src/modules/Elsa.Connections.Models/FodyWeavers.xml b/src/modules/Elsa.Connections.Models/FodyWeavers.xml
new file mode 100644
index 000000000..ba625ed13
--- /dev/null
+++ b/src/modules/Elsa.Connections.Models/FodyWeavers.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite/DbContextFactories.cs b/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite/DbContextFactories.cs
new file mode 100644
index 000000000..cd4fca536
--- /dev/null
+++ b/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite/DbContextFactories.cs
@@ -0,0 +1,15 @@
+using Elsa.EntityFrameworkCore.Abstractions;
+using Elsa.EntityFrameworkCore.Extensions;
+using JetBrains.Annotations;
+using Microsoft.EntityFrameworkCore;
+
+namespace Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite;
+
+[UsedImplicitly]
+public class SqliteConnectionDbContextFactory : DesignTimeDbContextFactoryBase
+{
+ protected override void ConfigureBuilder(DbContextOptionsBuilder builder, string connectionString)
+ {
+ builder.UseElsaSqlite(GetType().Assembly, connectionString);
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite/Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite.csproj b/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite/Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite.csproj
new file mode 100644
index 000000000..0f350f1e2
--- /dev/null
+++ b/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite/Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite.csproj
@@ -0,0 +1,19 @@
+
+
+
+ Provides an EF Core migrations for SQLite for the Connection Persistence module.
+ elsa module persistence efcore entity framework core sqlite
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite/FodyWeavers.xml b/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite/FodyWeavers.xml
new file mode 100644
index 000000000..ac6b5b292
--- /dev/null
+++ b/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite/Migrations/20250212220543_V3_3.Designer.cs b/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite/Migrations/20250212220543_V3_3.Designer.cs
new file mode 100644
index 000000000..8f3eb3658
--- /dev/null
+++ b/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite/Migrations/20250212220543_V3_3.Designer.cs
@@ -0,0 +1,60 @@
+//
+using Elsa.Connections.Persistence.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite.Migrations
+{
+ [DbContext(typeof(ConnectionDbContext))]
+ [Migration("20250212220543_V3_3")]
+ partial class V3_3
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasDefaultSchema("Elsa")
+ .HasAnnotation("ProductVersion", "8.0.12");
+
+ modelBuilder.Entity("Elsa.Connections.Persistence.Entities.ConnectionDefinition", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT");
+
+ b.Property("ConnectionConfiguration")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("ConnectionType")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Description")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("TenantId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .HasDatabaseName("IX_ConnectionDefinition_Name");
+
+ b.HasIndex("TenantId")
+ .HasDatabaseName("IX_ConnectionDefinition_TenantId");
+
+ b.ToTable("ConnectionDefinitions", "Elsa");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite/Migrations/20250212220543_V3_3.cs b/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite/Migrations/20250212220543_V3_3.cs
new file mode 100644
index 000000000..dd64e3a5f
--- /dev/null
+++ b/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite/Migrations/20250212220543_V3_3.cs
@@ -0,0 +1,62 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite.Migrations
+{
+ ///
+ public partial class V3_3 : Migration
+ {
+ private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema;
+
+ ///
+ public V3_3(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema)
+ {
+ _schema = schema;
+ }
+
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.EnsureSchema(
+ name: _schema.Schema);
+
+ migrationBuilder.CreateTable(
+ name: "ConnectionDefinitions",
+ schema: _schema.Schema,
+ columns: table => new
+ {
+ Id = table.Column(type: "TEXT", nullable: false),
+ Name = table.Column(type: "TEXT", nullable: false),
+ Description = table.Column(type: "TEXT", nullable: true),
+ ConnectionConfiguration = table.Column(type: "TEXT", nullable: false),
+ ConnectionType = table.Column(type: "TEXT", nullable: false),
+ TenantId = table.Column(type: "TEXT", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_ConnectionDefinitions", x => x.Id);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ConnectionDefinition_Name",
+ schema: _schema.Schema,
+ table: "ConnectionDefinitions",
+ column: "Name");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ConnectionDefinition_TenantId",
+ schema: _schema.Schema,
+ table: "ConnectionDefinitions",
+ column: "TenantId");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "ConnectionDefinitions",
+ schema: _schema.Schema);
+ }
+ }
+}
diff --git a/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite/Migrations/ConnectionDbContextModelSnapshot.cs b/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite/Migrations/ConnectionDbContextModelSnapshot.cs
new file mode 100644
index 000000000..50c535a68
--- /dev/null
+++ b/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite/Migrations/ConnectionDbContextModelSnapshot.cs
@@ -0,0 +1,57 @@
+//
+using Elsa.Connections.Persistence.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite.Migrations
+{
+ [DbContext(typeof(ConnectionDbContext))]
+ partial class ConnectionDbContextModelSnapshot : ModelSnapshot
+ {
+ protected override void BuildModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasDefaultSchema("Elsa")
+ .HasAnnotation("ProductVersion", "8.0.12");
+
+ modelBuilder.Entity("Elsa.Connections.Persistence.Entities.ConnectionDefinition", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT");
+
+ b.Property("ConnectionConfiguration")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("ConnectionType")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Description")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("TenantId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .HasDatabaseName("IX_ConnectionDefinition_Name");
+
+ b.HasIndex("TenantId")
+ .HasDatabaseName("IX_ConnectionDefinition_TenantId");
+
+ b.ToTable("ConnectionDefinitions", "Elsa");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite/SqliteProvidersExtensions.cs b/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite/SqliteProvidersExtensions.cs
new file mode 100644
index 000000000..9a38e3722
--- /dev/null
+++ b/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite/SqliteProvidersExtensions.cs
@@ -0,0 +1,28 @@
+using System.Reflection;
+using Elsa.Connections.Persistence.EntityFrameworkCore;
+
+// ReSharper disable once CheckNamespace
+namespace Elsa.EntityFrameworkCore.Extensions;
+
+///
+/// Provides extensions to configure EF Core to use Sqlite.
+///
+public static class ConnectionsSqliteProvidersExtensions
+{
+ private static Assembly Assembly => typeof(ConnectionsSqliteProvidersExtensions).Assembly;
+
+ ///
+ /// Configures the feature to use Sqlite.
+ ///
+ public static EFCoreConnectionPersistenceFeature UseSqlite(this EFCoreConnectionPersistenceFeature feature, string? connectionString = null, ElsaDbContextOptions? options = null)
+ {
+ feature.UseSqlite(Assembly, connectionString, options);
+ return feature;
+ }
+
+ public static EFCoreConnectionPersistenceFeature UseSqlite(this EFCoreConnectionPersistenceFeature feature, Func connectionStringFunc, ElsaDbContextOptions? options = null)
+ {
+ feature.UseSqlite(Assembly, connectionStringFunc, options);
+ return feature;
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite/efcore-3.3.sh b/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite/efcore-3.3.sh
new file mode 100644
index 000000000..e5eb4289e
--- /dev/null
+++ b/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite/efcore-3.3.sh
@@ -0,0 +1 @@
+ef-migration-runtime-schema --interface Elsa.EntityFrameworkCore.IElsaDbContextSchema --efOptions "migrations add V3_3 -c ConnectionDbContext -o Migrations"
\ No newline at end of file
diff --git a/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore/Configurations.cs b/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore/Configurations.cs
new file mode 100644
index 000000000..cc2a3167d
--- /dev/null
+++ b/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore/Configurations.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Elsa.Connections.Models;
+using Elsa.Connections.Persistence.Entities;
+using Elsa.EntityFrameworkCore.Extensions;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace Elsa.Connections.Persistence.EntityFrameworkCore;
+public class Configurations : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder.Property(x => x.ConnectionConfiguration).HasJsonValueConversion();
+ builder.HasIndex(x => x.Name).HasDatabaseName($"IX_{nameof(ConnectionDefinition)}_{nameof(ConnectionDefinition.Name)}");
+ builder.HasIndex(x => x.TenantId).HasDatabaseName($"IX_{nameof(ConnectionDefinition)}_{nameof(ConnectionDefinition.TenantId)}");
+
+ }
+}
diff --git a/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore/DbContext.cs b/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore/DbContext.cs
new file mode 100644
index 000000000..f1b521744
--- /dev/null
+++ b/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore/DbContext.cs
@@ -0,0 +1,34 @@
+using Elsa.Connections.Models;
+using Elsa.EntityFrameworkCore;
+using Elsa.Connections.Persistence.Entities;
+using JetBrains.Annotations;
+using Microsoft.EntityFrameworkCore;
+
+namespace Elsa.Connections.Persistence.EntityFrameworkCore;
+
+
+///
+/// Db Context for the Connection Module
+///
+[UsedImplicitly]
+public class ConnectionDbContext : ElsaDbContextBase
+{
+ ///
+ public ConnectionDbContext(DbContextOptions options, IServiceProvider serviceProvider)
+ : base(options, serviceProvider)
+ {
+ }
+
+ ///
+ /// The Connection DB set.
+ ///
+ public DbSet ConnectionDefinitions { get; set; } = default!;
+
+ ///
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ var configuration = new Configurations();
+ modelBuilder.ApplyConfiguration(configuration);
+ base.OnModelCreating(modelBuilder);
+ }
+}
diff --git a/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore/EFCoreConnectionStore.cs b/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore/EFCoreConnectionStore.cs
new file mode 100644
index 000000000..bfe4a6203
--- /dev/null
+++ b/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore/EFCoreConnectionStore.cs
@@ -0,0 +1,54 @@
+using Elsa.Connections.Persistence.Contracts;
+using Elsa.Connections.Persistence.Entities;
+using Elsa.Connections.Persistence.Filters;
+using Elsa.EntityFrameworkCore;
+using JetBrains.Annotations;
+
+namespace Elsa.Connections.Persistence.EntityFrameworkCore;
+
+///
+/// An EF Core implementation of .
+///
+[UsedImplicitly]
+public class EFCoreConnectionStore(EntityStore store) : IConnectionStore
+{
+ public Task AddAsync(ConnectionDefinition entity, CancellationToken cancellationToken = default)
+ {
+ return store.AddAsync(entity, cancellationToken);
+ }
+
+ public Task UpdateAsync(ConnectionDefinition entity, CancellationToken cancellationToken = default)
+ {
+ return store.UpdateAsync(entity, cancellationToken);
+ }
+
+ public Task GetAsync(string id, CancellationToken cancellationToken = default)
+ {
+ var filter = new ConnectionDefinitionFilter
+ {
+ Id = id
+ };
+
+ return FindAsync(filter, cancellationToken);
+ }
+
+ public Task FindAsync(ConnectionDefinitionFilter filter, CancellationToken cancellationToken = default)
+ {
+ return store.FindAsync(filter.Apply, cancellationToken);
+ }
+
+ public Task> ListAsync(CancellationToken cancellationToken = default)
+ {
+ return store.ListAsync(cancellationToken);
+ }
+
+ public Task DeleteAsync(ConnectionDefinition entity, CancellationToken cancellationToken = default)
+ {
+ return store.DeleteAsync(entity, cancellationToken);
+ }
+
+ public Task> FindManyAsync(ConnectionDefinitionFilter filter, CancellationToken cancellationToken = default)
+ {
+ return store.QueryAsync(filter.Apply, cancellationToken);
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore/Elsa.Connections.Persistence.EntityFrameworkCore.csproj b/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore/Elsa.Connections.Persistence.EntityFrameworkCore.csproj
new file mode 100644
index 000000000..0931ad805
--- /dev/null
+++ b/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore/Elsa.Connections.Persistence.EntityFrameworkCore.csproj
@@ -0,0 +1,13 @@
+
+
+
+ Provides an Entity Framework Core provider for the persistence layer of the Connection module
+ elsa connection persistence efcore entity framework core
+
+
+
+
+
+
+
+
diff --git a/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore/Extensions.cs b/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore/Extensions.cs
new file mode 100644
index 000000000..718e6446c
--- /dev/null
+++ b/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore/Extensions.cs
@@ -0,0 +1,22 @@
+using Elsa.Connections.Persistence.Features;
+using Elsa.Connections.Persistence.EntityFrameworkCore;
+using JetBrains.Annotations;
+
+// ReSharper disable once CheckNamespace
+namespace Elsa.Agents;
+
+///
+/// Provides extensions to the feature.
+///
+[PublicAPI]
+public static class Extensions
+{
+ ///
+ /// Configures the to use EF Core persistence providers.
+ ///
+ public static ConnectionPersistenceFeature UseEntityFrameworkCore(this ConnectionPersistenceFeature feature, Action? configure = default)
+ {
+ feature.Module.Configure(configure);
+ return feature;
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore/Feature.cs b/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore/Feature.cs
new file mode 100644
index 000000000..5d8d9524b
--- /dev/null
+++ b/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore/Feature.cs
@@ -0,0 +1,34 @@
+using Elsa.Connections.Persistence.Entities;
+using Elsa.Connections.Persistence.Features;
+using Elsa.EntityFrameworkCore;
+using Elsa.EntityFrameworkCore.EntityHandlers;
+using Elsa.Features.Attributes;
+using Elsa.Features.Services;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Elsa.Connections.Persistence.EntityFrameworkCore;
+
+///
+/// Configures the default workflow runtime to use EF Core persistence providers.
+///
+[DependsOn(typeof(ConnectionPersistenceFeature))]
+public class EFCoreConnectionPersistenceFeature(IModule module) : PersistenceFeatureBase(module)
+{
+ ///
+ public override void Configure()
+ {
+ Module.Configure(feature =>
+ {
+ feature
+ .UseConnectionStore(sp=> sp.GetRequiredService())
+ ;
+ });
+ }
+
+ ///
+ public override void Apply()
+ {
+ base.Apply();
+ AddEntityStore();
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore/FodyWeavers.xml b/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore/FodyWeavers.xml
new file mode 100644
index 000000000..ac6b5b292
--- /dev/null
+++ b/src/modules/Elsa.Connections.Persistence.EntityFrameworkCore/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/src/modules/Elsa.Connections.Persistence/Contracts/IConnectionStore.cs b/src/modules/Elsa.Connections.Persistence/Contracts/IConnectionStore.cs
new file mode 100644
index 000000000..1e4b63cb4
--- /dev/null
+++ b/src/modules/Elsa.Connections.Persistence/Contracts/IConnectionStore.cs
@@ -0,0 +1,42 @@
+using Elsa.Connections.Persistence.Filters;
+using Elsa.Connections.Persistence.Entities;
+
+namespace Elsa.Connections.Persistence.Contracts;
+
+public interface IConnectionStore
+{
+ ///
+ /// Get The Connection from the Store
+ ///
+ public Task GetAsync(string id, CancellationToken cancellationToken = default);
+
+ ///
+ /// Get All Connections from the store
+ ///
+ public Task> ListAsync(CancellationToken cancellationToken = default);
+
+ ///
+ /// Get All connection using Filter
+ ///
+ public Task FindAsync(ConnectionDefinitionFilter filter, CancellationToken cancellationToken = default);
+
+ ///
+ /// Get All connection using Filter
+ ///
+ public Task> FindManyAsync(ConnectionDefinitionFilter filter, CancellationToken cancellationToken = default);
+
+ ///
+ /// Add a new entity to the store
+ ///
+ public Task AddAsync(ConnectionDefinition entity, CancellationToken cancellationToken = default);
+
+ ///
+ /// Update the entity to the store
+ ///
+ public Task UpdateAsync(ConnectionDefinition entity, CancellationToken cancellationToken = default);
+
+ ///
+ /// Delete the entity from the store
+ ///
+ public Task DeleteAsync(ConnectionDefinition entity, CancellationToken cancellationToken = default);
+}
diff --git a/src/modules/Elsa.Connections.Persistence/Elsa.Connections.Persistence.csproj b/src/modules/Elsa.Connections.Persistence/Elsa.Connections.Persistence.csproj
new file mode 100644
index 000000000..cf7ec74b8
--- /dev/null
+++ b/src/modules/Elsa.Connections.Persistence/Elsa.Connections.Persistence.csproj
@@ -0,0 +1,13 @@
+
+
+
+ Provides Connections persistence services
+ elsa module connections persistence
+
+
+
+
+
+
+
+
diff --git a/src/modules/Elsa.Connections.Persistence/Entities/ConnectionDefinition.cs b/src/modules/Elsa.Connections.Persistence/Entities/ConnectionDefinition.cs
new file mode 100644
index 000000000..15a1e46ff
--- /dev/null
+++ b/src/modules/Elsa.Connections.Persistence/Entities/ConnectionDefinition.cs
@@ -0,0 +1,27 @@
+using System.Text.Json.Nodes;
+using Elsa.Common.Entities;
+
+namespace Elsa.Connections.Persistence.Entities;
+
+public class ConnectionDefinition : Entity
+{
+ ///
+ /// The Name of the Connection
+ ///
+ public string Name { get; set; } = null!;
+
+ ///
+ /// The Description of the Connection
+ ///
+ public string? Description { get; set; }
+
+ ///
+ /// The Configuration of the Connection in a Json Format
+ ///
+ public JsonObject ConnectionConfiguration { get; set; } = new();
+
+ ///
+ /// The Connection Type Name
+ ///
+ public string ConnectionType { get; set; } = null!;
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Connections.Persistence/Extensions/ConnectionPersistenceExtensions.cs b/src/modules/Elsa.Connections.Persistence/Extensions/ConnectionPersistenceExtensions.cs
new file mode 100644
index 000000000..af3eb822b
--- /dev/null
+++ b/src/modules/Elsa.Connections.Persistence/Extensions/ConnectionPersistenceExtensions.cs
@@ -0,0 +1,19 @@
+using Elsa.Connections.Persistence.Features;
+using Elsa.Features.Services;
+
+// ReSharper disable once CheckNamespace
+namespace Elsa.Extensions;
+
+///
+/// Extends with methods to use Connection Persistence Feature.
+///
+public static class ConnectionPersistenceExtensions
+{
+ ///
+ /// Installs the persistence feature for the Connection module.
+ ///
+ public static IModule UseConnectionPersistence(this IModule module, Action? configure = null)
+ {
+ return module.Use(configure);
+ }
+}
diff --git a/src/modules/Elsa.Connections.Persistence/Features/ConnectionPersistenceFeature.cs b/src/modules/Elsa.Connections.Persistence/Features/ConnectionPersistenceFeature.cs
new file mode 100644
index 000000000..b93658e06
--- /dev/null
+++ b/src/modules/Elsa.Connections.Persistence/Features/ConnectionPersistenceFeature.cs
@@ -0,0 +1,28 @@
+using Elsa.Extensions;
+using Elsa.Features.Abstractions;
+using Elsa.Features.Services;
+using Microsoft.Extensions.DependencyInjection;
+using Elsa.Connections.Persistence.Contracts;
+using Elsa.Connections.Persistence.Services;
+using Elsa.Connections.Persistence.Entities;
+
+namespace Elsa.Connections.Persistence.Features;
+
+public class ConnectionPersistenceFeature(IModule module) : FeatureBase(module)
+{
+ private Func _connectionStoreFactory = sp => sp.GetRequiredService();
+
+ public ConnectionPersistenceFeature UseConnectionStore(Func factory)
+ {
+ _connectionStoreFactory = factory;
+ return this;
+ }
+
+ public override void Apply()
+ {
+ Services.AddScoped(_connectionStoreFactory);
+
+ Services
+ .AddMemoryStore();
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Connections.Persistence/Filters/ConnectionDefinitionFilter.cs b/src/modules/Elsa.Connections.Persistence/Filters/ConnectionDefinitionFilter.cs
new file mode 100644
index 000000000..021a519cb
--- /dev/null
+++ b/src/modules/Elsa.Connections.Persistence/Filters/ConnectionDefinitionFilter.cs
@@ -0,0 +1,19 @@
+using Elsa.Connections.Persistence.Entities;
+
+namespace Elsa.Connections.Persistence.Filters;
+public class ConnectionDefinitionFilter
+{
+ public string? Id { get; set; }
+ public string? Name { get; set; }
+ public string? NotId { get; set; }
+ public string? Type { get; set; }
+
+ public IQueryable Apply(IQueryable queryable)
+ {
+ if(!string.IsNullOrEmpty(Id)) queryable = queryable.Where(x=>x.Id == Id);
+ if (!string.IsNullOrWhiteSpace(NotId)) queryable = queryable.Where(x => x.Id != NotId);
+ if (!string.IsNullOrWhiteSpace(Name)) queryable = queryable.Where(x=> x.Name == Name);
+ if(!string.IsNullOrWhiteSpace(Type)) queryable = queryable.Where(x=>x.ConnectionType == Type);
+ return queryable;
+ }
+}
diff --git a/src/modules/Elsa.Connections.Persistence/FodyWeavers.xml b/src/modules/Elsa.Connections.Persistence/FodyWeavers.xml
new file mode 100644
index 000000000..ac6b5b292
--- /dev/null
+++ b/src/modules/Elsa.Connections.Persistence/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/src/modules/Elsa.Connections.Persistence/Services/InMemoryConnectionStore.cs b/src/modules/Elsa.Connections.Persistence/Services/InMemoryConnectionStore.cs
new file mode 100644
index 000000000..3a9af9fa7
--- /dev/null
+++ b/src/modules/Elsa.Connections.Persistence/Services/InMemoryConnectionStore.cs
@@ -0,0 +1,52 @@
+using Elsa.Common.Services;
+using Elsa.Connections.Persistence.Contracts;
+using Elsa.Connections.Persistence.Filters;
+using Elsa.Connections.Persistence.Entities;
+using JetBrains.Annotations;
+
+namespace Elsa.Connections.Persistence.Services;
+
+[UsedImplicitly]
+public class InMemoryConnectionStore(MemoryStore memoryStore) : IConnectionStore
+{
+ public Task AddAsync(ConnectionDefinition model, CancellationToken cancellationToken = default)
+ {
+ memoryStore.Add(model, x => x.Id);
+ return Task.CompletedTask;
+ }
+
+ public Task DeleteAsync(ConnectionDefinition model, CancellationToken cancellationToken = default)
+ {
+ memoryStore.Delete(model.Id);
+ return Task.CompletedTask;
+ }
+
+ public Task GetAsync(string id, CancellationToken cancellationToken = default)
+ {
+ var result = memoryStore.Find(c => c.Id == id);
+ return Task.FromResult(result);
+ }
+
+ public Task> ListAsync(CancellationToken cancellationToken)
+ {
+ var results = memoryStore.List();
+ return Task.FromResult(results);
+ }
+ public Task FindAsync(ConnectionDefinitionFilter filter, CancellationToken cancellationToken = default)
+ {
+ var entity = memoryStore.Query(filter.Apply).FirstOrDefault();
+ return Task.FromResult(entity);
+ }
+
+ public Task> FindManyAsync(ConnectionDefinitionFilter filter, CancellationToken cancellationToken = default)
+ {
+ var entities = memoryStore.Query(filter.Apply);
+ return Task.FromResult(entities);
+ }
+
+ public Task UpdateAsync(ConnectionDefinition model, CancellationToken cancellationToken = default)
+ {
+ memoryStore.Update(model, x => x.Id);
+ return Task.CompletedTask;
+ }
+}
diff --git a/src/modules/Elsa.JavaScript/Handlers/ConfigureEngineWithVariablesAndInputOutputAccessors.cs b/src/modules/Elsa.JavaScript/Handlers/ConfigureEngineWithVariablesAndInputOutputAccessors.cs
index c64611074..14e407125 100644
--- a/src/modules/Elsa.JavaScript/Handlers/ConfigureEngineWithVariablesAndInputOutputAccessors.cs
+++ b/src/modules/Elsa.JavaScript/Handlers/ConfigureEngineWithVariablesAndInputOutputAccessors.cs
@@ -1,7 +1,6 @@
using Elsa.Expressions.Models;
using Elsa.Extensions;
using Elsa.JavaScript.Extensions;
-using Elsa.JavaScript.Helpers;
using Elsa.JavaScript.Notifications;
using Elsa.JavaScript.Options;
using Elsa.Mediator.Contracts;
diff --git a/src/modules/Elsa.Workflows.Core/Extensions/DictionaryExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/DictionaryExtensions.cs
index bc1bb6c44..33cb94f5f 100644
--- a/src/modules/Elsa.Workflows.Core/Extensions/DictionaryExtensions.cs
+++ b/src/modules/Elsa.Workflows.Core/Extensions/DictionaryExtensions.cs
@@ -57,7 +57,7 @@ public static class DictionaryExtensions
public static T? GetValueOrDefault(this IDictionary dictionary, string key, Func defaultValueFactory) => TryGetValue(dictionary, key, out var value) ? value : defaultValueFactory();
public static T? GetValueOrDefault(this IDictionary dictionary, IEnumerable keys, Func defaultValueFactory) => TryGetValue(dictionary, keys, out var value) ? value : defaultValueFactory();
public static T? GetValueOrDefault(this IDictionary dictionary, string key) => GetValueOrDefault(dictionary, key, () => default);
- public static object? GetValueOrDefault(this IDictionary dictionary, string key) => GetValueOrDefault