packages feed

servant-typescript-0.1.0.0: src/Servant/TypeScript/Types.hs

module Servant.TypeScript.Types where

import Control.Lens
import Data.Aeson.TypeScript.Recursive
import Data.Aeson.TypeScript.TH
import Data.String.Interpolate
import qualified Data.Text as T
import Servant.Foreign.Internal as FI
import qualified Servant.TypeScript.GetFunctions as GetFunctions
import Servant.TypeScript.Util


-- | Foreign type for getting TS types
data LangTS
instance (TypeScript a) => HasForeignType LangTS T.Text a where
  typeFor _proxyLang _proxyFtype proxyA = T.pack $ getTypeScriptType proxyA

-- | Foreign type for getting TS declarations
data LangTSDecls
instance (TypeScript a) => HasForeignType LangTSDecls [TSDeclaration] a where
  typeFor _proxyLang _proxyFtype proxyA = getTypeScriptDeclarationsRecursively proxyA

data ServantTypeScriptOptions = ServantTypeScriptOptions {
  -- | Extra TypeScript types to include in the @d.ts@ file.
  --
  -- Useful if you want to expose types that don't appear in your API, for whatever reason.
  extraTypes :: [TSType]

  -- | Determine to which output file the client function for the given request is mapped.
  --
  -- Useful to break up larger APIs into separate files based on criteria like route prefixes.
  --
  -- It's fine if the file key contains sub-directories; they will be created as needed.
  --
  -- A good approach is to split on @case req ^. (reqFuncName . _FunctionName) of ...@.
  --
  -- Default implementation is @const "client.ts"@.
  , getFileKey :: Req T.Text -> FilePath

  -- | Mangle a given request into a corresponding client function name.
  -- By default, just prepends the HTTP method to the camel-cased route.
  , getFunctionName :: Req T.Text -> T.Text

  -- | Given a list of requests, output a complete TypeScript module with the (exported) client functions,
  -- ready to be consumed by your TypeScript app.
  --
  -- For example, you can import dependencies at the top
  -- to use in your functions. The default version relies on the NPM "query-string" package to construct
  -- URLs. It uses the built-in @window.fetch@ by default, but allows you to pass your own @fetch@ function
  -- instead (useful for server-side rendering etc.). The default client functions return @Promise@s with
  -- the given return value, and on failure they reject the promise with a value of interface
  -- @{ status: number; text: string;  }@.
  --
  -- If you want to write your own 'getFunctions', check out the 'Servant.TypeScript.GetFunctions' module for
  -- inspiration.
  --
  -- The first argument passed to 'getFunctions' is the 'getFunctionName' function.
  , getFunctions :: (Req T.Text -> T.Text) -> [Req T.Text] -> T.Text
  }

-- | Reasonable default options.
defaultServantTypeScriptOptions :: ServantTypeScriptOptions
defaultServantTypeScriptOptions = ServantTypeScriptOptions {
  extraTypes = []

  , getFileKey = const "client.ts"

  , getFunctionName = \req -> case req ^. (reqFuncName . _FunctionName) of
      (method:xs) -> toCamelList $ fmap snakeToCamel (method:xs)
      _ -> error [i|Case not handled in getFunctionName: '#{req}'|]

  , getFunctions = GetFunctions.getFunctions
  }