packages feed

datastar-hs-0.1.0.0: src/Hypermedia/Datastar/PatchSignals.hs

-- |
-- Module      : Hypermedia.Datastar.PatchSignals
-- Description : Update the browser's reactive signal store
--
-- Signals are Datastar's reactive state — key-value pairs that live in the
-- browser and drive the UI. The server can update signals at any time by
-- sending a @datastar-patch-signals@ event.
--
-- Signal patching uses JSON Merge Patch semantics: set a key to update it,
-- set a key to @null@ to remove it, and omit a key to leave it unchanged.
--
-- @
-- sendPatchSignals gen (patchSignals \"{\\\"count\\\": 42}\")
-- @
--
-- To set initial state without overwriting values the user may have already
-- changed (e.g. form inputs), use 'psOnlyIfMissing':
--
-- @
-- sendPatchSignals gen
--   (patchSignals \"{\\\"name\\\": \\\"default\\\"}\")
--     { psOnlyIfMissing = True }
-- @
module Hypermedia.Datastar.PatchSignals where

import Data.Text (Text)
import Data.Text qualified as T
import Hypermedia.Datastar.Types

-- | Configuration for a @datastar-patch-signals@ SSE event.
--
-- Construct values with 'patchSignals', then customise with record updates.
data PatchSignals = PatchSignals
  { psSignals :: Text
  -- ^ JSON object containing the signal values to patch. Uses JSON Merge
  -- Patch semantics: set a key to update it, set to @null@ to remove it.
  , psOnlyIfMissing :: Bool
  -- ^ When 'True', signal values are only set if the key doesn't already
  -- exist in the browser's store. Useful for setting initial state that
  -- shouldn't overwrite user changes (e.g. form input defaults).
  -- Default: 'False'.
  , psEventId :: Maybe Text
  -- ^ Optional SSE event ID for reconnection. See 'Hypermedia.Datastar.PatchElements.PatchElements' for details.
  , psRetryDuration :: Int
  -- ^ SSE retry interval in milliseconds. Default: @1000@.
  }
  deriving (Eq, Show)

-- | Build a 'PatchSignals' event with sensible defaults.
--
-- The argument is a JSON object (as 'Text') describing the signals to update.
--
-- @
-- patchSignals \"{\\\"count\\\": 42, \\\"label\\\": \\\"hello\\\"}\"
-- @
patchSignals :: Text -> PatchSignals
patchSignals sigs =
  PatchSignals
    { psSignals = sigs
    , psOnlyIfMissing = defaultOnlyIfMissing
    , psEventId = Nothing
    , psRetryDuration = defaultRetryDuration
    }

toDatastarEvent :: PatchSignals -> DatastarEvent
toDatastarEvent ps =
  DatastarEvent
    { eventType = EventPatchSignals
    , eventId = psEventId ps
    , retry = psRetryDuration ps
    , dataLines = ifMissingLine ++ signalLines
    }
 where
  ifMissingLine = ["onlyIfMissing true" | psOnlyIfMissing ps]
  signalLines = map ("signals " <>) $ T.lines $ psSignals ps