The IMiMigrationSource interface is the primary extension point for implementing custom storage mechanisms. It allows you to provide raw migration and configuration content from any source (HTTP, S3, In-Memory, etc.), while the library handles all internal logic for parsing and managing migrations.
#r "../../src/Migrondi.Core/bin/Debug/net8.0/Migrondi.Core.dll"
#r "nuget: Microsoft.Extensions.Logging.Console, 9.0.0"
open System
open System.Threading
open System.Threading.Tasks
open Microsoft.Extensions.Logging
open System.Runtime.InteropServices
open Migrondi.Core
open Migrondi.Core.FileSystem
let logger =
// create a sample logger, you can provide your own
LoggerFactory.Create(fun builder -> builder.AddConsole() |> ignore)
|> fun x -> x.CreateLogger("FileSystem")
// Example URIs for documentation purposes
let rootDir = Uri("https://example.com/project/", UriKind.Absolute)
let migrationsDir = Uri("migrations/", UriKind.Relative)
NOTE: Please keep in mind that URI objects treat trailing slashes differently, since we're using them to represent directories. The
Uriobject must contain the trailing slash from therootDirandmigrationsDirobjects. If you fail to ensure the trailing slash is there, the library will not work as expected.
URI-based Path Handling
Migrondi uses URIs internally to represent both local and remote paths. This abstraction allows you to implement sources that work with: - Local file system (using file:// URIs) - Cloud storage (using http:// or https:// URIs) - Virtual file systems - Databases as file storage
The migration source is used by the internal file system service. You can provide your own implementation via the source parameter in MigrondiFactory.
Custom Migration Sources
To implement a custom storage backend, you implement the IMiMigrationSource interface. This interface deals strictly with raw strings and URIs, ensuring you don't have to worry about the internal migration format or serialization.
Using an object expression is the recommended way to implement this interface:
let createCustomSource (logger: ILogger) =
{ new IMiMigrationSource with
member _.ReadContent(uri: Uri) =
logger.LogDebug("Reading content from {Path}", uri.ToString())
// Implement your sync read logic here
"SQL CONTENT"
member _.ReadContentAsync
(uri: Uri, [<Optional>] ?cancellationToken: CancellationToken)
=
task {
logger.LogDebug(
"Reading content asynchronously from {Path}",
uri.ToString()
)
// Implement your async read logic here
return "SQL CONTENT"
}
member _.WriteContent(uri: Uri, content: string) =
logger.LogDebug("Writing content to {Path}", uri.ToString())
// Implement your sync write logic here
()
member _.WriteContentAsync
(
uri: Uri,
content: string,
[<Optional>] ?cancellationToken: CancellationToken
) =
task {
logger.LogDebug(
"Writing content asynchronously to {Path}",
uri.ToString()
)
// Implement your async write logic here
()
}
member _.ListFiles(locationUri: Uri) =
logger.LogDebug("Listing files in {Path}", locationUri.ToString())
// Return a sequence of URIs for migrations at this location
Seq.empty
member _.ListFilesAsync
(locationUri: Uri, [<Optional>] ?cancellationToken: CancellationToken)
=
task {
logger.LogDebug(
"Listing files asynchronously in {Path}",
locationUri.ToString()
)
// Return URIs asynchronously
return Seq.empty
}
}
Usage with MigrondiFactory
Once you have your custom source, you can pass it to the factory. Migrondi will wrap it in its internal file system service, providing all the listing, filtering, and parsing logic automatically.
let config = MigrondiConfig.Default
let mySource = createCustomSource logger
let migrondi = Migrondi.MigrondiFactory(
config,
rootDirectory = ".",
source = mySource
)
Benefits of the Source Abstraction
By using IMiMigrationSource instead of implementing the full file system:
1. Simplified API: You only handle raw string transfers.
2. Encapsulated Serialization: You don't need to know how Migrondi encodes migrations (Regex, JSON, etc.).
3. Internal Consistency: The library ensures that migrations are listed, filtered, and ordered correctly regardless of where they are stored.
type LoggerFactory = interface ILoggerFactory interface IDisposable new: unit -> unit + 5 overloads member AddProvider: provider: ILoggerProvider -> unit member CreateLogger: categoryName: string -> ILogger member Dispose: unit -> unit static member Create: configure: Action<ILoggingBuilder> -> ILoggerFactory
<summary> Produces instances of <see cref="T:Microsoft.Extensions.Logging.ILogger" /> classes based on the given providers. </summary>
--------------------
LoggerFactory() : LoggerFactory
LoggerFactory(providers: Collections.Generic.IEnumerable<ILoggerProvider>) : LoggerFactory
LoggerFactory(providers: Collections.Generic.IEnumerable<ILoggerProvider>, filterOptions: LoggerFilterOptions) : LoggerFactory
LoggerFactory(providers: Collections.Generic.IEnumerable<ILoggerProvider>, filterOption: Extensions.Options.IOptionsMonitor<LoggerFilterOptions>) : LoggerFactory
LoggerFactory(providers: Collections.Generic.IEnumerable<ILoggerProvider>, filterOption: Extensions.Options.IOptionsMonitor<LoggerFilterOptions>, options: Extensions.Options.IOptions<LoggerFactoryOptions>) : LoggerFactory
LoggerFactory(providers: Collections.Generic.IEnumerable<ILoggerProvider>, filterOption: Extensions.Options.IOptionsMonitor<LoggerFilterOptions>, ?options: Extensions.Options.IOptions<LoggerFactoryOptions>, ?scopeProvider: IExternalScopeProvider) : LoggerFactory
(extension) ILoggingBuilder.AddConsole(configure: Action<Console.ConsoleLoggerOptions>) : ILoggingBuilder
(extension) ILoggerFactory.CreateLogger(``type`` : Type) : ILogger
ILoggerFactory.CreateLogger(categoryName: string) : ILogger
type Uri = interface IEquatable<Uri> interface IFormattable interface ISpanFormattable interface ISerializable new: uriString: string -> unit + 6 overloads member Equals: comparand: obj -> bool + 1 overload member GetComponents: components: UriComponents * format: UriFormat -> string member GetHashCode: unit -> int member GetLeftPart: part: UriPartial -> string member IsBaseOf: uri: Uri -> bool ...
<summary>Provides an object representation of a uniform resource identifier (URI) and easy access to the parts of the URI.</summary>
--------------------
Uri(uriString: string) : Uri
Uri(uriString: string, creationOptions: inref<UriCreationOptions>) : Uri
Uri(uriString: string, uriKind: UriKind) : Uri
Uri(baseUri: Uri, relativeUri: string) : Uri
Uri(baseUri: Uri, relativeUri: Uri) : Uri
<summary>Defines the different kinds of URIs.</summary>
type ILogger = override BeginScope<'TState> : state: 'TState -> IDisposable override IsEnabled: logLevel: LogLevel -> bool override Log<'TState> : logLevel: LogLevel * eventId: EventId * state: 'TState * ``exception`` : exn * formatter: Func<'TState,exn,string> -> unit
<summary> Represents a type used to perform logging. </summary>
<remarks>Aggregates most logging patterns to a single method.</remarks>
--------------------
type ILogger<'TCategoryName> = inherit ILogger
<summary> A generic interface for logging where the category name is derived from the specified <typeparamref name="TCategoryName" /> type name. Generally used to enable activation of a named <see cref="T:Microsoft.Extensions.Logging.ILogger" /> from dependency injection. </summary>
<typeparam name="TCategoryName">The type whose name is used for the logger category name.</typeparam>
(extension) ILogger.LogDebug(eventId: EventId, message: string, [<ParamArray>] args: obj array) : unit
(extension) ILogger.LogDebug(``exception`` : exn, message: string, [<ParamArray>] args: obj array) : unit
(extension) ILogger.LogDebug(eventId: EventId, ``exception`` : exn, message: string, [<ParamArray>] args: obj array) : unit
type OptionalAttribute = inherit Attribute new: unit -> unit
<summary>Indicates that a parameter is optional.</summary>
--------------------
OptionalAttribute() : OptionalAttribute
[<Struct>] type CancellationToken = new: canceled: bool -> unit member Equals: other: obj -> bool + 1 overload member GetHashCode: unit -> int member Register: callback: Action -> CancellationTokenRegistration + 4 overloads member ThrowIfCancellationRequested: unit -> unit member UnsafeRegister: callback: Action<obj,CancellationToken> * state: obj -> CancellationTokenRegistration + 1 overload static member (<>) : left: CancellationToken * right: CancellationToken -> bool static member (=) : left: CancellationToken * right: CancellationToken -> bool member CanBeCanceled: bool member IsCancellationRequested: bool ...
<summary>Propagates notification that operations should be canceled.</summary>
--------------------
CancellationToken ()
CancellationToken(canceled: bool) : CancellationToken
val string: value: 'T -> string
--------------------
type string = String
Migrondi