Bidirectional mapping between AT Protocol records and Laravel Eloquent models.
What is Parity?#
Parity is a Laravel package that bridges your Eloquent models with AT Protocol records. It provides bidirectional mapping, automatic firehose synchronization, and type-safe transformations between your database and the decentralized social web.
Think of it as Laravel's model casts, but for AT Protocol records.
Why use Parity?#
- Laravel-style code - Familiar patterns you already know
- Bidirectional mapping - Transform records to models and back
- Firehose sync - Automatically sync network events to your database
- Type-safe DTOs - Full integration with atp-schema generated types
- Model traits - Add AT Protocol awareness to any Eloquent model
- Flexible mappers - Define custom transformations for your domain
Quick Example#
use SocialDept\AtpParity\RecordMapper;
use SocialDept\AtpSchema\Data\Data;
use Illuminate\Database\Eloquent\Model;
class PostMapper extends RecordMapper
{
public function recordClass(): string
{
return \SocialDept\AtpSchema\Generated\App\Bsky\Feed\Post::class;
}
public function modelClass(): string
{
return \App\Models\Post::class;
}
protected function recordToAttributes(Data $record): array
{
return [
'content' => $record->text,
'published_at' => $record->createdAt,
];
}
protected function modelToRecordData(Model $model): array
{
return [
'text' => $model->content,
'createdAt' => $model->published_at->toIso8601String(),
];
}
}
Installation#
composer require socialdept/atp-parity
Optionally publish the configuration:
php artisan vendor:publish --tag=parity-config
Getting Started#
Once installed, you're three steps away from syncing AT Protocol records:
1. Create a Mapper#
Define how your record maps to your model:
class PostMapper extends RecordMapper
{
public function recordClass(): string
{
return Post::class; // Your atp-schema DTO or custom Record
}
public function modelClass(): string
{
return \App\Models\Post::class;
}
protected function recordToAttributes(Data $record): array
{
return ['content' => $record->text];
}
protected function modelToRecordData(Model $model): array
{
return ['text' => $model->content];
}
}
2. Register Your Mapper#
// config/parity.php
return [
'mappers' => [
App\AtpMappers\PostMapper::class,
],
];
3. Add the Trait to Your Model#
use SocialDept\AtpParity\Concerns\HasAtpRecord;
class Post extends Model
{
use HasAtpRecord;
}
Your model can now convert to/from AT Protocol records and query by URI.
What can you build?#
- Data mirrors - Keep local copies of AT Protocol data
- AppViews - Build custom applications with synced data
- Analytics platforms - Store and analyze network activity
- Content aggregators - Collect and organize posts locally
- Moderation tools - Track and manage content in your database
- Hybrid applications - Combine local and federated data
Ecosystem Integration#
Parity is designed to work seamlessly with the other atp-* packages:
| Package | Integration |
|---|---|
| atp-schema | Records extend Data, use generated DTOs directly |
| atp-client | RecordHelper for fetching and hydrating records |
| atp-signals | ParitySignal for automatic firehose sync |
Using with atp-schema#
Use generated schema classes directly with SchemaMapper:
use SocialDept\AtpSchema\Generated\App\Bsky\Feed\Post;
use SocialDept\AtpParity\Support\SchemaMapper;
$mapper = new SchemaMapper(
schemaClass: Post::class,
modelClass: \App\Models\Post::class,
toAttributes: fn(Post $p) => [
'content' => $p->text,
'published_at' => $p->createdAt,
],
toRecordData: fn($m) => [
'text' => $m->content,
'createdAt' => $m->published_at->toIso8601String(),
],
);
$registry->register($mapper);
Using with atp-client#
Fetch records by URI and convert directly to models:
use SocialDept\AtpParity\Support\RecordHelper;
$helper = app(RecordHelper::class);
// Fetch as typed DTO
$record = $helper->fetch('at://did:plc:xxx/app.bsky.feed.post/abc123');
// Fetch and convert to model (unsaved)
$post = $helper->fetchAsModel('at://did:plc:xxx/app.bsky.feed.post/abc123');
// Fetch and sync to database (upsert)
$post = $helper->sync('at://did:plc:xxx/app.bsky.feed.post/abc123');
The helper automatically resolves the DID to find the correct PDS endpoint, so it works with any AT Protocol server - not just Bluesky.
Using with atp-signals#
Enable automatic firehose synchronization by registering the ParitySignal:
// config/signal.php
return [
'signals' => [
\SocialDept\AtpParity\Signals\ParitySignal::class,
],
];
Run php artisan signal:consume and your models will automatically sync with matching firehose events.
Importing Historical Data#
For existing records created before you started consuming the firehose:
# Import a user's records
php artisan parity:import did:plc:z72i7hdynmk6r22z27h6tvur
# Check import status
php artisan parity:import-status
Or programmatically:
use SocialDept\AtpParity\Import\ImportService;
$service = app(ImportService::class);
$result = $service->importUser('did:plc:z72i7hdynmk6r22z27h6tvur');
echo "Synced {$result->recordsSynced} records";
Documentation#
For detailed documentation on specific topics:
- Record Mappers - Creating and using mappers
- Model Traits - HasAtpRecord and SyncsWithAtp
- atp-schema Integration - Using generated DTOs
- atp-client Integration - RecordHelper and fetching
- atp-signals Integration - ParitySignal and firehose sync
- Importing - Syncing historical data
Model Traits#
HasAtpRecord#
Add AT Protocol awareness to your models:
use SocialDept\AtpParity\Concerns\HasAtpRecord;
class Post extends Model
{
use HasAtpRecord;
protected $fillable = ['content', 'atp_uri', 'atp_cid'];
}
Available methods:
// Get AT Protocol metadata
$post->getAtpUri(); // at://did:plc:xxx/app.bsky.feed.post/rkey
$post->getAtpCid(); // bafyre...
$post->getAtpDid(); // did:plc:xxx (extracted from URI)
$post->getAtpCollection(); // app.bsky.feed.post (extracted from URI)
$post->getAtpRkey(); // rkey (extracted from URI)
// Check sync status
$post->hasAtpRecord(); // true if synced
// Convert to record DTO
$record = $post->toAtpRecord();
// Query scopes
Post::withAtpRecord()->get(); // Only synced posts
Post::withoutAtpRecord()->get(); // Only unsynced posts
Post::whereAtpUri($uri)->first(); // Find by URI
SyncsWithAtp#
Extended trait for bidirectional sync tracking:
use SocialDept\AtpParity\Concerns\SyncsWithAtp;
class Post extends Model
{
use SyncsWithAtp;
}
Additional methods:
// Track sync status
$post->getAtpSyncedAt(); // Last sync timestamp
$post->hasLocalChanges(); // True if updated since last sync
// Mark as synced
$post->markAsSynced($uri, $cid);
// Update from remote
$post->updateFromRecord($record, $uri, $cid);
Database Migration#
Add AT Protocol columns to your models:
Schema::table('posts', function (Blueprint $table) {
$table->string('atp_uri')->nullable()->unique();
$table->string('atp_cid')->nullable();
$table->timestamp('atp_synced_at')->nullable(); // For SyncsWithAtp
});
Configuration#
// config/parity.php
return [
// Registered mappers
'mappers' => [
App\AtpMappers\PostMapper::class,
App\AtpMappers\ProfileMapper::class,
],
// Column names for AT Protocol metadata
'columns' => [
'uri' => 'atp_uri',
'cid' => 'atp_cid',
],
];
Creating Custom Records#
Extend the Record base class for custom AT Protocol records:
use SocialDept\AtpParity\Data\Record;
use Carbon\Carbon;
class PostRecord extends Record
{
public function __construct(
public readonly string $text,
public readonly Carbon $createdAt,
public readonly ?array $facets = null,
) {}
public static function getLexicon(): string
{
return 'app.bsky.feed.post';
}
public static function fromArray(array $data): static
{
return new static(
text: $data['text'],
createdAt: Carbon::parse($data['createdAt']),
facets: $data['facets'] ?? null,
);
}
}
The Record class extends atp-schema's Data and implements atp-client's Recordable interface, ensuring full compatibility with the ecosystem.
Requirements#
- PHP 8.2+
- Laravel 10, 11, or 12
- socialdept/atp-schema ^0.3
- socialdept/atp-client ^0.0
- socialdept/atp-resolver ^1.1
- socialdept/atp-signals ^1.1
Testing#
composer test
Resources#
- AT Protocol Documentation
- Bluesky API Docs
- atp-schema - Generated AT Protocol DTOs
- atp-client - AT Protocol HTTP client
- atp-signals - Firehose event consumer
Support & Contributing#
Found a bug or have a feature request? Open an issue.
Want to contribute? Check out the contribution guidelines.
Changelog#
Please see changelog for recent changes.
Credits#
- Miguel Batres - founder & lead maintainer
- All contributors
License#
Parity is open-source software licensed under the MIT license.
Built for the Federation - By Social Dept.