PHPackages                             socialdept/atp-parity - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. socialdept/atp-parity

ActiveLibrary[Database &amp; ORM](/categories/database)

socialdept/atp-parity
=====================

AT Protocol record mapping and sync for Laravel Eloquent models

v0.4.7(2mo ago)0517MITPHPPHP ^8.3

Since Dec 4Pushed 2mo agoCompare

[ Source](https://github.com/socialdept/atp-parity)[ Packagist](https://packagist.org/packages/socialdept/atp-parity)[ Docs](https://github.com/socialdept/atp-parity)[ RSS](/packages/socialdept-atp-parity/feed)WikiDiscussions main Synced 3w ago

READMEChangelogDependencies (16)Versions (46)Used By (0)

[![Parity Header](./header.png)](https://github.com/socialdept/atp-parity)

###  Bidirectional mapping between AT Protocol records and Laravel Eloquent models.

[](#----bidirectional-mapping-between-at-protocol-records-and-laravel-eloquent-models)

 [![](https://camo.githubusercontent.com/ea18b8a689ab2c9ea3a01cc7068d60e58a5226693ff56870a84f0c1cb77cd985/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f736f6369616c646570742f6174702d7061726974792e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/socialdept/atp-parity "Latest Version on Packagist") [![](https://camo.githubusercontent.com/2aec79c1e8d8ce32f51531eb2089d2ea4bf4575387e339eee33aff27e1c63784/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f736f6369616c646570742f6174702d7061726974792e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/socialdept/atp-parity "Total Downloads") [![](https://camo.githubusercontent.com/2c452b19d2f29862eba511c9ee14a8949b7a326f5484f4c8ed60d419c5408715/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f736f6369616c646570742f6174702d7061726974792f74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/socialdept/atp-parity/actions/workflows/tests.yml "GitHub Tests Action Status") [![](https://camo.githubusercontent.com/c336fb591acb7b5603bfa2fcf0081636292002ac480f247c7050119c52c0ef8a/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f736f6369616c646570742f6174702d7061726974793f7374796c653d666c61742d737175617265)](LICENSE "Software License")

---

What is Parity?
---------------

[](#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?
---------------

[](#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
- **Blob handling** - Download, upload, and serve images and videos

Quick Example
-------------

[](#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
------------

[](#installation)

```
composer require socialdept/atp-parity
```

Optionally publish the configuration:

```
php artisan vendor:publish --tag=parity-config
```

Getting Started
---------------

[](#getting-started)

Once installed, you're three steps away from syncing AT Protocol records:

### 1. Create a Mapper

[](#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

[](#2-register-your-mapper)

```
// config/parity.php
return [
    'mappers' => [
        App\AtpMappers\PostMapper::class,
    ],
];
```

### 3. Add the Trait to Your Model

[](#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?
-------------------

[](#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
---------------------

[](#ecosystem-integration)

Parity is designed to work seamlessly with the other atp-\* packages:

PackageIntegration**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

[](#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

[](#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

[](#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

[](#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
-------------

[](#documentation)

For detailed documentation on specific topics:

- [Record Mappers](docs/mappers.md) - Creating and using mappers
- [Model Traits](docs/traits.md) - HasAtpRecord and SyncsWithAtp
- [Automatic Syncing](docs/auto-sync.md) - Auto-sync models with AT Protocol
- [Blob Handling](docs/blobs.md) - Downloading, uploading, and serving blobs
- [atp-schema Integration](docs/atp-schema-integration.md) - Using generated DTOs
- [atp-client Integration](docs/atp-client-integration.md) - RecordHelper and fetching
- [atp-signals Integration](docs/atp-signals-integration.md) - ParitySignal and firehose sync
- [Importing](docs/importing.md) - Syncing historical data

Model Traits
------------

[](#model-traits)

### HasAtpRecord

[](#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

[](#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);
```

### HasAtpBlobs

[](#hasatpblobs)

Add blob handling to models with images or other binary content:

```
use SocialDept\AtpParity\Concerns\HasAtpRecord;
use SocialDept\AtpParity\Concerns\HasAtpBlobs;

class Post extends Model
{
    use HasAtpRecord, HasAtpBlobs;

    protected $casts = ['atp_blobs' => 'array'];
}
```

Available methods:

```
// Get URLs for blobs
$url = $post->getAtpBlobUrl('avatar');     // Single blob URL
$urls = $post->getAtpBlobUrls('images');   // Array of URLs

// Download blobs locally
$post->downloadAtpBlobs();

// Check status
$post->hasAtpBlobs();      // Has any blob data
$post->hasLocalBlobs();    // All blobs downloaded locally
```

See [Blob Handling](docs/blobs.md) for complete documentation including MediaLibrary integration.

### AutoSyncsWithAtp

[](#autosyncswithatp)

Automatically sync models with AT Protocol on create, update, and delete:

```
use SocialDept\AtpParity\Concerns\AutoSyncsWithAtp;

class Post extends Model
{
    use AutoSyncsWithAtp;

    public function syncAsDid(): ?string
    {
        return $this->user->did;
    }

    public function shouldAutoSync(): bool
    {
        return $this->status === 'published';
    }
}
```

When enabled, the model automatically syncs:

```
$post = Post::create(['content' => 'Hello!']);  // Syncs to ATP
$post->update(['content' => 'Updated']);         // Updates ATP record
$post->delete();                                  // Removes from ATP
```

Failed syncs due to expired OAuth sessions can be captured and retried after re-authentication. See [Automatic Syncing](docs/auto-sync.md) for complete documentation including pending sync configuration.

Database Migration
------------------

[](#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
    $table->json('atp_blobs')->nullable();          // For HasAtpBlobs
});
```

Publish and run Parity's core migration:

```
php artisan vendor:publish --tag=parity-migrations
php artisan migrate
```

Optional migrations are published separately based on which features you use:

```
# Manual conflict resolution (conflicts.strategy = 'manual')
php artisan vendor:publish --tag=parity-migrations-conflicts

# Filesystem blob storage (blobs.storage_driver = 'filesystem')
php artisan vendor:publish --tag=parity-migrations-blobs

# Database pending sync storage (pending_syncs.storage = 'database')
php artisan vendor:publish --tag=parity-migrations-pending-syncs
```

Configuration
-------------

[](#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',
    ],

    // Blob handling configuration
    'blobs' => [
        // 'filesystem' (requires migrations) or 'medialibrary' (no extra migrations)
        'storage_driver' => \SocialDept\AtpParity\Enums\BlobStorageDriver::Filesystem,
        'download_on_import' => env('PARITY_BLOB_DOWNLOAD', false),
        'disk' => env('PARITY_BLOB_DISK', 'local'),
        'url_strategy' => \SocialDept\AtpParity\Enums\BlobUrlStrategy::Cdn,
    ],
];
```

Creating Custom Records
-----------------------

[](#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
------------

[](#requirements)

- PHP 8.2+
- Laravel 10, 11, or 12
- [socialdept/atp-schema](https://github.com/socialdept/atp-schema) ^0.3
- [socialdept/atp-client](https://github.com/socialdept/atp-client) ^0.0
- [socialdept/atp-resolver](https://github.com/socialdept/atp-resolver) ^1.1
- [socialdept/atp-signals](https://github.com/socialdept/atp-signals) ^1.1

Testing
-------

[](#testing)

```
composer test
```

Resources
---------

[](#resources)

- [AT Protocol Documentation](https://atproto.com/)
- [Bluesky API Docs](https://docs.bsky.app/)
- [atp-schema](https://github.com/socialdept/atp-schema) - Generated AT Protocol DTOs
- [atp-client](https://github.com/socialdept/atp-client) - AT Protocol HTTP client
- [atp-signals](https://github.com/socialdept/atp-signals) - Firehose event consumer

Support &amp; Contributing
--------------------------

[](#support--contributing)

Found a bug or have a feature request? [Open an issue](https://github.com/socialdept/atp-parity/issues).

Want to contribute? Check out the [contribution guidelines](contributing.md).

Changelog
---------

[](#changelog)

Please see [changelog](changelog.md) for recent changes.

Credits
-------

[](#credits)

- [Miguel Batres](https://batres.co) - founder &amp; lead maintainer
- [All contributors](https://github.com/socialdept/atp-parity/graphs/contributors)

License
-------

[](#license)

Parity is open-source software licensed under the [MIT license](license.md).

---

**Built for the Atmosphere** - By Social Dept.

###  Health Score

43

—

FairBetter than 90% of packages

Maintenance84

Actively maintained with recent releases

Popularity16

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity53

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~3 days

Recently: every ~14 days

Total

44

Last Release

81d ago

PHP version history (2 changes)v0.1.0PHP ^8.2

v0.4.7PHP ^8.3

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/101910626?v=4)[Social Dept.](/maintainers/socialdept)[@socialdept](https://github.com/socialdept)

---

Top Contributors

[![btrsco](https://avatars.githubusercontent.com/u/1373528?v=4)](https://github.com/btrsco "btrsco (155 commits)")

---

Tags

laravelsyncblueskyat protocolatpparity

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/socialdept-atp-parity/health.svg)

```
[![Health](https://phpackages.com/badges/socialdept-atp-parity/health.svg)](https://phpackages.com/packages/socialdept-atp-parity)
```

###  Alternatives

[kirschbaum-development/eloquent-power-joins

The Laravel magic applied to joins.

1.6k29.9M42](/packages/kirschbaum-development-eloquent-power-joins)[psalm/plugin-laravel

Psalm plugin for Laravel

3345.1M337](/packages/psalm-plugin-laravel)[yajra/laravel-oci8

Oracle DB driver for Laravel via OCI8

8723.1M23](/packages/yajra-laravel-oci8)[glushkovds/phpclickhouse-laravel

Adapter of the most popular library https://github.com/smi2/phpClickHouse to Laravel

2051.4M2](/packages/glushkovds-phpclickhouse-laravel)[clickbar/laravel-magellan

This package provides functionality for working with the postgis extension in Laravel.

438834.4k1](/packages/clickbar-laravel-magellan)[reedware/laravel-relation-joins

Adds the ability to join on a relationship by name.

2121.2M16](/packages/reedware-laravel-relation-joins)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
