Public/Invoke-Connector.ps1

<#
.SYNOPSIS
Imports configured SuccessFactors OData resources into IAM Core Connector Space.

.DESCRIPTION
Validates the configured resource definitions, authenticates to SuccessFactors, retrieves every
configured resource, generates stable external IDs, and emits connector objects without applying
tenant-specific business rules or cross-resource filtering. Resource definitions may optionally
emit deterministic derived outputs or protect the primary PerPerson national ID before storage.

.PARAMETER ConnectorConfiguration
Connector configuration containing authentication settings and a resourcetypes JSON array.

.OUTPUTS
System.Collections.Hashtable. Source connector objects and any configured derived outputs.

.NOTES
The connector imports source rows losslessly unless the explicitly configured national-ID
protection profile removes sensitive source values. Derived outputs never replace source rows.
Data-quality and projection policy belong in source-system validation and IAM Core sync rules.
#>

function Invoke-Connector {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        $ConnectorConfiguration
    )

    $module = $ExecutionContext.SessionState.Module
    Write-ConnectorVerbose "Starting connector module '$($module.Name)' version '$($module.Version)'"

    $resourceTypes = @(ConvertFrom-SFResourceTypesConfiguration -ConnectorConfiguration $ConnectorConfiguration)
    Connect-SF -ConnectorConfiguration $ConnectorConfiguration
    Write-ConnectorVerbose "Connected successfully"

    $asOfDate = [DateTimeOffset]::UtcNow

    foreach ($resourceTypeConfiguration in $resourceTypes) {
        $resourceType = [string]$resourceTypeConfiguration.Type
        $queryString = Resolve-SFQueryString `
            -QueryString ([string]$resourceTypeConfiguration.QueryString) `
            -AsOfDate $asOfDate

        Write-ConnectorVerbose "Retrieving SuccessFactors resource type '$resourceType'"
        $resources = @(Invoke-SFRestMethod `
            -Resource $resourceType `
            -BaseUrl $ConnectorConfiguration.configuration.baseurl `
            -QueryString $queryString)

        # Validate every external ID before emitting this resource type. This prevents a
        # malformed or duplicate source key from being silently omitted from the snapshot.
        $externalIds = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::Ordinal)
        $preparedResources = @(
            foreach ($resource in $resources) {
                $externalId = Get-SFExternalId `
                    -Resource $resource `
                    -ResourceTypeConfiguration $resourceTypeConfiguration

                if (-not $externalIds.Add($externalId)) {
                    throw "SuccessFactors resource type '$resourceType' produced duplicate external ID '$externalId'."
                }

                $data = $resource |
                    ConvertTo-Json -Depth 20 |
                    ConvertFrom-Json -AsHashtable -Depth 20

                $nationalIdHashProfile = [string]$resourceTypeConfiguration.NationalIdHashProfile
                if (-not [string]::IsNullOrWhiteSpace($nationalIdHashProfile)) {
                    $data = Protect-SFNationalId -Data $data -HashProfile $nationalIdHashProfile
                }

                [PSCustomObject]@{
                    ExternalId = $externalId
                    Data       = $data
                }
            }
        )

        # Prepare and validate all derived outputs before emitting this source resource type.
        # The source rows remain available under their original object type.
        $preparedDerivedOutputs = @(
            $derivedOutputs = if ($null -eq $resourceTypeConfiguration.DerivedOutputs) { @() } else { @($resourceTypeConfiguration.DerivedOutputs) }
            foreach ($derivedOutput in $derivedOutputs) {
                # Derived outputs operate on protected representations so that an enabled
                # national-ID profile cannot reintroduce a raw value under another type.
                $protectedResources = @($preparedResources | ForEach-Object { $_.Data })
                $derivedResources = @(Invoke-SFDerivedOutput -Resources $protectedResources -DerivedOutput $derivedOutput)
                $derivedExternalIds = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::Ordinal)

                foreach ($derivedResource in $derivedResources) {
                    $derivedExternalId = Get-SFExternalId `
                        -Resource $derivedResource `
                        -ResourceTypeConfiguration $derivedOutput

                    if (-not $derivedExternalIds.Add($derivedExternalId)) {
                        throw "Derived resource type '$($derivedOutput.Type)' produced duplicate external ID '$derivedExternalId'."
                    }

                    [PSCustomObject]@{
                        ExternalId = $derivedExternalId
                        Data       = $derivedResource |
                            ConvertTo-Json -Depth 20 |
                            ConvertFrom-Json -AsHashtable -Depth 20
                        ObjectType = [string]$derivedOutput.Type
                    }
                }
            }
        )

        foreach ($preparedResource in $preparedResources) {
            Build-ConnectorObject `
                -ExternalId $preparedResource.ExternalId `
                -Data $preparedResource.Data `
                -ObjectType $resourceType
        }

        foreach ($preparedDerivedOutput in $preparedDerivedOutputs) {
            Build-ConnectorObject `
                -ExternalId $preparedDerivedOutput.ExternalId `
                -Data $preparedDerivedOutput.Data `
                -ObjectType $preparedDerivedOutput.ObjectType
        }

        Write-ConnectorVerbose "Processed $($preparedResources.Count) resources of type '$resourceType' and $($preparedDerivedOutputs.Count) derived resources"
    }
}