@pipup/atproto
v0.1.7
Published
AT Protocol utilities for blog platforms (PiPup, Whitewind, Leaflet, etc.)
Maintainers
Readme
@pipup/atproto
ATProto utilities for blog platforms including import functionality.
Overview
This package provides common functionality for working with ATProto-based blog platforms. Its main purpose is to translate not only from other lexicons that use Markdown but also from other formats like blocks.
Features
Leaflet Import: Convert Leaflet documents to PiPup format with support for:
- Rich text facets (bold, italic, code, links)
- Nested unordered lists
- Images, blockquotes, headers
- Code blocks, math equations
- Special blocks (Bluesky posts, websites, iframes)
Whitewind Import: Convert Whitewind blog entries to PiPup format
Lexicon-based Validation: ATProto lexicon-generated types and validation
Installation
pnpm add @pipup/atprotoUsage
Whitewind Import
import {
transformWhitewindToPipup,
validateWhitewindRecord,
type WhitewindBlogRecord,
type PipupBlogRecord
} from '@pipup/atproto'
// Validate using lexicon-generated function
const result = validateWhitewindRecord(record)
if (result.success) {
// Transform to PiPup format
const entry: PipupBlogRecord = transformWhitewindToPipup(record)
console.log(entry.content) // Markdown with title inserted
}Leaflet Import
import {
transformLeafletToPipup,
validateLeafletRecord,
isLeafletRecord,
type LeafletDocumentRecord,
type PipupBlogRecord
} from '@pipup/atproto'
// Real-time ingestion (jetstream/firehose) - use type guard only
if (isLeafletRecord(doc)) {
const entry: PipupBlogRecord = transformLeafletToPipup(doc, authorDid)
// Process entry...
}
// Backfill (ATProto API) - use full validation
const result = validateLeafletRecord(doc)
if (result.success) {
const entry = transformLeafletToPipup(doc, authorDid)
// Process entry...
} else {
console.error('Validation error:', result.error)
}Advanced: Block-level Transformation
import {
blocksToMarkdown,
applyFacets,
buildByteToCharMap
} from '@pipup/atproto'
// Transform Leaflet blocks to Markdown
const markdown = blocksToMarkdown(blocks, authorDid)
// Apply rich text facets to plaintext
const formatted = applyFacets('hello world', facets)
// Handle UTF-8 byte offsets
const byteMap = buildByteToCharMap(text)API Reference
Type Exports
All types are re-exported from lexicon-generated code:
WhitewindBlogRecord- Whitewind blog entry recordLeafletDocumentRecord- Leaflet document recordPipupBlogRecord- PiPup blog entry record (output format)
Validation Functions
Type Guards (for real-time ingestion from jetstream/firehose):
isWhitewindRecord(record: any): boolean- Checks$typefield onlyisLeafletRecord(record: any): boolean- Checks$typefield onlyisPipupRecord(record: any): boolean- Checks$typefield only
Full Validation (for backfill via ATProto API):
validateWhitewindRecord(record: any): ValidationResult- Complete lexicon schema validationvalidateLeafletRecord(record: any): ValidationResult- Complete lexicon schema validationvalidatePipupRecord(record: any): ValidationResult- Complete lexicon schema validation
Important: Use type guards for real-time ingestion (jetstream/firehose) because records are in raw IPLD format. Only use full validation for backfill where records are fetched through the ATProto API and have been processed.
Transformation Functions
Whitewind:
transformWhitewindToPipup(
entry: WhitewindBlogRecord
): PipupBlogRecordLeaflet:
transformLeafletToPipup(
doc: LeafletDocumentRecord,
authorDid: string
): PipupBlogRecord
blocksToMarkdown(
blocks: Array<any>,
authorDid: string
): string
applyFacets(
plaintext: string,
facets?: Array<any>
): string
buildByteToCharMap(
input: string
): number[]Development
# Install dependencies
pnpm install
# Build the package
pnpm build
# Run tests
pnpm test
# Watch mode for development
pnpm dev
# Run tests once (CI mode)
pnpm test:runArchitecture
This package follows a thin wrapper approach:
- Lexicon Schemas (
lexicons/) - ATProto record definitions in JSON - Generated Types (
src/lexicon/) - TypeScript types generated via@atproto/lex-cli - Transformation Logic (
src/*/transform.ts) - Business logic for format conversion - Re-exports (
src/*/index.ts) - Clean API surface
No custom wrapper types are created - all types come directly from lexicon generation.
Build Process
tsc- Compile TypeScript with@path aliasestsc-alias- Resolve@imports to relative pathsfix-imports.js- Add.jsextensions to lexicon imports
License
MIT
