packages feed

kiroku-store-0.12.0.0: src/Kiroku/Store/Link.hs

module Kiroku.Store.Link
  ( linkToStream,
  )
where

import Effectful (Eff, (:>))
import Effectful.Dispatch.Dynamic (send)
import GHC.Stack (HasCallStack)
import Kiroku.Store.Effect (Store (..))
import Kiroku.Store.Types

-- | Link existing events into a target stream.
--
-- __Provisional API.__ No known consumer uses this function (audited
-- 2026-06-11: zero usage in keiro, the only downstream framework). It is the
-- only public feature that requires the @stream_events@ junction-table layout,
-- which a future global-position migration may replace with a single-table event
-- layout (global position as a column on @events@); in that case this function
-- will be removed or redesigned (e.g. rehomed to a dedicated @stream_links@ side
-- table). If you have a real use case, surface it before depending on this. See
-- docs/architecture/global-position-migration-path.md.
--
-- Creates the target stream if it does not exist; otherwise appends
-- links to the existing target. Linking does not duplicate the event
-- payload — the @events@ row is shared, and only a new junction row in
-- @stream_events@ is created. Linked events keep their original
-- 'Kiroku.Store.Types.GlobalPosition' and 'Kiroku.Store.Types.RecordedEvent.originalStreamId' /
-- 'originalVersion' fields; the target's 'Kiroku.Store.Types.RecordedEvent.streamVersion'
-- reflects the link's position in the target.
--
-- Preconditions:
--
-- * Every supplied 'Kiroku.Store.Types.EventId' must reference an event
--   that currently exists. Linking an unknown id (a typo, or an id that
--   was hard-deleted) rejects the entire batch atomically — the target
--   stream is left in its pre-call state — with
--   'Kiroku.Store.Error.LinkSourceEventMissing'. (See EP-1 F3.)
-- * The target must not be soft-deleted. Linking to a soft-deleted target
--   fails with 'Kiroku.Store.Error.StreamNotFound'. (See EP-1 F5.)
-- * The target must not be @$all@. @$all@ is the global read stream, and
--   linking into it would bypass the append-only global ordering contract;
--   the interpreter rejects this with
--   'Kiroku.Store.Error.ReservedStreamName'.
-- * Linking the same event into the same target stream twice fails with
--   'Kiroku.Store.Error.EventAlreadyLinked'.
--
-- Returns the target's 'Kiroku.Store.Types.LinkResult' — its id and the
-- position of the /last/ linked event in the target.
--
-- Empty input @[]@ is a no-op programming mistake; do not call.
linkToStream ::
  (HasCallStack, Store :> es) =>
  StreamName ->
  [EventId] ->
  Eff es LinkResult
linkToStream targetStream eventIds = send (LinkToStream targetStream eventIds)