+397
-28
README.md
+397
-28
README.md
···
1
-
# AtpReplicator
1
+
[](https://github.com/socialdept/atp-parity)
2
+
3
+
<h3 align="center">
4
+
Bidirectional mapping between AT Protocol records and Laravel Eloquent models.
5
+
</h3>
6
+
7
+
<p align="center">
8
+
<br>
9
+
<a href="https://packagist.org/packages/socialdept/atp-parity" title="Latest Version on Packagist"><img src="https://img.shields.io/packagist/v/socialdept/atp-parity.svg?style=flat-square"></a>
10
+
<a href="https://packagist.org/packages/socialdept/atp-parity" title="Total Downloads"><img src="https://img.shields.io/packagist/dt/socialdept/atp-parity.svg?style=flat-square"></a>
11
+
<a href="https://github.com/socialdept/atp-parity/actions/workflows/tests.yml" title="GitHub Tests Action Status"><img src="https://img.shields.io/github/actions/workflow/status/socialdept/atp-parity/tests.yml?branch=main&label=tests&style=flat-square"></a>
12
+
<a href="LICENSE" title="Software License"><img src="https://img.shields.io/github/license/socialdept/atp-parity?style=flat-square"></a>
13
+
</p>
14
+
15
+
---
16
+
17
+
## What is Parity?
18
+
19
+
**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.
20
+
21
+
Think of it as Laravel's model casts, but for AT Protocol records.
22
+
23
+
## Why use Parity?
2
24
3
-
[![Latest Version on Packagist][ico-version]][link-packagist]
4
-
[![Total Downloads][ico-downloads]][link-downloads]
5
-
[![Build Status][ico-travis]][link-travis]
6
-
[![StyleCI][ico-styleci]][link-styleci]
25
+
- **Laravel-style code** - Familiar patterns you already know
26
+
- **Bidirectional mapping** - Transform records to models and back
27
+
- **Firehose sync** - Automatically sync network events to your database
28
+
- **Type-safe DTOs** - Full integration with atp-schema generated types
29
+
- **Model traits** - Add AT Protocol awareness to any Eloquent model
30
+
- **Flexible mappers** - Define custom transformations for your domain
31
+
32
+
## Quick Example
33
+
34
+
```php
35
+
use SocialDept\AtpParity\RecordMapper;
36
+
use SocialDept\AtpSchema\Data\Data;
37
+
use Illuminate\Database\Eloquent\Model;
38
+
39
+
class PostMapper extends RecordMapper
40
+
{
41
+
public function recordClass(): string
42
+
{
43
+
return \SocialDept\AtpSchema\Generated\App\Bsky\Feed\Post::class;
44
+
}
45
+
46
+
public function modelClass(): string
47
+
{
48
+
return \App\Models\Post::class;
49
+
}
50
+
51
+
protected function recordToAttributes(Data $record): array
52
+
{
53
+
return [
54
+
'content' => $record->text,
55
+
'published_at' => $record->createdAt,
56
+
];
57
+
}
7
58
8
-
This is where your description should go. Take a look at [contributing.md](contributing.md) to see a to do list.
59
+
protected function modelToRecordData(Model $model): array
60
+
{
61
+
return [
62
+
'text' => $model->content,
63
+
'createdAt' => $model->published_at->toIso8601String(),
64
+
];
65
+
}
66
+
}
67
+
```
9
68
10
69
## Installation
11
70
12
-
Via Composer
71
+
```bash
72
+
composer require socialdept/atp-parity
73
+
```
74
+
75
+
Optionally publish the configuration:
76
+
77
+
```bash
78
+
php artisan vendor:publish --tag=parity-config
79
+
```
80
+
81
+
## Getting Started
82
+
83
+
Once installed, you're three steps away from syncing AT Protocol records:
84
+
85
+
### 1. Create a Mapper
86
+
87
+
Define how your record maps to your model:
88
+
89
+
```php
90
+
class PostMapper extends RecordMapper
91
+
{
92
+
public function recordClass(): string
93
+
{
94
+
return Post::class; // Your atp-schema DTO or custom Record
95
+
}
96
+
97
+
public function modelClass(): string
98
+
{
99
+
return \App\Models\Post::class;
100
+
}
101
+
102
+
protected function recordToAttributes(Data $record): array
103
+
{
104
+
return ['content' => $record->text];
105
+
}
106
+
107
+
protected function modelToRecordData(Model $model): array
108
+
{
109
+
return ['text' => $model->content];
110
+
}
111
+
}
112
+
```
113
+
114
+
### 2. Register Your Mapper
115
+
116
+
```php
117
+
// config/parity.php
118
+
return [
119
+
'mappers' => [
120
+
App\AtpMappers\PostMapper::class,
121
+
],
122
+
];
123
+
```
124
+
125
+
### 3. Add the Trait to Your Model
126
+
127
+
```php
128
+
use SocialDept\AtpParity\Concerns\HasAtpRecord;
129
+
130
+
class Post extends Model
131
+
{
132
+
use HasAtpRecord;
133
+
}
134
+
```
135
+
136
+
Your model can now convert to/from AT Protocol records and query by URI.
137
+
138
+
## What can you build?
139
+
140
+
- **Data mirrors** - Keep local copies of AT Protocol data
141
+
- **AppViews** - Build custom applications with synced data
142
+
- **Analytics platforms** - Store and analyze network activity
143
+
- **Content aggregators** - Collect and organize posts locally
144
+
- **Moderation tools** - Track and manage content in your database
145
+
- **Hybrid applications** - Combine local and federated data
146
+
147
+
## Ecosystem Integration
148
+
149
+
Parity is designed to work seamlessly with the other atp-* packages:
150
+
151
+
| Package | Integration |
152
+
|---------|-------------|
153
+
| **atp-schema** | Records extend `Data`, use generated DTOs directly |
154
+
| **atp-client** | `RecordHelper` for fetching and hydrating records |
155
+
| **atp-signals** | `ParitySignal` for automatic firehose sync |
156
+
157
+
### Using with atp-schema
158
+
159
+
Use generated schema classes directly with `SchemaMapper`:
160
+
161
+
```php
162
+
use SocialDept\AtpSchema\Generated\App\Bsky\Feed\Post;
163
+
use SocialDept\AtpParity\Support\SchemaMapper;
164
+
165
+
$mapper = new SchemaMapper(
166
+
schemaClass: Post::class,
167
+
modelClass: \App\Models\Post::class,
168
+
toAttributes: fn(Post $p) => [
169
+
'content' => $p->text,
170
+
'published_at' => $p->createdAt,
171
+
],
172
+
toRecordData: fn($m) => [
173
+
'text' => $m->content,
174
+
'createdAt' => $m->published_at->toIso8601String(),
175
+
],
176
+
);
177
+
178
+
$registry->register($mapper);
179
+
```
180
+
181
+
### Using with atp-client
182
+
183
+
Fetch records by URI and convert directly to models:
184
+
185
+
```php
186
+
use SocialDept\AtpParity\Support\RecordHelper;
187
+
188
+
$helper = app(RecordHelper::class);
189
+
190
+
// Fetch as typed DTO
191
+
$record = $helper->fetch('at://did:plc:xxx/app.bsky.feed.post/abc123');
192
+
193
+
// Fetch and convert to model (unsaved)
194
+
$post = $helper->fetchAsModel('at://did:plc:xxx/app.bsky.feed.post/abc123');
195
+
196
+
// Fetch and sync to database (upsert)
197
+
$post = $helper->sync('at://did:plc:xxx/app.bsky.feed.post/abc123');
198
+
```
199
+
200
+
The helper automatically resolves the DID to find the correct PDS endpoint, so it works with any AT Protocol server - not just Bluesky.
201
+
202
+
### Using with atp-signals
203
+
204
+
Enable automatic firehose synchronization by registering the `ParitySignal`:
205
+
206
+
```php
207
+
// config/signal.php
208
+
return [
209
+
'signals' => [
210
+
\SocialDept\AtpParity\Signals\ParitySignal::class,
211
+
],
212
+
];
213
+
```
214
+
215
+
Run `php artisan signal:consume` and your models will automatically sync with matching firehose events.
216
+
217
+
### Importing Historical Data
218
+
219
+
For existing records created before you started consuming the firehose:
13
220
14
221
```bash
15
-
composer require socialdept/atp-parity
222
+
# Import a user's records
223
+
php artisan parity:import did:plc:z72i7hdynmk6r22z27h6tvur
224
+
225
+
# Check import status
226
+
php artisan parity:import-status
227
+
```
228
+
229
+
Or programmatically:
230
+
231
+
```php
232
+
use SocialDept\AtpParity\Import\ImportService;
233
+
234
+
$service = app(ImportService::class);
235
+
$result = $service->importUser('did:plc:z72i7hdynmk6r22z27h6tvur');
236
+
237
+
echo "Synced {$result->recordsSynced} records";
16
238
```
17
239
18
-
## Usage
240
+
## Documentation
241
+
242
+
For detailed documentation on specific topics:
243
+
244
+
- [Record Mappers](docs/mappers.md) - Creating and using mappers
245
+
- [Model Traits](docs/traits.md) - HasAtpRecord and SyncsWithAtp
246
+
- [atp-schema Integration](docs/atp-schema-integration.md) - Using generated DTOs
247
+
- [atp-client Integration](docs/atp-client-integration.md) - RecordHelper and fetching
248
+
- [atp-signals Integration](docs/atp-signals-integration.md) - ParitySignal and firehose sync
249
+
- [Importing](docs/importing.md) - Syncing historical data
19
250
20
-
## Change log
251
+
## Model Traits
21
252
22
-
Please see the [changelog](changelog.md) for more information on what has changed recently.
253
+
### HasAtpRecord
254
+
255
+
Add AT Protocol awareness to your models:
256
+
257
+
```php
258
+
use SocialDept\AtpParity\Concerns\HasAtpRecord;
259
+
260
+
class Post extends Model
261
+
{
262
+
use HasAtpRecord;
263
+
264
+
protected $fillable = ['content', 'atp_uri', 'atp_cid'];
265
+
}
266
+
```
267
+
268
+
Available methods:
269
+
270
+
```php
271
+
// Get AT Protocol metadata
272
+
$post->getAtpUri(); // at://did:plc:xxx/app.bsky.feed.post/rkey
273
+
$post->getAtpCid(); // bafyre...
274
+
$post->getAtpDid(); // did:plc:xxx (extracted from URI)
275
+
$post->getAtpCollection(); // app.bsky.feed.post (extracted from URI)
276
+
$post->getAtpRkey(); // rkey (extracted from URI)
277
+
278
+
// Check sync status
279
+
$post->hasAtpRecord(); // true if synced
280
+
281
+
// Convert to record DTO
282
+
$record = $post->toAtpRecord();
283
+
284
+
// Query scopes
285
+
Post::withAtpRecord()->get(); // Only synced posts
286
+
Post::withoutAtpRecord()->get(); // Only unsynced posts
287
+
Post::whereAtpUri($uri)->first(); // Find by URI
288
+
```
289
+
290
+
### SyncsWithAtp
291
+
292
+
Extended trait for bidirectional sync tracking:
293
+
294
+
```php
295
+
use SocialDept\AtpParity\Concerns\SyncsWithAtp;
296
+
297
+
class Post extends Model
298
+
{
299
+
use SyncsWithAtp;
300
+
}
301
+
```
302
+
303
+
Additional methods:
304
+
305
+
```php
306
+
// Track sync status
307
+
$post->getAtpSyncedAt(); // Last sync timestamp
308
+
$post->hasLocalChanges(); // True if updated since last sync
309
+
310
+
// Mark as synced
311
+
$post->markAsSynced($uri, $cid);
312
+
313
+
// Update from remote
314
+
$post->updateFromRecord($record, $uri, $cid);
315
+
```
316
+
317
+
## Database Migration
318
+
319
+
Add AT Protocol columns to your models:
320
+
321
+
```php
322
+
Schema::table('posts', function (Blueprint $table) {
323
+
$table->string('atp_uri')->nullable()->unique();
324
+
$table->string('atp_cid')->nullable();
325
+
$table->timestamp('atp_synced_at')->nullable(); // For SyncsWithAtp
326
+
});
327
+
```
328
+
329
+
## Configuration
330
+
331
+
```php
332
+
// config/parity.php
333
+
return [
334
+
// Registered mappers
335
+
'mappers' => [
336
+
App\AtpMappers\PostMapper::class,
337
+
App\AtpMappers\ProfileMapper::class,
338
+
],
339
+
340
+
// Column names for AT Protocol metadata
341
+
'columns' => [
342
+
'uri' => 'atp_uri',
343
+
'cid' => 'atp_cid',
344
+
],
345
+
];
346
+
```
347
+
348
+
## Creating Custom Records
349
+
350
+
Extend the `Record` base class for custom AT Protocol records:
351
+
352
+
```php
353
+
use SocialDept\AtpParity\Data\Record;
354
+
use Carbon\Carbon;
355
+
356
+
class PostRecord extends Record
357
+
{
358
+
public function __construct(
359
+
public readonly string $text,
360
+
public readonly Carbon $createdAt,
361
+
public readonly ?array $facets = null,
362
+
) {}
363
+
364
+
public static function getLexicon(): string
365
+
{
366
+
return 'app.bsky.feed.post';
367
+
}
368
+
369
+
public static function fromArray(array $data): static
370
+
{
371
+
return new static(
372
+
text: $data['text'],
373
+
createdAt: Carbon::parse($data['createdAt']),
374
+
facets: $data['facets'] ?? null,
375
+
);
376
+
}
377
+
}
378
+
```
379
+
380
+
The `Record` class extends `atp-schema`'s `Data` and implements `atp-client`'s `Recordable` interface, ensuring full compatibility with the ecosystem.
381
+
382
+
## Requirements
383
+
384
+
- PHP 8.2+
385
+
- Laravel 10, 11, or 12
386
+
- [socialdept/atp-schema](https://github.com/socialdept/atp-schema) ^0.3
387
+
- [socialdept/atp-client](https://github.com/socialdept/atp-client) ^0.0
388
+
- [socialdept/atp-resolver](https://github.com/socialdept/atp-resolver) ^1.1
389
+
- [socialdept/atp-signals](https://github.com/socialdept/atp-signals) ^1.1
23
390
24
391
## Testing
25
392
···
27
394
composer test
28
395
```
29
396
30
-
## Contributing
397
+
## Resources
398
+
399
+
- [AT Protocol Documentation](https://atproto.com/)
400
+
- [Bluesky API Docs](https://docs.bsky.app/)
401
+
- [atp-schema](https://github.com/socialdept/atp-schema) - Generated AT Protocol DTOs
402
+
- [atp-client](https://github.com/socialdept/atp-client) - AT Protocol HTTP client
403
+
- [atp-signals](https://github.com/socialdept/atp-signals) - Firehose event consumer
404
+
405
+
## Support & Contributing
406
+
407
+
Found a bug or have a feature request? [Open an issue](https://github.com/socialdept/atp-parity/issues).
31
408
32
-
Please see [contributing.md](contributing.md) for details and a todolist.
409
+
Want to contribute? Check out the [contribution guidelines](contributing.md).
33
410
34
-
## Security
411
+
## Changelog
35
412
36
-
If you discover any security related issues, please email author@email.com instead of using the issue tracker.
413
+
Please see [changelog](changelog.md) for recent changes.
37
414
38
415
## Credits
39
416
40
-
- [Author Name][link-author]
41
-
- [All Contributors][link-contributors]
417
+
- [Miguel Batres](https://batres.co) - founder & lead maintainer
418
+
- [All contributors](https://github.com/socialdept/atp-parity/graphs/contributors)
42
419
43
420
## License
44
421
45
-
MIT. Please see the [license file](license.md) for more information.
422
+
Parity is open-source software licensed under the [MIT license](license.md).
46
423
47
-
[ico-version]: https://img.shields.io/packagist/v/socialdept/atp-parity.svg?style=flat-square
48
-
[ico-downloads]: https://img.shields.io/packagist/dt/socialdept/atp-parity.svg?style=flat-square
49
-
[ico-travis]: https://img.shields.io/travis/socialdept/atp-parity/master.svg?style=flat-square
50
-
[ico-styleci]: https://styleci.io/repos/12345678/shield
424
+
---
51
425
52
-
[link-packagist]: https://packagist.org/packages/socialdept/atp-parity
53
-
[link-downloads]: https://packagist.org/packages/socialdept/atp-parity
54
-
[link-travis]: https://travis-ci.org/socialdept/atp-parity
55
-
[link-styleci]: https://styleci.io/repos/12345678
56
-
[link-author]: https://github.com/social-dept
57
-
[link-contributors]: ../../contributors
426
+
**Built for the Federation** - By Social Dept.