UNPKG

@tanstack/db

Version:

A reactive client store for building super fast apps on sync

2,011 lines • 73.1 kB
import { ensureIndexForExpression } from '../indexes/auto-index.js'
import { and, eq } from '../query/builder/functions.js'
import { PropRef, Value, getPropRefPropertyPath } from '../query/ir.js'
import { EventEmitter } from '../event-emitter.js'
import { compileExpression } from '../query/compiler/evaluators.js'
import { buildCursor, buildCursorCurrent } from '../utils/cursor.js'
import { deepEquals } from '../utils.js'
import { normalizeError } from '../utils/error.js'
import { reportUncaught, runAllCallbacks } from '../utils/callbacks.js'
import { createDeferred } from '../deferred.js'
import { LoadSubsetOperationAbortedError } from '../errors.js'
import { isFailedStatus } from '../live-query-primitives.js'
import { codedMessage, devBuild } from '../error-message.js'
import {
  createFilterFunctionFromExpression,
  createFilteredCallback,
} from './change-events.js'
import type { BasicExpression, OrderBy } from '../query/ir.js'
import type { IndexReader } from '../indexes/base-index.js'
import type {
  ChangeMessage,
  CollectionDependent,
  LoadSubsetOptions,
  LoadSubsetRequestResult,
  SubscribeChangesOptions,
  Subscription,
  SubscriptionEvents,
  SubscriptionLoadSubsetErrorEvent,
  SubscriptionStatus,
  SubscriptionUnsubscribedEvent,
} from '../types.js'
import type { CollectionImpl } from './index.js'
import type { Deferred } from '../deferred.js'

type RequestSnapshotOptions = {
  refetch?: boolean
  where?: BasicExpression<boolean>
  signal?: AbortSignal
  optimizedOnly?: boolean
  trackLoadSubsetPromise?: boolean
  /** Optional orderBy to pass to loadSubset for backend optimization */
  orderBy?: OrderBy
  /** Optional limit to pass to loadSubset for backend optimization */
  limit?: number
  /** Read only the first `limit` local rows: the source holds every row and an index serves the order. */
  boundedPrefix?: boolean
  /** Callback that receives the normalized loadSubset result for internal tracking */
  onLoadSubsetResult?: SubsetResultObserver
  /** Called when the local snapshot must fall back from an index to a scan. */
  onUnoptimized?: () => void
}

type RequestLimitedSnapshotOptions = {
  refetch?: boolean
  orderBy: OrderBy
  limit: number
  /** A single cursor value; composite cursor inputs are rejected. */
  minValues?: Array<unknown>
  /** Row offset for offset-based pagination (passed to sync layer) */
  offset?: number
  /** Whether to track the loadSubset promise on this subscription (default: true) */
  trackLoadSubsetPromise?: boolean
  /** Callback that receives the normalized loadSubset result for internal tracking */
  onLoadSubsetResult?: SubsetResultObserver
}

export type ReleaseLoadSubset = (primaryFailure?: { error: unknown }) => void

type SubsetResultObserver = (
  result: LoadSubsetRequestResult,
  options: LoadSubsetOptions,
  release: ReleaseLoadSubset,
) => void

type CollectionSubscriptionOptions = {
  includeInitialState?: boolean
  /** Pre-compiled expression for filtering changes */
  whereExpression?: BasicExpression<boolean>
  /** Callback to call when the subscription is unsubscribed */
  onUnsubscribe?: (event: SubscriptionUnsubscribedEvent) => void
  /** Callback for subset-load failures scoped to this subscription. */
  onLoadSubsetError?: (event: SubscriptionLoadSubsetErrorEvent) => void
  truncateReplayPublication?: TruncateReplayPublicationControl
  /**
   * Whether this subscription defers provider work: it starts no idle source
   * sync run and no acquisition attempt until its acquisition resumes. A
   * live-query Collection defers its source subscriptions until it has a
   * subscriber or a preload in its current sync run.
   */
  deferAcquisition?: boolean
  /** Start sync, then return the ownership notification for a live resumption. */
  onResumeAcquisition?: () => () => void
  /** The live query or Effect this subscription reads for. */
  owner?: CollectionDependent
}

type TruncateReplayPublicationControl = NonNullable<
  SubscribeChangesOptions[`truncateReplayPublication`]
>

type TruncatePublicationState = {
  loadedInitialState: boolean
  snapshotSent: boolean
}

/** Internal record spanning a tentative attempt and any accepted acquisition. */
type SubsetAcquisitionRecord = {
  options: LoadSubsetOptions
  syncRunGeneration: number
  abortController?: AbortController
  removeRequestAbortListener?: () => void
  releaseAttempted?: true
  // The replay attempt that started this acquisition.
  attempt?: TruncateReplayAttempt
}

type SubsetDemand = {
  requestOptions: LoadSubsetOptions
  acquisition: SubsetAcquisitionRecord
  acquisitionState: `starting` | `active` | `detached`
  initialResult?: Deferred<void>
  // A parked demand's wait in an open replay. An acquisition that replays
  // it replaces the wait, which settles only with that replay.
  parkedParticipant?: TruncateReplayParticipant
  // The demands this one replaces for its owner: their replay failures no
  // longer count once it loads (law 10: the latest load decides).
  replaces?: Array<SubsetDemand>
}

type TruncateReplayAttempt = {
  pendingCount: number
  setupComplete: boolean
}

type TruncateReplayParticipant = {
  demand: SubsetDemand
  attempt: TruncateReplayAttempt
}

type TruncateReplayState = {
  syncRunGeneration: number
  publicationState: TruncatePublicationState
  /** Direct subscribers buffer the replacement here; delegated publication has no buffer. */
  privateRows: Map<string | number, object> | undefined
  pending: Set<TruncateReplayParticipant>
  pendingSetups: number
  currentAttempt: TruncateReplayAttempt
  failures: Map<SubsetDemand, Error>
  /**
   * Demands whose load of the current attempt was aborted before it settled:
   * the abort ended it, so it loaded nothing and covers no demand it
   * replaces (law 10).
   */
  unloaded: Set<SubsetDemand>
  /** The current attempt failed, even if its failed demand was released since. */
  failed: boolean
  completion: Deferred<void>
}

function createReplayCompletion(): Deferred<void> {
  const completion = createDeferred<void>()
  void completion.promise.catch(() => {})
  return completion
}

function cancelAcquisition(acquisition: SubsetAcquisitionRecord): void {
  acquisition.abortController?.abort()
  acquisition.removeRequestAbortListener?.()
}

// An abort before the load settles ends it. Once the load settles, the
// listener goes, so it keeps nothing alive for the acquisition's lifetime
// (law 13).
function onAbortBeforeSettled(
  signal: AbortSignal | undefined,
  result: Promise<unknown>,
  onAbort: () => void,
): void {
  signal?.addEventListener(`abort`, onAbort, { once: true })
  const settled = () => signal?.removeEventListener(`abort`, onAbort)
  void result.then(settled, settled)
}

export class CollectionSubscription
  extends EventEmitter<SubscriptionEvents>
  implements Subscription
{
  private loadedInitialState = false

  // Flag to skip filtering in filterAndFlipChanges.
  // This is separate from loadedInitialState because we want to allow
  // requestSnapshot to still work even when filtering is skipped.
  private skipFiltering = false

  // Flag to indicate that we have sent at least 1 snapshot.
  // While `snapshotSent` is false we filter out all changes from subscription to the collection.
  private snapshotSent = false

  /**
   * Track all loadSubset calls made by this subscription so we can unload them on cleanup.
   * We store the exact LoadSubsetOptions we passed to loadSubset to ensure symmetric unload.
   */
  private subsetDemands: Array<SubsetDemand> = []
  private readonly requestedSubsetWhere = new WeakMap<
    LoadSubsetOptions,
    BasicExpression<boolean>
  >()

  // Keep track of keys selected by snapshots or delivered through callbacks.
  // Ordered snapshots skip these keys until a valid reset.
  private sentKeys = new Set<string | number>()
  private publishedRows = new Map<string | number, object>()
  private stalePublishedRows = new Map<string | number, object>()

  private filteredCallback: (changes: Array<ChangeMessage<any, any>>) => boolean

  private orderByIndex: IndexReader<string | number> | undefined

  // Status tracking
  private _status: SubscriptionStatus = `ready`
  private statusRevision = 0
  private _lastError: unknown | undefined
  private pendingLoadSubsetParticipants = new Set<{
    demand: SubsetDemand
    promise: Promise<unknown>
  }>()

  // Cleanup function for truncate event listener
  private truncateCleanup: (() => void) | undefined
  private collectionCleanup: (() => void) | undefined
  private collectionRestartCleanup: (() => void) | undefined

  // One replay state owns the publication baseline, overlapping attempts,
  // and buffered changes until every attempt settles.
  private truncateReplayState: TruncateReplayState | undefined
  private readonly loadSubsetPromiseErrors = new WeakMap<
    Promise<unknown>,
    Error
  >()
  private truncateReplacementPending = false
  private unsubscribed = false
  private defersAcquisition: boolean

  /** @internal The live query or Effect this subscription reads for. */
  public get owner(): CollectionSubscriptionOptions[`owner`] {
    return this.options.owner
  }

  public get status(): SubscriptionStatus {
    return this._status
  }

  public get lastError(): unknown | undefined {
    return this._lastError
  }

  constructor(
    private collection: CollectionImpl<any, any, any, any, any>,
    private callback: (changes: Array<ChangeMessage<any, any>>) => void,
    private options: CollectionSubscriptionOptions,
  ) {
    super()
    this.defersAcquisition = options.deferAcquisition === true
    if (options.onUnsubscribe) {
      this.on(`unsubscribed`, options.onUnsubscribe)
    }
    if (options.onLoadSubsetError) {
      this.on(`loadSubset:error`, options.onLoadSubsetError)
    }

    // Auto-index for where expressions if enabled
    if (options.whereExpression) {
      ensureIndexForExpression(options.whereExpression, this.collection)
    }

    const callbackWithSentKeysTracking = (
      changes: Array<ChangeMessage<any, any>>,
    ) => {
      this.trackPublishedRows(changes)
      this.trackSentKeys(changes)
      callback(changes)
    }

    this.callback = callbackWithSentKeysTracking

    // Create a filtered callback if where clause is provided
    this.filteredCallback = options.whereExpression
      ? createFilteredCallback(this.callback, options)
      : (changes) => {
          this.callback(changes)
          return true
        }

    // A truncate or a snapshot (for example a 409 must-refetch) clears every
    // row, so every one re-requests the loaded subsets, also one that a
    // reload's new run sends after the reload's replay began or settled: the
    // replacement deleted what that replay loaded.
    this.truncateCleanup = this.collection.on(`truncate`, () =>
      this.handleTruncate(),
    )
    this.collectionCleanup = this.collection._onCleanupStart(() => {
      this.handleCollectionCleanup()
    })
    this.collectionRestartCleanup = this.collection.on(
      `status:change`,
      ({ status }) => {
        if (status !== `loading` && status !== `ready`) return
        const syncRunGeneration = this.collection._sync.getSyncRunGeneration()
        const replayState = this.truncateReplayState
        if (
          this.subsetDemands.some(
            (demand) => demand.acquisitionState === `detached`,
          )
        ) {
          this.setStatus(`loadingSubset`)
        }
        queueMicrotask(() => {
          if (this.truncateReplayState === replayState) {
            this.restartDetachedDemands(syncRunGeneration)
          }
        })
      },
    )
  }

  /** Whether this subscription still defers provider work. */
  public isDeferringAcquisition(): boolean {
    return this.defersAcquisition
  }

  /**
   * Resume deferred acquisition: provider work may now start. This may start
   * an idle source Collection's sync run, then starts acquisition attempts for
   * the on-demand demand that stayed detached while acquisition was deferred.
   */
  public resumeDeferredAcquisition(): void {
    if (!this.defersAcquisition || this.unsubscribed) return
    // Start the source first: if its start throws, this subscription still
    // defers rather than claiming acquisition it never began.
    const notifyResumed = this.options.onResumeAcquisition?.()
    // Startup can synchronously retire this subscription. It then owns no
    // acquisition, including when a status callback unsubscribes during startup.
    if (this.unsubscribed as boolean) return
    this.defersAcquisition = false
    notifyResumed?.()
    if (this.collection.status !== `idle`) {
      this.restartDetachedDemands(this.collection._sync.getSyncRunGeneration())
    }
  }

  /**
   * @internal A reload retired the sync run. Its demand detaches from the
   * retired run, and on-demand demand replays through the new run at once,
   * as after a truncate, so a consumer of an aborted load follows that
   * replay. The rows stay: the new run's writes apply on them.
   */
  public retireSyncRun(): void {
    const replays = this.replaysDemand() && this.subsetDemands.length > 0
    // The replacement stays pending from the old replay to the new one, so
    // nothing that runs during the detach sees a settled source.
    this.handleCollectionCleanup(true, replays)
    if (replays) this.handleTruncate(true)
  }

  /** Whether a truncate acquires this subscription's demand again. */
  private replaysDemand(): boolean {
    // Eager demand is satisfied by source readiness, even when the adapter
    // happens to expose a loader. Deferred demand never acquired, so it
    // acquires the replacement rows when acquisition resumes.
    return (
      this.collection.config.syncMode === `on-demand` && !this.defersAcquisition
    )
  }

  /**
   * Detach logical demand from work owned by a discarded sync run. A reload
   * keeps waiting callers for its replay, and the rows.
   */
  private handleCollectionCleanup(
    reload = false,
    holdReplacement = false,
  ): void {
    this.discardTruncateReplay()
    this.truncateReplacementPending = holdReplacement
    // Cleanup discards the rows without publishing deletes, so the published
    // rows go stale. A reload keeps the rows: only rows already stale stay so.
    if (!reload) this.stalePublishedRows = new Map(this.publishedRows)
    this.pendingLoadSubsetParticipants.clear()

    for (const demand of [...this.subsetDemands]) {
      if (!reload || demand.acquisitionState === `starting`) {
        demand.initialResult?.reject(new LoadSubsetOperationAbortedError())
      }
      cancelAcquisition(demand.acquisition)
      if (demand.acquisitionState === `starting`) {
        const index = this.subsetDemands.indexOf(demand)
        if (index !== -1) this.subsetDemands.splice(index, 1)
      } else {
        demand.acquisitionState = `detached`
        demand.acquisition = {
          options: demand.requestOptions,
          syncRunGeneration: demand.acquisition.syncRunGeneration,
        }
      }
    }
    this.setReadyIfIdle()
  }

  /** Acquire detached demand after startup or initial-error recovery. */
  private restartDetachedDemands(syncRunGeneration: number): void {
    if (
      this.unsubscribed ||
      // Deferred demand stays detached until acquisition resumes, even when
      // another consumer starts the source.
      this.defersAcquisition ||
      !this.isSyncRunGenerationCurrent(syncRunGeneration)
    ) {
      return
    }
    if (
      isFailedStatus(this.collection.status) ||
      (this.collection.config.syncMode === `on-demand` &&
        this.collection._sync.syncLoadSubsetFn === null)
    ) {
      this.setReadyIfIdle()
      return
    }
    const demands = this.subsetDemands.filter(
      (demand) =>
        demand.acquisitionState === `detached` &&
        !demand.requestOptions.signal?.aborted,
    )
    if (demands.length === 0) {
      this.setReadyIfIdle()
      return
    }
    // Eager demand is satisfied by the complete ready snapshot, never by a
    // subset acquisition or the partial state of a loading sync run.
    if (
      this.collection.config.syncMode !== `on-demand` &&
      this.collection.status !== `ready`
    ) {
      return
    }

    // A replay still open in this sync run keeps its loads and failures: the
    // detached demand joins it (law 9), in its current attempt while that
    // attempt is in flight, and as a new attempt once it failed (law 16).
    const open = this.truncateReplayState
    const joins = open?.syncRunGeneration === syncRunGeneration
    const replayState = joins
      ? open
      : this.createTruncateReplayState(syncRunGeneration, () => {
          const currentRows = this.collection.currentStateAsChanges({
            optimizedOnly: false,
          })
          return new Map(
            // The API returns void for unavailable snapshots, not just undefined.
            // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
            (currentRows ?? [])
              .filter((change) => change.type !== `delete`)
              .map((change) => [change.key, change.value]),
          )
        })
    if (joins) {
      open.pendingSetups++
      if (open.failed) {
        if (!open.completion.isPending())
          open.completion = createReplayCompletion()
        open.currentAttempt = { pendingCount: 0, setupComplete: false }
      }
    }
    const attempt = replayState.currentAttempt
    this.truncateReplayState = replayState
    this.setStatus(`loadingSubset`)
    if (this.truncateReplayState !== replayState) return
    this.startTruncateReplayAttempt(replayState, attempt, demands)
  }

  /**
   * Handle collection truncate event by resetting state and re-requesting subsets.
   * This is called when the sync layer receives a must-refetch and clears all data.
   *
   * To prevent a flash of missing content, we buffer all changes (deletes from truncate
   * and inserts from refetch) until all loadSubset calls succeed, then emit them together.
   * A failed replay keeps the last published snapshot private until a later
   * authoritative replay succeeds.
   */
  private handleTruncate(retired = false) {
    // A reload's replay, or a snapshot, can start inside the new run's
    // `sync()`, before it returns its loader; the requests below start after
    // it has.
    const demandsToReload = this.replaysDemand() ? [...this.subsetDemands] : []

    // Retained rows still need the committed replacement even without demand.
    if (demandsToReload.length === 0 && this.stalePublishedRows.size === 0) {
      this.resetSnapshotTracking()
      return
    }

    let replayState = this.truncateReplayState
    if (replayState) {
      if (!replayState.completion.isPending()) {
        replayState.completion = createReplayCompletion()
      }
      // Setup itself holds publication: adapter/status callbacks may reenter
      // before a request returns its promise and joins the pending set.
      replayState.pendingSetups++
      replayState.failures.clear()
      replayState.unloaded.clear()
      replayState.failed = false
      replayState.currentAttempt = { pendingCount: 0, setupComplete: false }
    } else {
      // Every overlapping attempt shares one publication baseline and buffer.
      replayState = this.createTruncateReplayState(
        this.collection._sync.getSyncRunGeneration(),
        () => new Map(this.publishedRows),
      )
      this.truncateReplayState = replayState
    }
    const attempt = replayState.currentAttempt
    this.setStatus(`loadingSubset`)

    if (this.truncateReplayState !== replayState) return

    if (this.options.truncateReplayPublication) {
      this.truncateReplacementPending = true
      this.options.truncateReplayPublication.start(retired)
    }

    // A newer replay replaces every prior acquisition for these demands. Abort
    // the old work before it can install rows into the replacement replay.
    for (const demand of demandsToReload) {
      demand.acquisition.abortController?.abort()
    }

    // Reset snapshot/pagination tracking for the replacement snapshot. Rows
    // retained from an earlier failed replay stay marked until this attempt
    // either replaces them or proves they are absent.
    this.resetSnapshotTracking()

    // Defer the requests so the truncate commit's deletes enter the replay state
    // buffer before a synchronous adapter can publish replacement rows.
    queueMicrotask(() => {
      if (this.truncateReplayState !== replayState) return
      if (!this.isSyncRunGenerationCurrent(replayState.syncRunGeneration)) {
        this.retireStaleTruncateReplay(replayState)
        return
      }
      // A newer truncate that arrived before this attempt began source work
      // already captured the active demands. Starting them now would place the
      // obsolete acquisition outside the newer abort sweep.
      this.startTruncateReplayAttempt(
        replayState,
        attempt,
        replayState.currentAttempt === attempt ? demandsToReload : [],
      )
    })
  }

  /** Make tentative replay ownership visible before adapter code can reenter. */
  private startTruncateReplayDemand(
    replayState: TruncateReplayState,
    attempt: TruncateReplayAttempt,
    demand: SubsetDemand,
  ): void {
    const isCurrentAttempt = () =>
      this.truncateReplayState === replayState &&
      replayState.currentAttempt === attempt
    const isCurrent = () =>
      isCurrentAttempt() &&
      this.isSyncRunGenerationCurrent(replayState.syncRunGeneration) &&
      this.isDemandActive(demand)
    const waiting = demand.initialResult
    const parked = demand.parkedParticipant
    demand.parkedParticipant = undefined
    if (parked && replayState.pending.delete(parked))
      parked.attempt.pendingCount--
    if (waiting) {
      // A replay discarded by a later reload leaves the caller waiting for
      // that reload's replay; cleanup rejects waiting callers itself.
      void replayState.completion.promise.then(waiting.resolve, (error) => {
        if (!(error instanceof LoadSubsetOperationAbortedError))
          waiting.reject(error)
      })
    }

    // Sequential handoff: retire the old acquisition lease while retaining its
    // logical demand. Callback reentry cannot release that lease twice.
    const previous = demand.acquisition
    const hadPreviousAcquisition = demand.acquisitionState === `active`
    demand.acquisitionState = `detached`
    if (hadPreviousAcquisition) this.releaseAcquisition(previous)
    if (!isCurrent() || demand.requestOptions.signal?.aborted) return

    const next = this.createSubsetAcquisitionRecord(demand)
    next.attempt = attempt
    demand.acquisition = next
    demand.acquisitionState = `starting`
    const result = this.collection._sync.loadSubset(next.options)

    if (!isCurrent()) {
      if (demand.acquisition === next) demand.acquisitionState = `detached`
      this.releaseAcquisition(next)
      return
    }

    demand.acquisitionState = `active`
    // Observe first: a failure reports before its settlement can publish the
    // replay, also when a held replacement covers it and it settles last.
    this.observeLoadSubsetResult(
      result,
      demand,
      next.options,
      true,
      () => isCurrent() && !next.options.signal?.aborted,
    )
    this.trackTruncateReplayParticipant(
      replayState,
      attempt,
      demand,
      result,
      next.options.signal,
    )
  }

  private settleTruncateReplay(
    replayState: TruncateReplayState,
    pending: { demand: SubsetDemand; attempt: TruncateReplayAttempt },
  ): void {
    try {
      if (this.truncateReplayState !== replayState) return
      if (!this.isSyncRunGenerationCurrent(replayState.syncRunGeneration)) {
        this.retireStaleTruncateReplay(replayState)
        return
      }
      if (replayState.pending.delete(pending)) pending.attempt.pendingCount--
      this.checkTruncateReplayComplete(replayState)
    } catch (error) {
      // Replay settlement runs from a Promise callback, so throwing here would
      // create an unobserved derived rejection. Surface subscriber errors like
      // other async collection events instead.
      queueMicrotask(() => {
        throw error
      })
    }
  }

  /**
   * Keep every acquisition begun during recovery inside its publication
   * barrier until it settles or aborts. An abort ends the load: the replay
   * no longer waits for it, and its later settlement decides nothing.
   */
  private trackTruncateReplayParticipant(
    replayState: TruncateReplayState,
    attempt: TruncateReplayAttempt,
    demand: SubsetDemand,
    result: LoadSubsetRequestResult,
    signal: AbortSignal | undefined,
  ): TruncateReplayParticipant | undefined {
    if (
      this.truncateReplayState !== replayState ||
      (replayState.currentAttempt !== attempt &&
        attempt.setupComplete &&
        attempt.pendingCount === 0) ||
      !(result instanceof Promise)
    ) {
      return undefined
    }

    // An older attempt can still accept returning startup work while setup or
    // another participant retains it. Once drained, it cannot reopen. Shared
    // promises still get one participant per logical acquisition.
    const pending = { demand, attempt }
    attempt.pendingCount++
    replayState.pending.add(pending)
    const settle = () => this.settleTruncateReplay(replayState, pending)
    // After the step that aborted it, as a settlement would be.
    onAbortBeforeSettled(signal, result, () => {
      if (replayState.currentAttempt === attempt)
        replayState.unloaded.add(demand)
      queueMicrotask(settle)
    })
    void result.then(settle, (error: unknown) => {
      if (
        !signal?.aborted &&
        this.truncateReplayState === replayState &&
        replayState.currentAttempt === attempt &&
        this.isSyncRunGenerationCurrent(replayState.syncRunGeneration) &&
        this.subsetDemands.includes(demand)
      ) {
        const normalized = this.normalizeLoadSubsetPromiseError(result, error)
        replayState.failures.set(demand, normalized)
        replayState.failed = true
      }
      settle()
    })
    return pending
  }

  /** Stop obsolete logical demand from pinning a replay barrier. */
  private removeTruncateReplayParticipant(demand: SubsetDemand): void {
    const replayState = this.truncateReplayState
    if (!replayState) return
    replayState.failures.delete(demand)
    for (const pending of replayState.pending) {
      if (pending.demand === demand) {
        replayState.pending.delete(pending)
        pending.attempt.pendingCount--
      }
    }
  }

  /** Publish only after every overlapping replay attempt has settled. */
  private checkTruncateReplayComplete(replayState: TruncateReplayState): void {
    if (this.truncateReplayState !== replayState) return
    if (replayState.pendingSetups > 0 || replayState.pending.size > 0) return

    // A held replacement that loaded decides the demands it replaces.
    const activeFailures = [...replayState.failures]
      .filter(
        ([demand]) =>
          this.subsetDemands.includes(demand) &&
          !this.subsetDemands.some(
            (replacement) =>
              replacement.replaces?.includes(demand) &&
              !replayState.failures.has(replacement) &&
              !replayState.unloaded.has(replacement),
          ),
      )
      .map(([, error]) => error)
    try {
      if (activeFailures.length > 0) {
        this.abandonTruncateReplay(replayState, activeFailures)
      } else {
        if (
          this.awaitsSiblingReplays(replayState, () =>
            this.checkTruncateReplayComplete(replayState),
          )
        )
          return
        this.flushTruncateReplay(replayState)
      }
    } finally {
      this.setReadyIfIdle()
    }
  }

  /**
   * Keep an incomplete replay private. The source no longer proves a complete
   * state, so only a later successful truncate replay may reopen publication.
   */
  private abandonTruncateReplay(
    replayState: TruncateReplayState,
    failures: Array<Error>,
  ): void {
    if (this.truncateReplayState !== replayState) return
    // A later check of the same failed attempt reports nothing new. The
    // replay's own error is its earliest failure.
    const first = replayState.completion.isPending()
    replayState.completion.reject(failures[0])
    if (first) this.options.truncateReplayPublication?.fail?.(failures)
    // Delegated publication already delivered its rows. Only a private buffer
    // returns the caller's pagination position to the public snapshot; the
    // private rows and their sent-key tracking stay together for a retry.
    if (!replayState.privateRows) return
    const publicationState = replayState.publicationState
    this.loadedInitialState = publicationState.loadedInitialState
    this.snapshotSent = publicationState.snapshotSent
  }

  /** Publish the buffered replacement as one batch, or release the delegate. */
  private flushTruncateReplay(replayState: TruncateReplayState): void {
    if (this.truncateReplayState !== replayState) return
    this.truncateReplayState = undefined
    this.truncateReplacementPending = false
    try {
      this.publishReplayRows(replayState.privateRows)
    } finally {
      replayState.completion.resolve()
      this.options.truncateReplayPublication?.succeed()
    }
  }

  /** Replace the public snapshot with a replay's private rows in one batch. */
  private publishReplayRows(
    privateRows: Map<string | number, object> | undefined,
  ): void {
    // Retained rows the source never re-delivered leave the replacement.
    for (const key of this.stalePublishedRows.keys()) privateRows?.delete(key)
    this.stalePublishedRows.clear()
    try {
      if (privateRows) {
        // Diff the retained public snapshot against the applied source replacement.
        const replacement = this.createStateDiff(
          this.publishedRows,
          privateRows,
        )
        if (replacement.length > 0) this.filteredCallback(replacement)
      }
    } finally {
      // Restore tracking even when a subscriber rejects the replacement.
      this.restorePublishedSnapshotTracking()
    }
  }

  private restorePublishedSnapshotTracking(): void {
    this.sentKeys = new Set(this.publishedRows.keys())
  }

  /** Fold changes into the private replacement; false when they publish now. */
  private bufferPrivately(
    changes: ReadonlyArray<ChangeMessage<any, any>>,
  ): boolean {
    const privateRows = this.truncateReplayState?.privateRows
    if (!privateRows) return false
    for (const change of changes) {
      if (change.type === `delete`) privateRows.delete(change.key)
      else privateRows.set(change.key, change.value)
    }
    return true
  }

  private createStateDiff(
    baseline: ReadonlyMap<string | number, object>,
    finalRows: ReadonlyMap<string | number, object>,
  ): Array<ChangeMessage<any, any>> {
    const replacement: Array<ChangeMessage<any, any>> = []
    for (const [key, previousValue] of baseline) {
      const value = finalRows.get(key)
      if (value === undefined) {
        replacement.push({
          type: `delete`,
          key,
          value: previousValue,
        })
      } else if (!deepEquals(value, previousValue)) {
        replacement.push({
          type: `update`,
          key,
          value,
          previousValue,
        })
      }
    }
    for (const [key, value] of finalRows) {
      if (!baseline.has(key)) replacement.push({ type: `insert`, key, value })
    }
    return replacement
  }

  private get isBufferingForTruncate(): boolean {
    return this.truncateReplayState !== undefined
  }

  private setReadyIfIdle(): void {
    if (
      this.pendingLoadSubsetParticipants.size === 0 &&
      !this.hasTruncateReplayWork
    ) {
      this.setStatus(`ready`)
    }
  }

  private isSyncRunGenerationCurrent(syncRunGeneration: number): boolean {
    return syncRunGeneration === this.collection._sync.getSyncRunGeneration()
  }

  private retireStaleTruncateReplay(replayState: TruncateReplayState): void {
    if (this.truncateReplayState !== replayState) return
    this.discardTruncateReplay()
    this.stalePublishedRows.clear()
  }

  /**
   * A lazy demand that failed outside this subscription's replay, while a
   * failed replay of another of the graph's subscriptions to this collection
   * holds publication, fails this subscription's replacement too. It keeps
   * the last publication until every demand load that joins this replay
   * loads, a later replay succeeds, or this subscription's last demand
   * retires. The failed demand's retry joins it, so it cannot publish
   * without that demand's rows.
   */
  failReplacement(error: Error): void {
    if (this.truncateReplayState) return
    const replayState = this.createTruncateReplayState(
      this.collection._sync.getSyncRunGeneration(),
      () => new Map(),
    )
    replayState.pendingSetups = 0
    replayState.failed = true
    replayState.completion.reject(error)
    this.truncateReplayState = replayState
    this.truncateReplacementPending = true
  }

  /**
   * A graph's subscriptions to one collection share a barrier. While another
   * one's replay still loads, this replay stays open, so demand this one
   * starts meanwhile joins it; it finishes once those replays settle.
   */
  private awaitsSiblingReplays(
    replayState: TruncateReplayState,
    finish: () => void,
  ): boolean {
    const siblings = this.options.truncateReplayPublication?.siblingReplays()
    void siblings?.then(() => {
      if (this.truncateReplayState === replayState) finish()
    })
    return siblings !== undefined
  }

  /** Whether this subscription's replay still waits on its own work. */
  public get hasTruncateReplayWork(): boolean {
    const replayState = this.truncateReplayState
    return (
      replayState !== undefined &&
      (replayState.pendingSetups > 0 || replayState.pending.size > 0)
    )
  }

  /** Drop the replay without publishing; an unfinished wait rejects as aborted. */
  private discardTruncateReplay(): void {
    const replayState = this.truncateReplayState
    if (replayState?.completion.isPending()) {
      replayState.completion.reject(new LoadSubsetOperationAbortedError())
    }
    this.truncateReplayState = undefined
    this.truncateReplacementPending = false
  }

  private resetSnapshotTracking(): void {
    this.snapshotSent = false
    this.loadedInitialState = false
  }

  /** One replay state; only direct subscribers buffer a private replacement. */
  private createTruncateReplayState(
    syncRunGeneration: number,
    privateRows: () => Map<string | number, object>,
  ): TruncateReplayState {
    return {
      syncRunGeneration,
      publicationState: {
        loadedInitialState: this.loadedInitialState,
        snapshotSent: this.snapshotSent,
      },
      privateRows: this.options.truncateReplayPublication
        ? undefined
        : privateRows(),
      pending: new Set(),
      // Setup itself holds publication: adapter/status callbacks may reenter
      // before a request returns its promise and joins the pending set.
      pendingSetups: 1,
      currentAttempt: { pendingCount: 0, setupComplete: false },
      failures: new Map(),
      unloaded: new Set(),
      failed: false,
      completion: createReplayCompletion(),
    }
  }

  /** Start one attempt's demands, then release the setup hold on publication. */
  private startTruncateReplayAttempt(
    replayState: TruncateReplayState,
    attempt: TruncateReplayAttempt,
    demands: ReadonlyArray<SubsetDemand>,
  ): void {
    for (const demand of demands) {
      // A demand this attempt already started, as a restart that joined it
      // before its own setup ran, is loading for it: one attempt loads each
      // demand once.
      if (
        !this.subsetDemands.includes(demand) ||
        demand.acquisition.attempt === attempt
      )
        continue
      this.startTruncateReplayDemand(replayState, attempt, demand)
      if (
        this.truncateReplayState !== replayState ||
        replayState.currentAttempt !== attempt
      ) {
        break
      }
    }
    attempt.setupComplete = true
    replayState.pendingSetups--
    this.checkTruncateReplayComplete(replayState)
  }

  public get hasPendingTruncateReplacement(): boolean {
    return this.truncateReplacementPending
  }

  /** Whether the source accepted sync rows that are not yet visible. */
  public get hasHeldSyncTransaction(): boolean {
    return this.collection._hasHeldSyncTransaction()
  }

  public get pendingTruncateReplacement(): Promise<void> | undefined {
    const completion = this.truncateReplayState?.completion
    return completion?.isPending() ? completion.promise : undefined
  }

  public get hasFailedTruncateReplacement(): boolean {
    const completion = this.truncateReplayState?.completion
    return (
      this.truncateReplacementPending &&
      completion !== undefined &&
      !completion.isPending()
    )
  }

  setOrderByIndex(index: IndexReader<any>) {
    this.orderByIndex = index
  }

  /**
   * Set subscription status and emit events if changed
   */
  private setStatus(newStatus: SubscriptionStatus) {
    if (this.unsubscribed) return
    if (this._status === newStatus) {
      return // No change
    }

    const previousStatus = this._status
    this._status = newStatus
    const revision = ++this.statusRevision

    // Emit status:change event
    this.emitInnerWhile(
      `status:change`,
      {
        type: `status:change`,
        subscription: this,
        previousStatus,
        status: newStatus,
      },
      () => this.statusRevision === revision,
    )

    // A listener may synchronously start or release demand. Do not follow that
    // newer transition with a stale specific event.
    if (this.statusRevision !== revision) return

    // Emit specific status event
    const eventKey: `status:${SubscriptionStatus}` = `status:${newStatus}`
    this.emitInnerWhile(
      eventKey,
      {
        type: eventKey,
        subscription: this,
        previousStatus,
        status: newStatus,
      } as SubscriptionEvents[typeof eventKey],
      () => this.statusRevision === revision,
    )
  }

  /** Observe an asynchronous subset load and restore status on settlement. */
  private observeLoadSubsetResult(
    syncResult: LoadSubsetRequestResult,
    demand: SubsetDemand,
    options: LoadSubsetOptions,
    trackStatus: boolean,
    shouldReportError: () => boolean = () => true,
  ): void {
    if (!(syncResult instanceof Promise)) return

    const syncRunGeneration = this.collection._sync.getSyncRunGeneration()
    const participant = { demand, promise: syncResult }

    if (trackStatus) {
      this.pendingLoadSubsetParticipants.add(participant)
      this.setStatus(`loadingSubset`)
    }

    const finish = () => {
      if (trackStatus) {
        this.pendingLoadSubsetParticipants.delete(participant)
        if (this.isSyncRunGenerationCurrent(syncRunGeneration)) {
          this.setReadyIfIdle()
        }
      }
    }
    // An abort ends the load: after the step that aborted it, it holds
    // readiness no more.
    if (trackStatus)
      onAbortBeforeSettled(options.signal, syncResult, () =>
        queueMicrotask(finish),
      )

    void syncResult.then(finish, (error: unknown) => {
      if (
        this.isSyncRunGenerationCurrent(syncRunGeneration) &&
        shouldReportError()
      ) {
        this.recordLoadSubsetError(
          options,
          this.normalizeLoadSubsetPromiseError(syncResult, error),
        )
      }
      finish()
    })
  }

  /** Give every logical observer of one transport rejection the same Error. */
  private normalizeLoadSubsetPromiseError(
    promise: Promise<unknown>,
    error: unknown,
  ): Error {
    const existing = this.loadSubsetPromiseErrors.get(promise)
    if (existing) return existing
    const normalized = normalizeError(error)
    this.loadSubsetPromiseErrors.set(promise, normalized)
    return normalized
  }

  private stopDemandStatusParticipants(demand: SubsetDemand): void {
    for (const participant of this.pendingLoadSubsetParticipants) {
      if (participant.demand === demand) {
        this.pendingLoadSubsetParticipants.delete(participant)
      }
    }
    this.setReadyIfIdle()
  }

  /** Create the record for a fresh, abortable acquisition attempt. */
  private createSubsetAcquisitionRecord(
    demand: SubsetDemand,
  ): SubsetAcquisitionRecord {
    // Eager sync never passes subset options to an adapter, so an abortable
    // acquisition would only allocate a controller and an AbortError.
    if (this.collection.config.syncMode !== `on-demand`) {
      return {
        options: demand.requestOptions,
        syncRunGeneration: this.collection._sync.getSyncRunGeneration(),
      }
    }
    const abortController = new AbortController()
    const requestSignal = demand.requestOptions.signal
    let removeRequestAbortListener: (() => void) | undefined

    if (requestSignal?.aborted) {
      abortController.abort(requestSignal.reason)
    } else if (requestSignal) {
      const abort = () => abortController.abort(requestSignal.reason)
      requestSignal.addEventListener(`abort`, abort, { once: true })
      removeRequestAbortListener = () =>
        requestSignal.removeEventListener(`abort`, abort)
    }

    return {
      options: {
        ...demand.requestOptions,
        signal: abortController.signal,
      },
      syncRunGeneration: this.collection._sync.getSyncRunGeneration(),
      abortController,
      removeRequestAbortListener,
    }
  }

  /**
   * Retire an acquisition before user code; failed cleanup is not retryable.
   * An `unloadSubset` that throws is an adapter bug: it is reported uncaught
   * and fails no load, replay, or caller.
   */
  private releaseAcquisition(acquisition: SubsetAcquisitionRecord): void {
    if (acquisition.releaseAttempted) return
    acquisition.releaseAttempted = true
    try {
      acquisition.abortController?.abort()
      if (this.isSyncRunGenerationCurrent(acquisition.syncRunGeneration)) {
        this.collection._sync.unloadSubset(acquisition.options)
      }
    } catch (error) {
      reportUncaught(error)
    } finally {
      acquisition.removeRequestAbortListener?.()
    }
  }

  /** Start and retain the first acquisition for one logical subset demand. */
  private startSubsetDemand(requestOptions: LoadSubsetOptions): {
    demand: SubsetDemand
    result: LoadSubsetRequestResult
    started: boolean
  } {
    const demand: SubsetDemand = {
      requestOptions,
      acquisition: {
        options: requestOptions,
        syncRunGeneration: this.collection._sync.getSyncRunGeneration(),
      },
      acquisitionState: `starting`,
    }
    // A demand that starts during an open replay joins it, parked or not (law
    // 9: a new demand joins the open replay).
    const replayState = this.truncateReplayState
    const replayAttempt = replayState?.currentAttempt
    if (
      this.collection.status === `cleaned-up` ||
      // A deferring subscription starts no acquisition attempt until it resumes.
      (this.defersAcquisition &&
        this.collection.config.syncMode === `on-demand`) ||
      // Ready/error callbacks can run before sync returns its loader. Idle
      // deferred starts still acquire through the sync manager's queue.
      (this.collection.config.syncMode === `on-demand` &&
        (isFailedStatus(this.collection.status) ||
          (this.collection.status !== `idle` &&
            this.collection._sync.syncLoadSubsetFn === null)))
    ) {
      demand.acquisitionState = `detached`
      this.subsetDemands.push(demand)
      const initialResult = createDeferred<void>()
      demand.initialResult = initialResult
      const abort = () =>
        initialResult.reject(new LoadSubsetOperationAbortedError())
      requestOptions.signal?.addEventListener(`abort`, abort, { once: true })
      const finish = () => {
        requestOptions.signal?.removeEventListener(`abort`, abort)
        demand.initialResult = undefined
      }
      void initialResult.promise.then(finish, finish)
      if (replayState && replayAttempt)
        demand.parkedParticipant = this.trackTruncateReplayParticipant(
          replayState,
          replayAttempt,
          demand,
          initialResult.promise,
          requestOptions.signal,
        )
      return { demand, result: initialResult.promise, started: false }
    }
    const acquisition = this.createSubsetAcquisitionRecord(demand)
    demand.acquisition = acquisition
    const syncRunGeneration = this.collection._sync.getSyncRunGeneration()
    // Reentrant release must see the exact acquisition before adapter work
    // starts. A load that throws is a rejection: it keeps its owner.
    this.subsetDemands.push(demand)
    const result = this.collection._sync.loadSubset(acquisition.options)

    if (!this.isSyncRunGenerationCurrent(syncRunGeneration)) {
      const demandIndex = this.subsetDemands.indexOf(demand)
      if (demandIndex !== -1) this.subsetDemands.splice(demandIndex, 1)
      cancelAcquisition(acquisition)
      return { demand, result, started: true }
    }

    demand.acquisitionState = `active`
    if (!this.subsetDemands.includes(demand)) {
      this.releaseAcquisition(acquisition)
      return { demand, result, started: true }
    }

    if (replayState && replayAttempt) {
      this.trackTruncateReplayParticipant(
        replayState,
        replayAttempt,
        demand,
        result,
        acquisition.options.signal,
      )
    }
    return { demand, result, started: true }
  }

  /** Re-check ownership after adapter and event callbacks that may reenter. */
  private isDemandActive(demand: SubsetDemand): boolean {
    return !this.unsubscribed && this.subsetDemands.includes(demand)
  }

  private recordLoadSubsetError(
    options: LoadSubsetOptions,
    error: unknown,
    reportAborted = false,
  ): Error {
    const normalized = normalizeError(error)
    // Aborted subset requests are obsolete demand, not load failures. The
    // request may reject after its route has already been released.
    if (options.signal?.aborted && !reportAborted) return normalized

    this._lastError = normalized
    this.emitInner(`loadSubset:error`, {
      type: `loadSubset:error`,
      subscription: this,
      options,
      error: normalized,
    })
    return normalized
  }

  emitEvents(changes: Array<ChangeMessage<any, any>>): boolean {
    if (this.unsubscribed) return false
    const newChanges = this.filterAndFlipChanges(changes)

    // Reconciliation can reduce a source delta to no visible change. Do not
    // wake subscribers for an empty semantic batch.
    if (changes.length > 0 && newChanges.length === 0) return false

    // A direct subscriber sees the replacement as one batch, not a flash of
    // missing content. Delegated publication keeps its private D2 contributions.
    if (this.bufferPrivately(newChanges)) return false
    return this.filteredCallback(newChanges)
  }

  /** Keep direct snapshot reads private while an authoritative replay is open. */
  private publishSnapshot(changes: Array<ChangeMessage<any, any>>): void {
    if (!this.bufferPrivately(changes)) this.callback(changes)
  }

  /**
   * Sends the snapshot to the callback.
   * Returns a boolean indicating if it succeeded.
   * It can only fail if there is no index to fulfill the request
   * and the optimizedOnly option is set to true,
   * or, the entire state was already loaded or the request was cancelled.
   */
  requestSnapshot(opts?: RequestSnapshotOptions): boolean {
    // Cancel before acquiring ownership or publishing a local snapshot.
    if (this.unsubscribed || opts?.signal?.aborted) return false
    if (this.loadedInitialState) {
      // Subscription was deoptimized so we already sent the entire initial state
      return false
    }

    const stateOpts: RequestSnapshotOptions = {
      where: this.options.whereExpression,
      optimizedOnly: opts?.optimizedOnly ?? false,
    }

    if (opts) {
      if (`where` in opts) {
        const snapshotWhereExp = opts.where
        if (stateOpts.where) {
          // Combine the two where expressions
          const subWhereExp = stateOpts.where
          const combinedWhereExp = and(subWhereExp, snapshotWhereExp)
          stateOpts.where = combinedWhereExp
        } else {
          stateOpts.where = snapshotWhereExp
        }
      }
      // The ordered loader asks for a bounded prefix only when the source
      // holds every row and an index serves its single order term. Any other
      // prefix resends every matching row: an on-demand repair relies on them.
      if (opts.boundedPrefix) {
        stateOpts.orderBy = opts.orderBy
        stateOpts.limit = opts.limit
      }
    } else {
      // No options provided so it's loading the entire initial state
      this.loadedInitialState = true
    }

    // Request the sync layer to load more data
    // don't await it, we will load the data into the collection when it comes in
    const loadOptions: LoadSubsetOptions = {
      ...(opts?.refetch ? { refetch: true } : {}),
      where: stateOpts.where,
      signal: opts?.signal,
      subscription: this,
      // Include orderBy and limit if provided so sync layer can optimize the query
      orderBy: opts?.orderBy,
      // A local optimistic row can hide one source row from a bounded prefix.
      limit:
        opts?.limit === undefined
          ? undefined
          : opts.limit + this.collection._optimisticOverlaySize(),
    }
    // The loader can reenter releaseSnapshot before startSubsetDemand returns.
    if (opts?.where) this.requestedSubsetWhere.set(loadOptions, opts.where)

    const {
      demand,
      result: syncResult,
      started,
    } = this.startSubsetDemand(loadOptions)
    if (!this.isDemandActive(demand)) return false

    // Report the result synchronously, including a wait for an unavailable loader.
    opts?.onLoadSubsetResult?.(
      syncResult,
      demand.acquisition.options,
      (primaryFailure) => this.releaseDemand(demand, primaryFailure),
    )
    if (!this.isDemandActive(demand)) return false

    if (started) {
      this.observeLoadSubsetResult(
        syncResult,
        demand,
        demand.acquisition.options,
        opts?.trackLoadSubsetPromise ?? true,
      )
    }
    if (!this.isDemandActive(demand)) return false

    // Also load data immediately from the collection
    let snapshot: Array<ChangeMessage<any, any>> | void
    if (opts?.onUnoptimized) {
      snapshot = this.collection.currentStateAsChanges({
        ...stateOpts,
        optimizedOnly: true,
      })
      if (snapshot === undefined) {
        opts.onUnoptimized()
        // The callback can retire this demand before the fallback read.
        if (!this.isDemandActive(demand)) return false
        snapshot = this.collection.currentStateAsChanges({
          ...stateOpts,
          optimizedOnly: false,
        })
      }
    } else {
      snapshot = this.collection.currentStateAsChanges(stateOpts)
    }
    // Snapshot evaluation may call user code that retires this demand.
    if (!this.isDemandActive(demand)) return false

    if (snapshot === undefined) {
      // Couldn't load from indexes
      return false
    }

    // Skip known rows, except retained rows from an abandoned replay: a new
    // snapshot must reconcile those with the source, not suppress their update.
    const knownRows =
      this.truncateReplayState?.privateRows ?? this.publishedRows
    const filteredSnapshot = snapshot.filter(
      (change) =>
        (!this.isBufferingForTruncate &&
          this.stalePublishedRows.has(change.key)) ||
        (!this.sentKeys.has(change.key) && !knownRows.has(change.key)),
    )

    // Reserve the selected keys before publication. A truncate replay can keep
    // this snapshot private, so callback tracking cannot reserve it yet.
    for (const change of filteredSnapshot) {
      this.sentKeys.add(change.key)
    }

    this.snapshotSent = true
    this.publishSnapshot(
      this.isBufferingForTruncate
        ? filteredSnapshot
        : this.reconcileStalePublishedChanges(filteredSnapshot),
    )
    return true
  }

  /**
   * Release exact subset requests while keeping the subscription alive. The
   * releases are one demand change (law 9): an open replay is judged once,
   * after all of them.
   */
  releaseSnapshot(...wheres: Array<BasicExpression<boolean>>): void {
    const demands: Array<SubsetDemand> = []
    for (const where of wheres) {
      const demand = this.subsetDemands.find(
        (candidate) =>
          !demands.includes(candidate) && this.requestedWhere(candidate, where),
      )
      if (demand) demands.push(demand)
    }
    if (demands.length > 0) this.releaseDemands(demands)
  }

  /**
   * @internal The snapshot for `where` replaces those for `replaced` for
   * their owner, which releases them once it applies. Until then they keep
   * their older work in the replay.
   */
  replaceSnapshots(
    where: BasicExpression<boolean>,
    replaced: Array<BasicExpression<boolean>>,
  ): void {
    const replacement = this.subsetDemands.find((demand) =>
      this.requestedWhere(demand, where),
    )
    if (!replacement) return
    replacement.replaces = this.subsetDemands.filter((demand) =>
      replaced.some((other) => this.requestedWhere(demand, other)),
    )
  }

  private requestedWhere(
    demand: SubsetDemand,
    where: BasicExpression<boolean>,
  ): boolean {
    return (
      demand.requestOptions.where === where ||
      this.requestedSubsetWhere.get(demand.requestOptions) === where
    )
  }

  private releaseDemand(
    demand: SubsetDemand,
    primaryFailure?: { error: unknown },
  ): void {
    try {
      if (primaryFailure)
        this.recordLoadSubsetError(
          demand.acquisition.options,
          primaryFailure.error,
          true,
        )
    } finally {
      // The failed request remains the public error, even if cleanup also fails.
      if (this.subsetDemands.includes(demand)) this.releaseDemands([demand])
    }
  }

  /** Retire held demands as one step, then judge an open replay once. */
  private releaseDemands(demands: Array<SubsetDemand>): void {
    const replayState = this.truncateReplayState
    const releaseCallbacks: Array<() => void> = []
    for (const demand of demands) {
      const acquisition = demand.acquisition
      this.subsetDemands.splice(this.subsetDemands.indexOf(demand), 1)
      demand.initialResult?.reject(new LoadSubsetOperationAbortedError())
      releaseCallbacks.push(() => this.removeTruncateReplayParticipant(demand))
      // Adapter release is a supported reentrancy boundary. A demand started
      // from unload joins this replacement before completion.
      if (demand.acquisitionState === `active`)
        releaseCallbacks.push(() => this.releaseAcquisition(acquisition))
    }
    runAllCallbacks([
      ...releaseCallbacks,
      () => this.retireEmptyReplay(),
      () => {
        if (replayState) this.checkTruncateReplayComplete(replayState)
      },
      // Ready follows replacement publication, never the delete half of it.
      ...demands.map(
        (demand) => () => this.stopDemandStatusParticipants(demand),
      ),
    ])
  }

  /**
   * A replay with no remaining logical demand cannot establish more rows. One
   * that has not failed publishes the rows it holds, so the subscriber does
   * not keep values the source has replaced or deleted. A failed replay keeps
   * the last public snapshot until an authoritative replay succeeds.
   */
  private retireEmptyReplay(): void {
    const replayState = this.truncateReplayState
    if (this.subsetDemands.length !== 0 || !replayState) return
    if (replayState.privateRows && !replayState.failed) {
      this.flushTruncateReplay(replayState)
      return
    }
    if (this.awaitsSiblingReplays(replayState, () => this.retireEmptyReplay()))
      return
    this.discardTruncateReplay()
    this.stalePublishedRows = new Map(this.publishedRows)
    this.restorePublishedSnapshotTracking()
    this.options.truncateReplayPublication?.succeed()
  }

  /** Read source-derived ordered rows without starting demand. */
  readOrderedSnapshot(
    options: LoadSubsetOptions,
  ): Array<ChangeMessage<Record<string, unknown>, string | number>> {
    const predicates = [
      this.options.whereExpression,
      options.where,
      options.cursor?.whereFrom,
    ].filter((where) => where !== undefined)
    const snapshot = this.collection.currentStateAsChanges({
      orderBy: options.orderBy,
      limit: options.limit,
      where:
        predicates.length > 0
          ? predicates.reduce((left, right) => and(left, right))
          : undefined,
    })
    // Optimistic rows can fill the visible window but cannot establish a
    // provider offset or cursor boundary. Excluding their keys is conservative
    // when an optimistic update hides a source row in the local ordered view.
    return Array.isArray(snapshot)
      ? snapshot.filter(
          ({ key }) => !this.collection._hasOptimisticOverlay(key),
        )
      : []
  }

  /**
   * Sends a snapshot that fulfills the `where` clause and all rows are bigger or equal to the cursor.
   * Requires a range index to be set with `setOrderByIndex` prior to calling this method.
   * It uses that range index to load the items in the order of the index.
   *
   * Cursor requests use one minValue for the leading order term. For a
   * multi-column order, a separate equality request loads the complete
   * leading-value tie before local ordering applies the trailing terms.
   *
   * Note 1: it may load more rows than the provided LIMIT because it loads all values equal to the first cursor value + limit values greater.
   *         This is needed to ensure that it does not accidentally skip duplicate values when the limit falls in the middle of some duplicated values.
   * Note 2: it does not send keys that have already been sent before.
   */
  requestLimitedSnapshot({
    refetch,
    orderBy,
    limit,
    minValues,
    offset,
    trackLoadSubsetPromise: shouldTrackLoadSubsetPromise = true,
    onLoadSubsetResult,
  }: RequestLimitedSnapshotOptions) {
    if (this.unsubscribed) return
    if (!limit)
      throw new Error(
        devBuild() && process.env.NODE_ENV !== `production`
          ? `limit is required`
          : codedMessage(96),
      )

    if (!this.orderByIndex) {
      throw new Error(
        devBuild() && process.env.NODE_ENV !== `production`
          ? `Ordered snapshot was requested but no index was found. You have to call setOrderByIndex before requesting an ordered snapshot.`
          : codedMessage(97),
      )
    }

    // Validate cursor input before local delivery changes sent keys or calls user code.
    const whereFromCursor = minValues
      ? buildCursor(orderBy, minValues)
      : undefined

    // Check if minValues has a first element (regardless of its value)
    // This distinguishes between "no min value provided" vs "min value is undefined"
    const hasMinValue = minValues !== undefined && minValues.length > 0
    // Derive first column value from minValues (used for local index operations)
    const minValue = minValues?.[0]
    // Cast for index operations (index expects string | number)
    const minValueForIndex = minValue as string | number | undefined

    const index = this.orderByIndex
    const where = this.options.whereExpression
    const whereFilterFn = where
      ? createFilterFunctionFromExpression(where)
      : undefined

    const filterFn = (key: string | number | undefined): boolean => {
      if (key !== undefined && this.sentKeys.has(key)) {
        return false
      }

      const value = this.collection.get(key)
      if (value === undefined) {
        return false
      }

      return whereFilterFn?.(value) ?? true
    }

    let biggestObservedValue = minValueForIndex
    const changes: Array<ChangeMessage<any, string | number>> = []

    // If we have a minValue we need to handle the case
    // where there might be duplicate values equal to minValue that we need to include
    // because we can have data like this: [1, 2, 3, 3, 3, 4, 5]
    // so if minValue is 3 then the previous snapshot may not have included all 3s
    // e.g. if it was offset 0 and limit 3 it would only have loaded the first 3
    //      so we load all rows equal to minValue first, to be sure we don't skip any duplicate values
    let keys: Array<string | number> = []
    if (hasMinValue) {
      // First, get all items with the same FIRST COLUMN value as minValue
      // This provides wide bounds for the local index
      const { expression } = orderBy[0]!
      const allRowsWithMinValue = this.collection.currentStateAsChanges({
        where: eq(expression, new Value(minValueForIndex)),
      })

      if (allRowsWithMinValue) {
        const keysWithMinValue = allRowsWithMinValue
          .map((change) => change.key)
          .filter(filterFn)

        // Add items with the minValue first
        keys.push(...keysWithMinValue)

        // Then get items greater than minValue
        const keysGreaterThanMin = index.take(
          limit - keys.length,
          minValueForIndex!,
          filterFn,
        )
        keys.push(...keysGreaterThanMin)
      } else {
        keys = index.take(limit, minValueForIndex!, filterFn)
      }
    } else {
      // No min value provided, start from the beginning
      keys = index.takeFromStart(limit, filterFn)
    }

    const valuesNeeded = () => Math.max(limit - changes.length, 0)
    const collectionExhausted = () => keys.length === 0

    // Create a value extractor for the orderBy field to properly track the biggest indexed value
    const orderByExpression = orderBy[0]!.expression
    const valueExtractor =
      orderByExpression.type === `ref`
        ? compileExpression(
            new PropRef(getPropRefPropertyPath(orderByExpression)),
            true,
          )
        : null

    while (valuesNeeded() > 0 && !collectionExhausted()) {
      for (const key of keys) {
        const value = this.collection.get(key)!
        changes.push({
          type: `insert`,
          key,
          value,
        })
        // Extract the indexed value (e.g., salary) from the row, not the full row
        // This is needed for index.take() to work correctly with the BTree comparator
        biggestObservedValue = valueExtractor ? valueExtractor(value) : value
      }

      keys = index.take(valuesNeeded(), biggestObservedValue!, filterFn)
    }

    // Reserve rows before publication. A direct truncate replay keeps them in
    // the private replacement, but repeated ordered snapshots must continue.
    for (const change of changes) {
      this.sentKeys.add(change.key)
    }

    this.publishSnapshot(changes)
    // A subscriber callback can synchronously tear down this subscription.
    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
    if (this.unsubscribed) return

    // Build cursor expressions for sync layer loadSubset
    // The cursor expressions are separate from the main where clause
    // so the sync layer can choose cursor-based or offset-based pagination.
    // A local key is not an authoritative provider cursor: sparse source rows
    // and callback reentry can leave lower-ranked provider rows unseen.
    let cursorExpressions:
      | {
          whereFrom: BasicExpression<boolean>
          whereCurrent: BasicExpression<boolean>
        }
      | undefined

    if (whereFromCursor && minValues) {
      const whereCurrentCursor = buildCursorCurrent(orderBy, minValues)
      if (whereCurrentCursor) {
        cursorExpressions = {
          whereFrom: whereFromCursor,
          whereCurrent: whereCurrentCursor,
        }
      }
    }

    // A cursor value does not bound how many provider rows precede it. When an
    // adapter ignores cursor expressions and no offset was supplied, only a
    // full-source request can reach an arbitrary cursor value. Otherwise,
    // selected keys and optimistic overlays can each hide source rows from a
    // local page. Their cardinalities bound how many extra provider rows the
    // request may need to reach `limit` visible rows.
    const overlaySize = this.collection._optimisticOverlaySize()
    const providerLimit =
      offset !== undefined
        ? limit + overlaySize
        : hasMinValue
          ? undefined
          : limit + this.sentKeys.size + overlaySize

    // Request the sync layer to load more data
    // don't await it, we will load the data into the collection when it comes in
    // Note: `where` does NOT include cursor expressions - they are passed separately
    // The sync layer can choose to use cursor-based or offset-based pagination
    const loadOptions: LoadSubsetOptions = {
      ...(refetch ? { refetch: true } : {}),
      where, // Main filter only, no cursor
      limit: providerLimit,
      orderBy,
      cursor: cursorExpressions, // Cursor expressions passed separately
      offset: offset ?? 0,
      subscription: this,
    }

    const {
      demand,
      result: syncResult,
      started,
    } = this.startSubsetDemand(loadOptions)
    if (!this.isDemandActive(demand)) return

    // Report the result synchronously, including a wait for an unavailable loader.
    onLoadSubsetResult?.(
      syncResult,
      demand.acquisition.options,
      (primaryFailure) => this.releaseDemand(demand, primaryFailure),
    )
    if (!this.isDemandActive(demand)) return
    if (started) {
      this.observeLoadSubsetResult(
        syncResult,
        demand,
        demand.acquisition.options,
        shouldTrackLoadSubsetPromise,
      )
    }
    if (!this.isDemandActive(demand)) return
  }

  // TODO: also add similar test but that checks that it can also load it from the collection's loadSubset function
  //       and that that also works properly (i.e. does not skip duplicate values)

  /**
   * Filters and flips changes for keys that have not been sent yet.
   * Deletes are filtered out for keys that have not been sent yet.
   * Updates are flipped into inserts for keys that have not been sent yet.
   * Duplicate inserts are filtered out to prevent D2 multiplicity > 1.
   */
  private filterAndFlipChanges(changes: Array<ChangeMessage<any, any>>) {
    changes = this.reconcileStalePublishedChanges(changes)

    if (this.loadedInitialState || this.skipFiltering) {
      // We loaded the entire initial state or filtering is explicitly skipped
      // so no need to filter or flip changes
      return changes
    }

    // A former full-state subscription is reset before truncate deletes arrive.
    // Pass even unreserved deletes into its private replacement so stale
    // published rows do not survive the replay.
    const skipDeleteFilter = this.isBufferingForTruncate

    // sentKeys covers snapshot selections and callback rows. Keys inserted
    // earlier in this batch are tracked locally, so a row the where clause
    // drops cannot advance pagination or later look like a duplicate insert.
    const insertedInBatch = new Set<string | number>()
    const newChanges = []
    for (const change of changes) {
      let newChange = change
      const keyInSentKeys =
        this.sentKeys.has(change.key) || insertedInBatch.has(change.key)

      if (!keyInSentKeys) {
        if (change.type === `update`) {
          newChange = { ...change, type: `insert`, previousValue: undefined }
          insertedInBatch.add(change.key)
        } else if (change.type === `delete`) {
          // Filter out deletes for keys that have not been sent,
          // UNLESS we're buffering for truncate (where all deletes should pass through)
          if (!skipDeleteFilter) {
            continue
          }
        } else {
          insertedInBatch.add(change.key)
        }
      } else {
        // Key was already sent - handle based on change type
        if (change.type === `insert`) {
          // Filter out duplicate inserts - the key was already inserted.
          // This prevents D2 multiplicity from going above 1, which would
          // cause deletes to not properly remove items (multiplicity would
          // go from 2 to 1 instead of 1 to 0).
          continue
        } else if (change.type === `delete`) {
          // Remove from sentKeys so future inserts for this key are allowed
          // (e.g., after truncate + reinsert)
          this.sentKeys.delete(change.key)
          insertedInBatch.delete(change.key)
        }
      }
      newChanges.push(newChange)
    }
    return newChanges
  }

  /**
   * After a failed replay, the source collection is empty but subscribers still
   * hold the last good publication. Reconcile the first later source delta for
   * each retained key against that publication instead of treating it as a
   * duplicate insert.
   */
  private reconcileStalePublishedChanges(
    changes: Array<ChangeMessage<any, any>>,
  ): Array<ChangeMessage<any, any>> {
    if (this.stalePublishedRows.size === 0) return changes

    const reconciled: Array<ChangeMessage<any, any>> = []
    for (const change of changes) {
      const previous = this.stalePublishedRows.get(change.key)
      if (previous === undefined) {
        reconciled.push(change)
        continue
      }

      this.stalePublishedRows.delete(change.key)
      if (change.type === `delete`) {
        reconciled.push({
          ...change,
          value: previous,
          previousValue: undefined,
        })
      } else if (!deepEquals(previous, change.value)) {
        reconciled.push({
          ...change,
          type: `update`,
          previousValue: previous,
        })
      }
    }
    // Cleanup discards rows without publishing deletes. Eager sources publish
    // their installed state; subset sources must first finish reacquisition.
    if (
      this.collection.config.syncMode !== `on-demand` &&
      !this.isBufferingForTruncate
    ) {
      for (const [key, value] of this.stalePublishedRows) {
        if (this.collection.has(key)) continue
        this.stalePublishedRows.delete(key)
        reconciled.push({ type: `delete`, key, value })
      }
    }
    return reconciled
  }

  private trackPublishedRows(
    changes: Array<ChangeMessage<any, string | number>>,
  ): void {
    for (const change of changes) {
      if (change.type === `delete`) {
        this.publishedRows.delete(change.key)
      } else {
        this.publishedRows.set(change.key, change.value)
      }
    }
  }

  private trackSentKeys(changes: Array<ChangeMessage<any, string | number>>) {
    for (const change of changes) {
      if (change.type === `delete`) {
        this.sentKeys.delete(change.key)
      } else {
        this.sentKeys.add(change.key)
      }
    }
  }

  /**
   * Mark that the subscription should not filter any changes.
   * This is used when includeInitialState is explicitly set to false,
   * meaning the caller doesn't want initial state but does want ALL future changes.
   */
  markAllStateAsSeen() {
    this.skipFiltering = true
  }

  unsubscribe() {
    if (this.unsubscribed) return
    this.unsubscribed = true
    // Stop any status listener set already being iterated. Clearing the
    // emitter's map cannot invalidate that captured Set by itself.
    this.statusRevision++
    const sourceListenerCleanups = [
      this.truncateCleanup,
      this.collectionCleanup,
      this.collectionRestartCleanup,
    ]
    this.truncateCleanup = undefined
    this.collectionCleanup = undefined
    this.collectionRestartCleanup = undefined

    runAllCallbacks([
      ...sourceListenerCleanups.map((cleanup) => () => cleanup?.()),
      () => {
        // Stop any buffered replay from publishing after unsubscription.
        this.discardTruncateReplay()
        this.stalePublishedRows.clear()

        // Retire every owner before an unload can reenter teardown.
        const acquisitions = this.subsetDemands
          .filter((demand) => demand.acquisitionState === `active`)
          .map((demand) => demand.acquisition)
        for (const demand of this.subsetDemands) {
          demand.initialResult?.reject(new LoadSubsetOperationAbortedError())
          this.stopDemandStatusParticipants(demand)
          if (demand.acquisitionState === `starting`) {
            cancelAcquisition(demand.acquisition)
          }
        }
        this.subsetDemands = []
        for (const acquisition of acquisitions) {
          this.releaseAcquisition(acquisition)
        }
      },
      () =>
        this.emitInner(`unsubscribed`, {
          type: `unsubscribed`,
          subscription: this,
        }),
      // Clear all event listeners to prevent memory leaks
      () => this.clearListeners(),
    ])
  }
}