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'
Generate the next TIDconst tid = TID.next()
console.log(tid.toString()) // "3l4yhqsgvzc2g"
TID.nextStr
(prev?: string) => string
Generate the next TID as a stringconst tidStr = TID.nextStr()
TID.fromTime
(timestamp: number, clockid: number) => TID
Create a TID from a timestamp and clock IDconst tid = TID.fromTime(Date.now() * 1000, 0)
Parse a TID from a stringconst tid = TID.fromStr('3l4yhqsgvzc2g')
Check if a string is a valid TIDif (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,
}
)
The async function to retry
Maximum number of retry attempts (default: 3)
Initial delay in milliseconds (default: 100)
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