# notion-client-effectful
An [effectful][effectful] surface for
[`notion-client`](https://hackage.haskell.org/package/notion-client).
[effectful]: https://hackage.haskell.org/package/effectful
This package wraps the `Notion.V1.Methods` record as a dynamic
effect — so code written against `effectful` does not have to
thread the `Methods` value through every call site and does not
have to drop into `IO` at each API boundary.
## Purpose
`notion-client` exposes a single `Methods` record whose fields
are `IO`-typed API calls. That is ergonomic for a plain-`IO`
program but awkward for an `effectful`-based caller: every call
requires `liftIO` and escapes the ambient `Error` / `Reader` /
`Log` stack.
`notion-client-effectful` gives that caller an equivalent set of
operations surfaced as an `Effect` so the stack is preserved.
## Import pattern
Every smart constructor shares its name with the matching
`Notion.V1.Methods` record selector. That is on purpose — it
keeps migration from IO mechanical — but it means a file that
imports both modules unqualified will see name clashes. Import
one of the two qualified:
```haskell
import Notion.V1 (Methods, getClientEnv, makeMethods)
import Notion.V1.Effectful qualified as NE
```
Then `NE.retrievePage`, `NE.search`, `NE.runNotion`, and so on.
## Error handling
`runNotion` catches `Notion.V1.Error.NotionError` thrown by the
underlying `Methods` and re-raises it via the `Error NotionError`
effect, so callers can branch on API error shapes
(`object_not_found`, validation failures, etc.) without reaching
for `IO`-level exception handling.
Other `Servant.Client.ClientError` values — network failures,
decoding errors — are *not* caught and remain `IO` exceptions.
This preserves the contract of the underlying `notion-client`
library and lets callers layer their own `Error ClientError`
interpretation on top later. The same applies to
`UnknownHTTPResponseError`, `RequestTimeoutError` and
`InvalidPathParameterError` from `Notion.V1.Error`. Retries of
rate-limited requests happen inside `Methods`, before an error reaches
`runNotion`.
## Minimum viable usage
```haskell
module Demo where
import Data.Text (Text)
import Data.Text qualified as Text
import Effectful (Eff, runEff, (:>))
import Effectful.Error.Static (Error, runErrorNoCallStack)
import Notion.V1 (getClientEnv, makeMethods)
import Notion.V1.Common (UUID (..))
import Notion.V1.Effectful qualified as NE
import Notion.V1.Error (NotionError)
import System.Environment qualified as Env
demo ::
(NE.Notion :> es, Error NotionError :> es) =>
UUID ->
Eff es Text
demo pid = do
page <- NE.retrievePage pid
pure (Text.pack (show page))
main :: IO ()
main = do
token <- Text.pack <$> Env.getEnv "NOTION_TOKEN"
env <- getClientEnv "https://api.notion.com/v1"
let methods = makeMethods env token
result <-
runEff
. runErrorNoCallStack @NotionError
. NE.runNotion methods
$ demo (UUID "00000000-0000-0000-0000-000000000000")
print result
```
## What you get
One smart constructor per field of `Notion.V1.Methods`. The
argument types and order match the underlying field, so an
existing call like
```haskell
page <- retrievePage methods pid
```
becomes
```haskell
page <- NE.retrievePage pid
```
with no other change to the call site.
## Versioning
The `0.y.z` series tracks `notion-client` 0.7.x. A major version
bump is planned if `notion-client` changes the shape of `Methods`
in a way that forces a constructor rename.