Header menu logo Migrondi

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 Uri object must contain the trailing slash from the rootDir and migrationsDir objects. 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.

namespace System
namespace System.Threading
namespace System.Threading.Tasks
namespace Microsoft
namespace Microsoft.Extensions
namespace Microsoft.Extensions.Logging
namespace System.Runtime
namespace System.Runtime.InteropServices
val logger: ILogger
Multiple items
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
LoggerFactory.Create(configure: Action<ILoggingBuilder>) : ILoggerFactory
val builder: ILoggingBuilder
(extension) ILoggingBuilder.AddConsole() : ILoggingBuilder
(extension) ILoggingBuilder.AddConsole(configure: Action<Console.ConsoleLoggerOptions>) : ILoggingBuilder
val ignore: value: 'T -> unit
val x: ILoggerFactory
(extension) ILoggerFactory.CreateLogger<'T>() : ILogger<'T>
(extension) ILoggerFactory.CreateLogger(``type`` : Type) : ILogger
ILoggerFactory.CreateLogger(categoryName: string) : ILogger
val rootDir: Uri
Multiple items
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
[<Struct>] type UriKind = | RelativeOrAbsolute = 0 | Absolute = 1 | Relative = 2
<summary>Defines the different kinds of URIs.</summary>
field UriKind.Absolute: UriKind = 1
val migrationsDir: Uri
field UriKind.Relative: UriKind = 2
val createCustomSource: logger: ILogger -> 'a
Multiple items
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(message: string, [<ParamArray>] args: obj array) : unit
(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
Multiple items
type OptionalAttribute = inherit Attribute new: unit -> unit
<summary>Indicates that a parameter is optional.</summary>

--------------------
OptionalAttribute() : OptionalAttribute
Multiple items
[<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 task: TaskBuilder
Multiple items
val string: value: 'T -> string

--------------------
type string = String
module Seq from Microsoft.FSharp.Collections
val empty<'T> : 'T seq
val config: obj
val mySource: obj
val migrondi: obj

Type something to start searching.