Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/bluesky-social/atproto/llms.txt

Use this file to discover all available pages before exploring further.

The @atproto/common-web package provides shared utilities and helper functions used across AT Protocol packages. All utilities are web-platform-friendly and work in browsers, Node.js, and other JavaScript runtimes.

Installation

npm install @atproto/common-web

Overview

This package contains:
  • TID - Timestamp Identifiers for distributed systems
  • Async utilities - Promise helpers and async iteration
  • Data structures - Array and object manipulation
  • DID Document utilities - Working with DID documents
  • IPLD utilities - Content addressing and encoding
  • Retry logic - Configurable retry mechanisms
  • Time utilities - Time parsing and formatting
  • String utilities - String manipulation helpers
This package is designed to be a dependency of other @atproto/* packages. Most applications should use @atproto/api instead of importing this package directly.

TID (Timestamp Identifier)

TIDs are time-ordered identifiers used throughout AT Protocol for records, events, and other entities.

Class: TID

import { TID } from '@atproto/common-web'
TID.next
(prev?: TID) => TID
Generate the next TID
const tid = TID.next()
console.log(tid.toString()) // "3l4yhqsgvzc2g"
TID.nextStr
(prev?: string) => string
Generate the next TID as a string
const tidStr = TID.nextStr()
TID.fromTime
(timestamp: number, clockid: number) => TID
Create a TID from a timestamp and clock ID
const tid = TID.fromTime(Date.now() * 1000, 0)
TID.fromStr
(str: string) => TID
Parse a TID from a string
const tid = TID.fromStr('3l4yhqsgvzc2g')
TID.is
(str: string) => boolean
Check if a string is a valid TID
if (TID.is(str)) {
  // Valid TID
}

Instance Methods

const tid = TID.next()

// Get timestamp (microseconds since epoch)
const timestamp = tid.timestamp()

// Get clock ID
const clockid = tid.clockid()

// Format with dashes
const formatted = tid.formatted() // "3l4y-hqs-gvzc-2g"

// Convert to string
const str = tid.toString() // "3l4yhqsgvzc2g"

// Compare TIDs
const other = TID.next()
const comparison = tid.compareTo(other) // -1, 0, or 1
const isNewer = tid.newerThan(other)
const isOlder = tid.olderThan(other)
const isEqual = tid.equals(other)

Sorting

const tids = [tid3, tid1, tid2]

// Sort oldest first
tids.sort(TID.oldestFirst)

// Sort newest first
tids.sort(TID.newestFirst)

Async Utilities

retry

Retry a function with exponential backoff:
import { retry } from '@atproto/common-web'

const result = await retry(
  async () => {
    return await fetchData()
  },
  {
    maxRetries: 3,
    initialDelay: 100,
    maxDelay: 1000,
    retryable: (error) => error.status === 429,
  }
)
fn
() => Promise<T>
required
The async function to retry
options.maxRetries
number
Maximum number of retry attempts (default: 3)
options.initialDelay
number
Initial delay in milliseconds (default: 100)
options.maxDelay
number
Maximum delay in milliseconds (default: 5000)
options.retryable
(error: unknown) => boolean
Function to determine if an error is retryable (default: all errors retryable)

wait

Delay execution for a specified time:
import { wait } from '@atproto/common-web'

await wait(1000) // Wait 1 second

bailableWait

Create a wait that can be cancelled:
import { bailableWait } from '@atproto/common-web'

const { wait, bail } = bailableWait(5000)

// In one path
wait().then(() => console.log('Waited 5 seconds'))

// In another path, cancel early
setTimeout(() => bail(), 1000)

Array Utilities

chunkArray

Split an array into chunks:
import { chunkArray } from '@atproto/common-web'

const items = [1, 2, 3, 4, 5, 6, 7]
const chunks = chunkArray(items, 3)
// [[1, 2, 3], [4, 5, 6], [7]]

dedupeStrs

Remove duplicate strings from an array:
import { dedupeStrs } from '@atproto/common-web'

const unique = dedupeStrs(['a', 'b', 'a', 'c', 'b'])
// ['a', 'b', 'c']

range

Create an array of numbers:
import { range } from '@atproto/common-web'

const numbers = range(5)
// [0, 1, 2, 3, 4]

asyncFilter

Filter array with async predicate:
import { asyncFilter } from '@atproto/common-web'

const results = await asyncFilter(items, async (item) => {
  return await checkItem(item)
})

Object Utilities

omit

Create a shallow copy without specified keys:
import { omit } from '@atproto/common-web'

const obj = { a: 1, b: 2, c: 3 }
const result = omit(obj, ['b'])
// { a: 1, c: 3 }

noUndefinedVals

Remove undefined values from an object:
import { noUndefinedVals } from '@atproto/common-web'

const obj = { a: 1, b: undefined, c: 3 }
const result = noUndefinedVals(obj)
// { a: 1, c: 3 }

String Utilities

parseLanguage

Parse a language code:
import { parseLanguage } from '@atproto/common-web'

const lang = parseLanguage('en-US')
// { language: 'en', region: 'US' }

s32encode / s32decode

Encode/decode numbers using base-32 (sortable variant):
import { s32encode, s32decode } from '@atproto/common-web'

const encoded = s32encode(12345)
const decoded = s32decode(encoded)

DID Document Utilities

isValidDidDoc

Validate a DID document structure:
import { isValidDidDoc } from '@atproto/common-web'

if (isValidDidDoc(doc)) {
  // Valid DID document
}

getPdsEndpoint

Extract the PDS endpoint from a DID document:
import { getPdsEndpoint } from '@atproto/common-web'

const endpoint = getPdsEndpoint(didDoc)
// "https://pds.example.com"

getEntryUrlFromDidDoc

Get the entry URL for a service:
import { getEntryUrlFromDidDoc } from '@atproto/common-web'

const url = getEntryUrlFromDidDoc(
  didDoc,
  'did:web:example.com',
  'AtprotoPersonalDataServer'
)

IPLD Utilities

cidForCbor

Generate a CID for CBOR data:
import { cidForCbor } from '@atproto/common-web'

const data = { hello: 'world' }
const cid = await cidForCbor(data)

verifyCidForBytes

Verify that bytes match a CID:
import { verifyCidForBytes } from '@atproto/common-web'

const isValid = await verifyCidForBytes(
  cid,
  bytes
)

Type Guards

check

The check namespace provides type checking utilities:
import { check } from '@atproto/common-web'

// Check if value is defined
if (check.is(value)) {
  // value is not null or undefined
}

Error Utilities

aggregateErrors

Combine multiple errors:
import { aggregateErrors } from '@atproto/common-web'

const errors = [error1, error2, error3]
const combined = aggregateErrors(errors, 'Multiple operations failed')

throw combined

isErrnoException

Check if error is a Node.js errno exception:
import { isErrnoException } from '@atproto/common-web'

if (isErrnoException(error)) {
  console.log('Error code:', error.code)
}

errHasMsg

Check if error has a specific message:
import { errHasMsg } from '@atproto/common-web'

if (errHasMsg(error, 'ENOENT')) {
  // Handle file not found
}

Binary Data Utilities

flattenUint8Arrays

Combine multiple Uint8Arrays:
import { flattenUint8Arrays } from '@atproto/common-web'

const arrays = [new Uint8Array([1, 2]), new Uint8Array([3, 4])]
const flattened = flattenUint8Arrays(arrays)
// Uint8Array([1, 2, 3, 4])

streamToBuffer

Convert async iterable to Uint8Array:
import { streamToBuffer } from '@atproto/common-web'

async function* generateChunks() {
  yield new Uint8Array([1, 2])
  yield new Uint8Array([3, 4])
}

const buffer = await streamToBuffer(generateChunks())
// Uint8Array([1, 2, 3, 4])

Time Utilities

parseIntWithFallback

Parse integer with fallback:
import { parseIntWithFallback } from '@atproto/common-web'

const value = parseIntWithFallback('123', 0) // 123
const invalid = parseIntWithFallback('abc', 0) // 0

jitter

Generate random jitter:
import { jitter } from '@atproto/common-web'

const delay = 1000 + jitter(200)
// Random value between 800 and 1200

Complete Examples

Retry with Custom Logic

import { retry, wait } from '@atproto/common-web'

interface ApiError {
  status: number
  retryAfter?: number
}

async function fetchWithRetry(url: string) {
  return retry(
    async () => {
      const response = await fetch(url)
      if (!response.ok) {
        const error: ApiError = {
          status: response.status,
          retryAfter: parseInt(response.headers.get('Retry-After') || '0'),
        }
        throw error
      }
      return response.json()
    },
    {
      maxRetries: 5,
      retryable: (error: any) => {
        // Retry on 429 (rate limit) and 5xx errors
        return error.status === 429 || error.status >= 500
      },
    }
  )
}

Batch Processing with Chunks

import { chunkArray, wait } from '@atproto/common-web'

async function processBatch(items: string[]) {
  const chunks = chunkArray(items, 10)
  
  for (const chunk of chunks) {
    await Promise.all(
      chunk.map(item => processItem(item))
    )
    
    // Rate limiting
    await wait(100)
  }
}

async function processItem(item: string) {
  console.log('Processing:', item)
  // Process the item
}

Working with TIDs

import { TID } from '@atproto/common-web'

class RecordManager {
  private lastTid?: TID
  
  createRecord(data: any) {
    const tid = TID.next(this.lastTid)
    this.lastTid = tid
    
    return {
      rkey: tid.toString(),
      data,
      createdAt: new Date(tid.timestamp() / 1000),
    }
  }
  
  isRecordNewer(tid1: string, tid2: string): boolean {
    return TID.fromStr(tid1).newerThan(TID.fromStr(tid2))
  }
  
  sortRecords(records: { rkey: string }[]) {
    return records.sort((a, b) => {
      return TID.fromStr(a.rkey).compareTo(TID.fromStr(b.rkey))
    })
  }
}

Stream Processing

import { streamToBuffer, flattenUint8Arrays } from '@atproto/common-web'

async function downloadFile(url: string): Promise<Uint8Array> {
  const response = await fetch(url)
  
  if (!response.body) {
    throw new Error('No response body')
  }
  
  // Convert ReadableStream to AsyncIterable
  const reader = response.body.getReader()
  
  async function* stream() {
    try {
      while (true) {
        const { done, value } = await reader.read()
        if (done) break
        yield value
      }
    } finally {
      reader.releaseLock()
    }
  }
  
  return streamToBuffer(stream())
}

Type Definitions

Key types exported from the package:
// Utility types
export type DateISO = string
export type TimeISO = string

// Error types
export interface ErrnoException extends Error {
  errno?: number
  code?: string
  path?: string
  syscall?: string
}

// Async types
export interface RetryOptions {
  maxRetries?: number
  initialDelay?: number
  maxDelay?: number
  retryable?: (error: unknown) => boolean
}

export interface BailableWait {
  bail: () => void
  wait: () => Promise<void>
}

Notes

  • All utilities are designed to work in web browsers and Node.js
  • The package has no platform-specific dependencies
  • TID generation is not cryptographically secure but provides collision resistance
  • Retry logic uses exponential backoff with jitter

See Also

Build docs developers (and LLMs) love