@atproto/tap

中文文档
0.4.19发布于 2026/11/06MITTypeScriptES Module构建来源已验证近 30 天下载 7780

atproto tap client

原始使用文档
文档目录16 节
  1. Quick Start
  2. Running Tap
  3. API
  4. Tap
  5. TapChannel
  6. SimpleIndexer
  7. LexIndexer
  8. TapHandler
  9. Event Types
  10. RecordEvent
  11. IdentityEvent
  12. Webhook Mode
  13. Utilities
  14. Auth helpers
  15. Event parsing
  16. License

@atproto/tap

TypeScript client library for Tap, a sync utility for the AT Protocol network.

Tap handles firehose connections, cryptographic verification, backfill, and filtering. This client library lets you connect to a Tap instance and receive simple JSON events for the repos you care about.

NPM Github CI Status

Quick Start

npm install @atproto/tap
import { Tap, SimpleIndexer } from '@atproto/tap'

const tap = new Tap('http://localhost:2480', { adminPassword: 'secret' })

const indexer = new SimpleIndexer()

indexer.identity(async (evt) => {
  console.log(`${evt.did} updated identity: ${evt.handle} (${evt.status})`)
})

indexer.record(async (evt) => {
  const uri = `at://${evt.did}/${evt.collection}/${evt.rkey}`
  if (evt.action === 'create' || evt.action === 'update') {
    console.log(`${evt.action}: ${uri}`)
  } else {
    console.log(`deleted: ${uri}`)
  }
})

indexer.error((err) => console.error(err))

const channel = tap.channel(indexer)
channel.start()

await tap.addRepos(['did:plc:ewvi7nxzyoun6zhxrhs64oiz'])

// On shutdown
await channel.destroy()

Running Tap

See the Tap README for details getting Tap up and running. Your app can communicate with it either locally or over the internet.

This library is intended to be used with Tap running in the default mode of "WebScoket with acks". In this mode, Tap provides:

  • At-least-once delivery: Events may be redelivered if the connection drops before an ack is received
  • Per-repo ordering: Events for the same repo are delivered in order
  • Backfill: When you add a repo, historical events are delivered before live events

API

Tap

The main client for interacting with a Tap server.

const tap = new Tap(url: string, config?: TapConfig)

Config options:

  • adminPassword?: string - Password for Basic auth (required if Tap server has auth enabled)

Methods:

  • channel(handler: TapHandler, opts?: TapWebsocketOptions): TapChannel - Create a WebSocket channel to receive events
  • addRepos(dids: string[]): Promise<void> - Add repos to track (triggers backfill)
  • removeRepos(dids: string[]): Promise<void> - Stop tracking repos
  • resolveDid(did: string): Promise<DidDocument | null> - Resolve a DID to its DID document
  • getRepoInfo(did: string): Promise<RepoInfo> - Get info about a tracked repo

TapChannel

WebSocket connection for receiving events. Created via tap.channel().

const channel = tap.channel(handler, opts?)

Methods:

  • start(): Promise<void> - Start receiving events. Returns a promise that resolves when the connection is destroyed or errors.
  • destroy(): Promise<void> - Close the connection

The channel automatically handles reconnection and keepalive. Events are automatically acknowledged after your handler completes successfully.

SimpleIndexer

A convenience class for handling events by type. Passed into tap.channel() when opening a channel with Tap.

const indexer = new SimpleIndexer()

indexer.identity(async (evt: IdentityEvent) => { ... })
indexer.record(async (evt: RecordEvent) => { ... })
indexer.error((err: Error) => { ... })

If no error handler is registered, errors will throw as unhandled exceptions.

LexIndexer

A typed indexer that uses @atproto/lex schemas for type-safe event handling. Register handlers for specific record types and actions:

import { LexIndexer } from '@atproto/tap'
import * as com from './lexicons/com'

const indexer = new LexIndexer()

// Handle creates for a specific record type
indexer.create(com.example.post, async (evt) => {
  // evt.record is fully typed as com.example.post.Main
  console.log(`New post: ${evt.record.text}`)
})

// Handle updates
indexer.update(com.example.post, async (evt) => {
  console.log(`Updated post: ${evt.record.text}`)
})

// Handle deletes (no record on delete events)
indexer.delete(com.example.post, async (evt) => {
  console.log(`Deleted: at://${evt.did}/${evt.collection}/${evt.rkey}`)
})

// Handle both creates and updates with put()
indexer.put(com.example.like, async (evt) => {
  console.log(`Like ${evt.action}: ${evt.record.subject.uri}`)
})

// Fallback for unhandled record types/actions
indexer.other(async (evt) => {
  console.log(`Unhandled: ${evt.action}, ${evt.collection}`)
})

// Identity and error handlers
indexer.identity(async (evt) => { ... })
indexer.error((err) => { ... })

const channel = tap.channel(indexer)

Records are validated against their schemas before handlers are called. If validation fails, an error is thrown which will be picked up through the error handler..

Duplicate handler registration throws an error, including conflicts between put() and create()/update() for the same schema.

TapHandler

You can create your own custom handler by creating a class that implements the TapHandler interface:

interface TapHandler {
  onEvent: (evt: TapEvent, opts: HandlerOpts) => void | Promise<void>
  onError: (err: Error) => void
}

interface HandlerOpts {
  signal: AbortSignal
  ack: () => Promise<void>
}

When implementing a custom handler, be sure to call ack() when you're done processing the event.

Event Types

RecordEvent

type RecordEvent = {
  id: number
  type: 'record'
  action: 'create' | 'update' | 'delete'
  did: string
  rev: string
  collection: string
  rkey: string
  record?: Record<string, unknown> // present for create/update
  cid?: string // present for create/update
  live: boolean // true if from firehose, false if from backfill
}

IdentityEvent

type IdentityEvent = {
  id: number
  type: 'identity'
  did: string
  handle: string
  isActive: boolean
  status: 'active' | 'takendown' | 'suspended' | 'deactivated' | 'deleted'
}

Webhook Mode

If your Tap server is configured for webhook delivery, you can use parseTapEvent to validate incoming webhook payloads:

import express from 'express'
import { parseTapEvent, assureAdminAuth } from '@atproto/tap'

const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD

const app = express()
app.use(express.json())

app.post('/webhook', async (req, res) => {
  try {
    assureAdminAuth(ADMIN_PASSWORD, req.headers.authorization)
  } catch {
    return res.status(401).json({ error: 'Unauthorized' })
  }

  try {
    const evt = parseTapEvent(req.body)
    // handle event...
    res.sendStatus(200)
  } catch (err) {
    console.error('Failed to process event:', err)
    res.status(500).json({ error: 'Failed to process event' })
  }
})

Utilities

Auth helpers

import {
  formatAdminAuthHeader,
  parseAdminAuthHeader,
  assureAdminAuth,
} from '@atproto/tap'

// Format a password into a Basic auth header value
const header = formatAdminAuthHeader('secret')
// => 'Basic YWRtaW46c2VjcmV0'

// Parse an auth header to extract the password (throws if invalid)
const password = parseAdminAuthHeader(header)

// Verify auth header matches expected password (timing-safe, throws if invalid)
assureAdminAuth('secret', req.headers.authorization)

Event parsing

import { parseTapEvent } from '@atproto/tap'

const evt = parseTapEvent(jsonData) // validates and returns typed TapEvent

License

MIT

版本与发布记录

@atproto/tap 共发布 66 个版本,首次发布于 2025/12/12;过去 12 个月发布了 66 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.4.19latest2026/11/061 天前
0.0.0-spaces-alpha-20261105160032alpha2026/11/062 天前
0.0.0-spaces-alpha-202610291719132026/10/309 天前
0.4.172026/10/2810 天前
0.4.162026/10/2711 天前
0.0.0-spaces-alpha-202610221412282026/10/2216 天前

其他发布通道:next → 0.3.0-next.0,alpha → 0.0.0-spaces-alpha-20261105160032。

关于 @atproto/tap 的常见问题

@atproto/tap 是什么?

atproto tap client

如何安装 @atproto/tap?

在项目目录运行 npm install @atproto/tap,或使用 pnpm add / yarn add / bun add。

@atproto/tap 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@atproto/tap 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@atproto/tap 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@atproto/tap 可以商用吗?

@atproto/tap 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@atproto/tap 的下载量有多少?

根据 npm 官方下载统计,@atproto/tap 在 2026/10/07 – 2026/11/05 这 30 天内被下载 7,780 次,日均约 259 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@atproto/tap 还在维护吗?

最新版本 0.4.19 发布于 2026/11/06(1 天前);至今共发布 66 个版本,其中过去 12 个月发布了 66 个;首次发布于 2025/12/12。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。