Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/Process-PSModule.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ permissions:

jobs:
Process-PSModule:
uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@205d193f34cbbaf9992955c21d842bcf98a1859f # v5.4.6
secrets: inherit
uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@fb1bdb8fefd243292f779d2a856a38db6fe6daf4 # v6.1.13
secrets:
APIKey: ${{ secrets.APIKey }}
69 changes: 69 additions & 0 deletions .github/zensical.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
[project]
site_name = "Context"
repo_name = "PSModule/Context"
repo_url = "https://github.com/PSModule/Context"

[project.theme]
variant = "classic"
language = "en"
logo = "Assets/icon.png"
favicon = "Assets/icon.png"
features = [
"navigation.instant",
"navigation.instant.progress",
"navigation.indexes",
"navigation.top",
"navigation.tracking",
"navigation.expand",
"search.suggest",
"search.highlight",
"content.code.copy"
]

[[project.theme.palette]]
media = "(prefers-color-scheme)"
toggle.icon = "lucide/sun-moon"
toggle.name = "Switch to dark mode"

[[project.theme.palette]]
media = "(prefers-color-scheme: dark)"
scheme = "slate"
primary = "black"
accent = "green"
toggle.icon = "lucide/moon"
toggle.name = "Switch to light mode"

[[project.theme.palette]]
media = "(prefers-color-scheme: light)"
scheme = "default"
primary = "indigo"
accent = "green"
toggle.icon = "lucide/sun"
toggle.name = "Switch to system preference"

[project.theme.icon]
repo = "fontawesome/brands/github"

[project.markdown_extensions.toc]
permalink = true

[project.markdown_extensions.attr_list]
[project.markdown_extensions.admonition]
[project.markdown_extensions.md_in_html]
[project.markdown_extensions.pymdownx.details]
[project.markdown_extensions.pymdownx.superfences]

[[project.extra.social]]
icon = "fontawesome/brands/discord"
link = "https://discord.gg/jedJWCPAhD"
name = "PSModule on Discord"

[[project.extra.social]]
icon = "fontawesome/brands/github"
link = "https://github.com/PSModule/"
name = "PSModule on GitHub"

[project.extra.consent]
title = "Cookie consent"
description = "We use cookies to recognize your repeated visits and preferences, as well as to measure the effectiveness of our documentation and whether users find what they're searching for. With your consent, you're helping us to make our documentation better."
actions = ["accept", "reject"]
59 changes: 59 additions & 0 deletions src/functions/private/Assert-ContextSodiumModule.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
function Assert-ContextSodiumModule {
<#
.SYNOPSIS
Ensures the required Sodium module version is available for context cryptography.

.DESCRIPTION
Validates that Sodium v2.2.4 or newer is loaded in the current session.
Imports Sodium v2.2.4 if needed, and verifies required commands exist.

.EXAMPLE
Assert-ContextSodiumModule

Ensures Sodium cryptography commands are available before encryption or decryption.
#>
[CmdletBinding()]
param()

begin {
$stackPath = Get-PSCallStackPath
Write-Debug "[$stackPath] - Begin"
}

process {
if ($script:ContextSodiumModuleReady) {
return
}

$minimumVersion = [version]'2.2.4'
$loadedSodium = Get-Module -Name Sodium | Sort-Object Version -Descending | Select-Object -First 1

if ($loadedSodium -and $loadedSodium.Version -lt $minimumVersion) {
$message = "Loaded Sodium version [$($loadedSodium.Version)] is older than required version [$minimumVersion]. "
$message += "Start a new PowerShell session and import Sodium $minimumVersion."
throw $message
}

if (-not $loadedSodium) {
Import-Module -Name Sodium -RequiredVersion $minimumVersion -ErrorAction Stop
}

$requiredCommands = @(
'New-SodiumKeyPair',
'ConvertTo-SodiumSealedBox',
'ConvertFrom-SodiumSealedBox'
)

foreach ($commandName in $requiredCommands) {
if (-not (Get-Command -Name $commandName -ErrorAction SilentlyContinue)) {
throw "Required Sodium command '$commandName' is not available after loading the module."
}
}

$script:ContextSodiumModuleReady = $true
}

end {
Write-Debug "[$stackPath] - End"
}
}
208 changes: 208 additions & 0 deletions src/functions/private/ContextFileIndexCache.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
function Get-ContextFileIndex {
<#
.SYNOPSIS
Gets a cached context file index for a vault.

.DESCRIPTION
Builds (or returns) an in-memory index of context IDs to metadata file paths for a vault.
This avoids repeated full vault scans for exact-ID lookups in hot paths.

.EXAMPLE
Get-ContextFileIndex -Vault 'MyVault' -VaultPath 'C:\Users\Jane\.contextvaults\MyVault'

Returns a dictionary where keys are context IDs and values are metadata file paths.

.OUTPUTS
[System.Collections.Generic.Dictionary[string,string]]
#>
[OutputType([System.Collections.Generic.Dictionary[string, string]])]
[CmdletBinding()]
param(
# The vault name.
[Parameter(Mandatory)]
[string] $Vault,

# The full path to the vault folder.
[Parameter(Mandatory)]
[string] $VaultPath,

# Rebuilds the index from disk even if cached.
[Parameter()]
[switch] $Refresh
)

begin {
$stackPath = Get-PSCallStackPath
Write-Debug "[$stackPath] - Begin"
if ($null -eq $script:ContextFileIndexCache) {
$script:ContextFileIndexCache = @{}
}
}

process {
if (-not $Refresh -and $script:ContextFileIndexCache.ContainsKey($Vault)) {
return $script:ContextFileIndexCache[$Vault]
}

$index = [System.Collections.Generic.Dictionary[string, string]]::new([System.StringComparer]::OrdinalIgnoreCase)
$files = Get-ChildItem -Path $VaultPath -Filter *.json -File -ErrorAction SilentlyContinue

foreach ($file in $files) {
try {
$contextInfo = Get-ContextInfoFromFile -Path $file.FullName -Vault $Vault -ErrorAction Stop
$index[$contextInfo.ID] = $contextInfo.Path
} catch {
Write-Warning "[$stackPath] - Failed to index context file '$($file.FullName)': $($_.Exception.Message)"
}
}

$script:ContextFileIndexCache[$Vault] = $index
return $index
}

end {
Write-Debug "[$stackPath] - End"
}
}

function Set-ContextFileIndexEntry {
<#
.SYNOPSIS
Adds or updates a context entry in the in-memory vault index.

.DESCRIPTION
Stores or updates an ID-to-file-path mapping in the in-memory cache for a vault.

.EXAMPLE
Set-ContextFileIndexEntry -Vault 'MyVault' -ID 'MyContext' -Path 'C:\vault\abc.json'

Updates the in-memory index for the given vault.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseShouldProcessForStateChangingFunctions', '',
Justification = 'This private helper only mutates an in-memory cache.'
)]
[CmdletBinding()]
param(
# The vault name.
[Parameter(Mandatory)]
[string] $Vault,

# The context ID.
[Parameter(Mandatory)]
[string] $ID,

# The metadata file path.
[Parameter(Mandatory)]
[string] $Path
)

begin {
$stackPath = Get-PSCallStackPath
Write-Debug "[$stackPath] - Begin"
if ($null -eq $script:ContextFileIndexCache) {
$script:ContextFileIndexCache = @{}
}
}

process {
if (-not $script:ContextFileIndexCache.ContainsKey($Vault)) {
$vaultIndex = [System.Collections.Generic.Dictionary[string, string]]::new(
[System.StringComparer]::OrdinalIgnoreCase
)
$script:ContextFileIndexCache[$Vault] = $vaultIndex
}

$script:ContextFileIndexCache[$Vault][$ID] = $Path
}

end {
Write-Debug "[$stackPath] - End"
}
}

function Remove-ContextFileIndexEntry {
<#
.SYNOPSIS
Removes a context entry from the in-memory vault index.

.DESCRIPTION
Removes an ID mapping from the in-memory index for a vault.

.EXAMPLE
Remove-ContextFileIndexEntry -Vault 'MyVault' -ID 'MyContext'

Removes the mapping for the specified context ID.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseShouldProcessForStateChangingFunctions', '',
Justification = 'This private helper only mutates an in-memory cache.'
)]
[CmdletBinding()]
param(
# The vault name.
[Parameter(Mandatory)]
[string] $Vault,

# The context ID.
[Parameter(Mandatory)]
[string] $ID
)

begin {
$stackPath = Get-PSCallStackPath
Write-Debug "[$stackPath] - Begin"
}

process {
if ($null -eq $script:ContextFileIndexCache -or -not $script:ContextFileIndexCache.ContainsKey($Vault)) {
return
}

$null = $script:ContextFileIndexCache[$Vault].Remove($ID)
}

end {
Write-Debug "[$stackPath] - End"
}
}

function Clear-ContextFileIndex {
<#
.SYNOPSIS
Clears in-memory vault index entries.

.DESCRIPTION
Removes one or more vault entries from the in-memory context file index cache.

.EXAMPLE
Clear-ContextFileIndex -Vault 'MyVault'

Clears the in-memory index for 'MyVault'.
#>
[CmdletBinding()]
param(
# One or more vault names to clear from cache.
[Parameter(Mandatory)]
[string[]] $Vault
)

begin {
$stackPath = Get-PSCallStackPath
Write-Debug "[$stackPath] - Begin"
}

process {
if ($null -eq $script:ContextFileIndexCache) {
return
}

foreach ($vaultName in $Vault) {
$null = $script:ContextFileIndexCache.Remove($vaultName)
}
}

end {
Write-Debug "[$stackPath] - End"
}
}
Loading
Loading