Ctrl K
ComunidadGuías…

packages/client/src/shared-events.ts

Rama dev · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
export * as SharedEvents from "./shared-events.js"

/** The members of event union `A` whose type matches one of `Type`, including `rpc.*` events. */
export type SelectedEvent<A extends { readonly type: string }, Type extends string> = A extends unknown
  ? [Extract<Type, A["type"]>] extends [never]
    ? never
    : A
  : never

export type SubscribeOptions<Type extends string = string> = {
  readonly types?: ReadonlyArray<Type>
  readonly signal?: AbortSignal
  /** Reports transport activity on the shared stream, including keepalive frames that carry no event. */
  readonly onActivity?: () => void
}

export function make<A extends { readonly type: string }>(
  connect: (signal: AbortSignal, onActivity: () => void) => AsyncIterable<A>,
) {
  type Completion = { readonly error: unknown } | Record<string, never>
  type Subscriber = {
    push: (value: A) => void
    finish: (completion: Completion) => void
    activity?: () => void
  }
  type Connection = {
    controller: AbortController
    subscribers: Set<Subscriber>
    connected?: A
  }

  let current: Connection | undefined
  const capacity = 4_096

  function stop(connection: Connection) {
    connection.connected = undefined
    connection.controller.abort()
    if (current === connection) current = undefined
  }

  async function run(connection: Connection) {
    let iterator: AsyncIterator<A> | undefined
    let completion: Completion = {}
    try {
      if (connection.controller.signal.aborted) return
      iterator = connect(connection.controller.signal, () => {
        connection.subscribers.forEach((subscriber) => subscriber.activity?.())
      })[Symbol.asyncIterator]()
      while (!connection.controller.signal.aborted) {
        const item = await iterator.next()
        if (item.done || connection.controller.signal.aborted) break
        if (item.value.type === "server.connected") connection.connected = item.value
        connection.subscribers.forEach((subscriber) => subscriber.push(item.value))
      }
    } catch (error) {
      completion = { error }
    } finally {
      stop(connection)
      try {
        await iterator?.return?.()
      } catch (error) {
        if (!("error" in completion)) completion = { error }
      }
      connection.subscribers.forEach((subscriber) => subscriber.finish(completion))
    }
  }

  return {
    subscribe<Type extends Exclude<A["type"], "server.connected"> = Exclude<A["type"], "server.connected">>(
      options?: SubscribeOptions<Type>,
    ): AsyncIterable<SelectedEvent<A, Type | "server.connected">> {
      return {
        [Symbol.asyncIterator]() {
          // server.connected always comes through so a filtered subscriber still sees reconnects and can resync.
          type Item = A & SelectedEvent<A, Type | "server.connected">
          const pending: ReturnType<typeof Promise.withResolvers<IteratorResult<Item>>>[] = []
          const queued: Item[] = []
          let started = false
          let completion: Completion | undefined
          let connection: Connection | undefined
          const types = options?.types?.length ? new Set<string>(options.types) : undefined
          const selected = (value: A): value is Item =>
            !types || value.type === "server.connected" || types.has(value.type)

          function finish(result: Completion, discard = true) {
            completion = result
            if (discard) queued.length = 0
            options?.signal?.removeEventListener("abort", abort)
            if (connection?.subscribers.delete(subscriber) && !connection.subscribers.size) stop(connection)
            pending.splice(0).forEach((request) => {
              if ("error" in result) request.reject(result.error)
              else request.resolve({ done: true, value: undefined })
            })
          }

          function abort() {
            finish({})
          }

          const subscriber: Subscriber = {
            activity: options?.onActivity,
            finish(result) {
              finish(result, false)
            },
            push(value) {
              if (completion) return
              if (!selected(value)) return
              const request = pending.shift()
              if (request) {
                request.resolve({ done: false, value })
                return
              }
              if (queued.length === capacity) {
                finish({ error: new Error(`Event subscriber exceeded its ${capacity}-event capacity`) })
                return
              }
              queued.push(value)
            },
          }

          function start() {
            if (completion) return
            const fresh = !current
            connection = current ?? {
              controller: new AbortController(),
              subscribers: new Set<Subscriber>(),
            }
            current = connection
            connection.subscribers.add(subscriber)
            if (connection.connected) void subscriber.push(connection.connected)
            if (fresh) void run(connection)
          }

          return {
            next(): Promise<IteratorResult<Item>> {
              const value = queued.shift()
              if (value) return Promise.resolve({ done: false, value })
              if (completion) {
                if ("error" in completion) return Promise.reject(completion.error)
                return Promise.resolve({ done: true, value: undefined })
              }
              if (options?.signal?.aborted) {
                abort()
                return Promise.resolve({ done: true, value: undefined })
              }
              const request = Promise.withResolvers<IteratorResult<Item>>()
              pending.push(request)
              if (!started) {
                started = true
                options?.signal?.addEventListener("abort", abort, { once: true })
                start()
              }
              return request.promise
            },
            return(): Promise<IteratorResult<Item>> {
              finish({})
              return Promise.resolve({ done: true, value: undefined })
            },
          }
        },
      }
    },
  }
}