Public/Add-ExpressionCacheProvider.ps1
<#
.SYNOPSIS Registers a cache provider with ExpressionCache. .DESCRIPTION Add-ExpressionCacheProvider registers a provider specification and (if an Initialize function is supplied) eagerly initializes it. The provider spec may be a hashtable or PSCustomObject. It is validated and normalized by Test-ExpressionCacheProviderSpec. Configuration semantics: - One-time merge: if the spec contains InitializeArgs, those values are merged into Config at registration time (via Merge-ExpressionCacheConfig) and then InitializeArgs is removed. - No duplicate names: registration fails if a provider with the same Name already exists (comparison is case-insensitive). - Eager initialization: when the spec includes an Initialize function, it is invoked once using parameters built from Config (Build-SplatFromConfig). Required parameters are enforced (Assert-MandatoryParamsPresent). If Config has an 'Initialized' property, it is set to $true upon success. Expected spec shape (examples below): - Name or Key (string) : provider name (e.g., 'LocalFileSystemCache', 'Redis'). - Config (PSCustomObject) : provider configuration. - Initialize (string) : optional function name to prepare backing store. - GetOrCreate (string) : function name used to fetch/create cached values. - ClearCache (string) : optional function name to clear cache state. - InitializeArgs (hashtable, optional) : convenience values that will be merged into Config once. .PARAMETER Provider A provider specification (hashtable/PSCustomObject). Accepts pipeline input. May use 'Key' instead of 'Name'; it is normalized during validation. .INPUTS System.Object (provider spec via the pipeline) .OUTPUTS PSCustomObject (the normalized/registered provider spec) .EXAMPLE # Register LocalFileSystem provider with explicit functions and config Add-ExpressionCacheProvider @{ Key = 'LocalFileSystemCache' Initialize = 'Initialize-LocalFileSystem-Cache' GetOrCreate= 'Get-LocalFileSystem-CachedValue' ClearCache = 'Clear-LocalFileSystem-Cache' Config = @{ Prefix = 'ExpressionCache:v1:MyApp' CacheFolder = "$env:TEMP/ExpressionCache" } } .EXAMPLE # Register Redis; pass connection details via InitializeArgs (merged into Config once) Add-ExpressionCacheProvider @{ Key = 'Redis' Initialize = 'Initialize-Redis-Cache' GetOrCreate = 'Get-Redis-CachedValue' ClearCache = 'Clear-Redis-Cache' Config = @{ Prefix = 'ExpressionCache:v1:MyApp' Database = 2 } InitializeArgs = @{ Host = '127.0.0.1' Port = 6379 Password = $env:EXPRCACHE_REDIS_PASSWORD } } .EXAMPLE # Register multiple providers via the pipeline @( @{ Key='LocalFileSystemCache'; Config=@{ Prefix='ExpressionCache:v1:MyApp' } } @{ Key='Redis'; Config=@{ Prefix='ExpressionCache:v1:MyApp'; Database=2 }; Initialize='Initialize-Redis-Cache'; GetOrCreate='Get-Redis-CachedValue' } ) | Add-ExpressionCacheProvider .EXAMPLE # Preview registration/initialization without changing module state Add-ExpressionCacheProvider @{ Key='LocalFileSystemCache'; Config=@{ Prefix='ExpressionCache:v1:MyApp' } } -WhatIf .NOTES - Duplicate names are rejected: "A provider named 'X' is already registered." - When Initialize is present, missing mandatory parameters (as inferred from the command’s signature) will produce a validation error before initialization runs. - If your provider tracks an Initialized boolean in Config, it will be set to $true after a successful Initialize call. .LINK Initialize-ExpressionCache Get-ExpressionCacheProvider Get-ExpressionCache New-ExpressionCacheKey about_CommonParameters #> function Add-ExpressionCacheProvider { [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] param( [Parameter(Mandatory, ValueFromPipeline)] [object]$Provider ) process { if (-not $script:RegisteredStorageProviders) { $script:RegisteredStorageProviders = @() } # Validate/normalize spec (safe to do even under -WhatIf) $spec = Test-ExpressionCacheProviderSpec -Spec $Provider # One-time merge: InitializeArgs -> Config, then drop InitializeArgs if ($spec.PSObject.Properties.Name -contains 'InitializeArgs' -and $spec.InitializeArgs) { $spec.Config = Merge-ExpressionCacheConfig -Base $spec.Config -Overrides $spec.InitializeArgs $null = $spec.PSObject.Properties.Remove('InitializeArgs') } # Duplicate check (fail fast, even with -WhatIf) $existing = $script:RegisteredStorageProviders | Where-Object { $_.Name -eq $spec.Name } | Select-Object -First 1 if ($existing) { throw "ExpressionCache: A provider named '$($spec.Name)' is already registered." } $registered = $false $target = "Provider '$($spec.Name)'" # Register if ($PSCmdlet.ShouldProcess($target, 'Register')) { $script:RegisteredStorageProviders += , $spec $registered = $true } # Eager initialize (only if we actually registered above) if ($registered -and $spec.Initialize) { if ($PSCmdlet.ShouldProcess($target, 'Initialize')) { $paramSet = Build-SplatFromConfig -CommandName $spec.Initialize -Config $spec.Config Assert-MandatoryParamsPresent -CommandName $spec.Initialize -Splat $paramSet & $spec.Initialize @paramSet if ($spec.Config.PSObject.Properties.Name -contains 'Initialized') { $spec.Config.Initialized = $true } } } return $spec } } |