synapse-cc-0.1.0.0: src/SynapseCC/Types.hs
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
-- | Core types for synapse-cc
module SynapseCC.Types
( -- * Version Information
synapseCCVersion
-- * Configuration
, Config(..)
, Target(..)
, Backend(..)
, Options(..)
, defaultOptions
-- * Tool Locations
, ToolLocations(..)
, ToolPath(..)
-- * Pipeline Results
, IRPath(..)
, GeneratedPath(..)
, CompiledPath(..)
-- * Cache Types
, ToolchainVersions(..)
, IRPluginCache(..)
, CodePluginCache(..)
, IRCacheManifest(..)
, CodeCacheManifest(..)
, CacheResult(..)
, CacheMissReason(..)
-- * Metadata Types
, CodegenMetadata(..)
, CacheInfo(..)
-- * Errors
, SynapseCCError(..)
, formatError
) where
import Data.Aeson (FromJSON, ToJSON, fieldLabelModifier)
import qualified Data.Aeson as Aeson
import Data.Map.Strict (Map)
import Data.Text (Text)
import qualified Data.Text as T
import GHC.Generics (Generic)
import System.FilePath (FilePath)
-- ============================================================================
-- Version Information
-- ============================================================================
-- | synapse-cc version (from cabal file: synapse-cc.cabal)
synapseCCVersion :: Text
synapseCCVersion = "0.1.0.0"
-- ============================================================================
-- Configuration
-- ============================================================================
-- | Main configuration for synapse-cc
data Config = Config
{ cfgTarget :: !Target
, cfgBackend :: !Backend
, cfgHost :: !Text
, cfgPort :: !Text
, cfgOptions :: !Options
} deriving stock (Show, Eq, Generic)
-- | Target language for code generation
data Target
= TypeScript
| Python
| Rust
deriving stock (Show, Eq, Ord, Generic)
deriving anyclass (FromJSON, ToJSON)
-- | Backend identifier
data Backend = Backend
{ backendName :: !Text -- ^ Backend name (e.g., "substrate", "plexus")
} deriving stock (Show, Eq, Generic)
deriving anyclass (FromJSON, ToJSON)
-- | Options for code generation and compilation
data Options = Options
{ optOutput :: !FilePath
, optBundleTransport :: !Bool
, optInstallDeps :: !Bool
, optBuild :: !Bool
, optRunTests :: !Bool
, optCacheDir :: !FilePath
, optForce :: !Bool
, optWatch :: !Bool
, optDebug :: !Bool
} deriving stock (Show, Eq, Generic)
-- | Default options
defaultOptions :: Options
defaultOptions = Options
{ optOutput = "./generated"
, optBundleTransport = True
, optInstallDeps = True
, optBuild = True
, optRunTests = False
, optCacheDir = "~/.cache/plexus-codegen"
, optForce = False
, optWatch = False
, optDebug = False
}
-- ============================================================================
-- Tool Locations
-- ============================================================================
-- | Discovered locations of required tools
data ToolLocations = ToolLocations
{ toolSynapse :: !ToolPath
, toolHubCodegen :: !ToolPath
} deriving stock (Show, Eq)
-- | Path to a discovered tool
data ToolPath
= LocalDev !FilePath -- ^ Local development build
| SystemPath !FilePath -- ^ Found in $PATH
| PlexusBin !FilePath -- ^ Installed in ~/.plexus/bin
deriving stock (Show, Eq)
-- ============================================================================
-- Pipeline Results
-- ============================================================================
-- | Path to generated IR JSON file
newtype IRPath = IRPath { unIRPath :: FilePath }
deriving stock (Show, Eq)
-- | Path to generated code directory
newtype GeneratedPath = GeneratedPath { unGeneratedPath :: FilePath }
deriving stock (Show, Eq)
-- | Path to compiled output
newtype CompiledPath = CompiledPath { unCompiledPath :: FilePath }
deriving stock (Show, Eq)
-- ============================================================================
-- Cache Types
-- ============================================================================
-- | Toolchain version information for cache invalidation
data ToolchainVersions = ToolchainVersions
{ tvSynapseCC :: !Text
, tvSynapse :: !Text
, tvHubCodegen :: !(Maybe Text) -- Only known after codegen
} deriving stock (Show, Eq, Generic)
deriving anyclass (FromJSON, ToJSON)
-- | Cache entry for a single plugin's IR
data IRPluginCache = IRPluginCache
{ ipcIRHash :: !Text -- Hash of the generated IR for this plugin
, ipcSchemaHash :: !Text -- Hash of the source schema (composite, backward compatible)
, ipcSelfHash :: !Text -- V2: Methods-only hash (for granular invalidation)
, ipcChildrenHash :: !Text -- V2: Children-only hash (for granular invalidation)
, ipcDependencies :: ![Text] -- List of plugin dependencies
, ipcCachedAt :: !Text -- ISO 8601 timestamp
} deriving stock (Show, Eq, Generic)
deriving anyclass (FromJSON, ToJSON)
-- | Cache entry for a single plugin's generated code
data CodePluginCache = CodePluginCache
{ cpcIRHash :: !Text -- Hash of the IR that generated this code
, cpcFileHashes :: !(Map Text Text) -- Per-file content hashes (path -> hash)
, cpcCachedAt :: !Text -- ISO 8601 timestamp
} deriving stock (Show, Eq, Generic)
deriving anyclass (FromJSON, ToJSON)
-- | Cache manifest for IR (synapse/ir/manifest.json)
data IRCacheManifest = IRCacheManifest
{ ircmVersion :: !Text
, ircmIRVersion :: !Text
, ircmToolchain :: !ToolchainVersions
, ircmUpdatedAt :: !Text
, ircmPlugins :: !(Map Text IRPluginCache)
} deriving stock (Show, Eq, Generic)
deriving anyclass (FromJSON, ToJSON)
-- | Cache manifest for generated code (hub-codegen/typescript/manifest.json)
data CodeCacheManifest = CodeCacheManifest
{ ccmVersion :: !Text
, ccmTarget :: !Text
, ccmToolchain :: !ToolchainVersions
, ccmUpdatedAt :: !Text
, ccmPlugins :: !(Map Text CodePluginCache)
} deriving stock (Show, Eq, Generic)
deriving anyclass (FromJSON, ToJSON)
-- | Reason for cache miss
data CacheMissReason
= ToolVersionChanged -- Tool versions don't match
| SchemaHashChanged -- Source schema changed
| IRHashChanged -- IR changed
| DependencyInvalidated -- Transitive dependency changed
| ManifestNotFound -- No cache manifest exists
| ManifestCorrupted -- Manifest exists but is invalid
deriving stock (Show, Eq)
-- | Result of cache validation
data CacheResult
= FullCacheHit
-- ^ All plugins are cached and valid
| PartialCacheHit ![Text] ![Text]
-- ^ Some plugins valid (first list), some invalid (second list)
| CacheMiss !CacheMissReason
-- ^ Cache invalid, must regenerate everything
deriving stock (Show, Eq)
-- ============================================================================
-- Metadata Types (for parsing .codegen-metadata.json)
-- ============================================================================
-- | Cache information from .codegen-metadata.json
data CacheInfo = CacheInfo
{ ciFileHashes :: !(Map Text Text) -- file path -> hash
} deriving stock (Show, Eq, Generic)
instance FromJSON CacheInfo where
parseJSON = Aeson.genericParseJSON Aeson.defaultOptions
{ fieldLabelModifier = \case
"ciFileHashes" -> "file_hashes"
other -> other
}
instance ToJSON CacheInfo where
toJSON = Aeson.genericToJSON Aeson.defaultOptions
{ fieldLabelModifier = \case
"ciFileHashes" -> "file_hashes"
other -> other
}
-- | Metadata file generated by hub-codegen
data CodegenMetadata = CodegenMetadata
{ cmFormatVersion :: !Text
, cmCache :: !CacheInfo
} deriving stock (Show, Eq, Generic)
instance FromJSON CodegenMetadata where
parseJSON = Aeson.genericParseJSON Aeson.defaultOptions
{ fieldLabelModifier = \case
"cmFormatVersion" -> "format_version"
"cmCache" -> "cache"
other -> other
}
instance ToJSON CodegenMetadata where
toJSON = Aeson.genericToJSON Aeson.defaultOptions
{ fieldLabelModifier = \case
"cmFormatVersion" -> "format_version"
"cmCache" -> "cache"
other -> other
}
-- ============================================================================
-- Errors
-- ============================================================================
-- | All possible errors in synapse-cc
data SynapseCCError
= ToolNotFound !Text ![Text]
-- ^ Tool not found with suggestions
| SynapseError !Text !Int
-- ^ Synapse execution failed with stderr and exit code
| HubCodegenError !Text !Int
-- ^ hub-codegen execution failed with stderr and exit code
| LanguageToolError !Text !Text !Int
-- ^ Language tool (npm, tsc, etc.) failed
| CacheError !Text
-- ^ Cache operation failed
| InvalidIR !Text
-- ^ IR parsing failed
| BackendUnreachable !Text !Text
-- ^ Cannot connect to backend
| ConfigError !Text
-- ^ Invalid configuration
deriving stock (Show, Eq)
-- | Format error for display to user
formatError :: SynapseCCError -> Text
formatError = \case
ToolNotFound tool suggestions ->
T.unlines $
[ "[!] Error: " <> tool <> " not found"
, ""
, tool <> " is required by synapse-cc."
, ""
, "Try:"
] ++ map (" - " <>) suggestions ++
[ ""
, "For more info: https://github.com/hypermemetic/synapse-cc"
]
SynapseError stderr exitCode ->
T.unlines
[ "[!] Error: synapse failed (exit code " <> T.pack (show exitCode) <> ")"
, ""
, "Output:"
, stderr
]
HubCodegenError stderr exitCode ->
T.unlines
[ "[!] Error: hub-codegen failed (exit code " <> T.pack (show exitCode) <> ")"
, ""
, "Output:"
, stderr
]
LanguageToolError tool stderr exitCode ->
T.unlines
[ "[!] Error: " <> tool <> " failed (exit code " <> T.pack (show exitCode) <> ")"
, ""
, "Output:"
, stderr
]
CacheError msg ->
"[!] Cache error: " <> msg
InvalidIR msg ->
T.unlines
[ "[!] Error: Invalid IR"
, ""
, msg
]
BackendUnreachable url msg ->
T.unlines
[ "[!] Error: Cannot connect to backend"
, ""
, "URL: " <> url
, "Reason: " <> msg
, ""
, "Is the backend running?"
]
ConfigError msg ->
"[!] Configuration error: " <> msg