Independent DID PLC Directory client for Dart 🦋
1. Guide 🌎
A high-performance, independent Dart library for interacting with DID PLC Directory services. This library provides a complete implementation of the DID PLC specification with zero external dependencies on atproto packages.
1.1. Features ⭐
- ✅ Zero atproto Dependencies - Completely independent implementation
- ✅ High Performance - Built-in caching, batching, and streaming support
- ✅ Type Safe - Full type safety with freezed data classes
- ✅ Comprehensive - Complete DID PLC specification support
- ✅ Well Tested - 100% test coverage with comprehensive test suite
- ✅ Modern Dart - Uses latest Dart features and null safety
- ✅ Simple API - Clean and intuitive interface for DID operations
- ✅ Efficient Caching - Built-in memory cache with TTL support
- ✅ Batch Processing - Process multiple DIDs efficiently
1.2. Installation 📦
Add this to your package's pubspec.yaml file:
dependencies:
did_plc: ^1.1.2
Then run:
dart pub get
1.3. Quick Start 🚀
import 'package:did_plc/did_plc.dart';
Future<void> main() async {
// Create a PLC client
final plc = PLC();
try {
// Fetch a DID document
final document = await plc.getDocument('did:plc:iijrtk7ocored6zuziwmqq3c');
print('DID Document: ${document.id}');
// Get operation log
final operationLog = await plc.getOperationLog('did:plc:iijrtk7ocored6zuziwmqq3c');
print('Operations: ${operationLog.log.length}');
} on NetworkException catch (e) {
print('Network error: ${e.message}');
} on PlcException catch (e) {
print('PLC error: ${e.message}');
} finally {
// Always close the client
plc.close();
}
}
1.4. API Overview 📚
Core Operations
final plc = PLC();
// Document operations
final document = await plc.getDocument(did);
final documentData = await plc.getDocumentData(did);
// Operation log operations
final operationLog = await plc.getOperationLog(did);
final auditLog = await plc.getAuditableLog(did);
final lastOperation = await plc.getLastOp(did);
// Batch operations
final documents = await plc.getDocuments([did1, did2, did3]);
// Health check
final health = await plc.health();
Advanced Features
// Custom configuration
final plc = PLC(
service: 'https://plc.directory',
cacheManager: CacheManager(
CachePolicy(
ttl: Duration(minutes: 10),
maxSize: 1000,
enableLru: true,
),
),
retryPolicy: RetryPolicy(
maxAttempts: 3,
initialDelay: Duration(seconds: 1),
backoffMultiplier: 2.0,
),
);
// Streaming large datasets
await for (final operation in plc.exportOpsStream()) {
print('Processing: ${operation.did}');
}
1.5. Advanced Features 🔧
Caching
Built-in intelligent caching reduces network requests and improves performance:
final plc = PLC(
cacheManager: CacheManager(
CachePolicy(
ttl: Duration(minutes: 15), // Cache for 15 minutes
maxSize: 2000, // Maximum 2000 entries
enableLru: true, // LRU eviction
),
),
);
// Remember to call `plc.close()` when done — it also disposes the cache
// manager and releases its resources.
Batch Processing
Efficiently process multiple DIDs in parallel:
final dids = ['did:plc:abc123', 'did:plc:def456', 'did:plc:ghi789'];
final documents = await plc.getDocuments(dids);
// Process results
for (final entry in documents.entries) {
print('${entry.key}: ${entry.value.id}');
}
Streaming
Handle large datasets efficiently with streaming:
await for (final entry in plc.exportOpsStream(
after: DateTime.now().subtract(Duration(days: 1)),
count: 1000,
)) {
// Process each entry as it arrives
print('Processing: ${entry.did}');
}
Resource Management
Proper resource management:
final plc = PLC();
try {
final document = await plc.getDocument('did:plc:example');
print('Document: ${document.id}');
} finally {
// Always close the client
plc.close();
}
Cryptography & Operation Building
Since v1.1.0 the package ships real ECDSA cryptography (secp256k1 / P-256, with
RFC 6979 deterministic k, low-S normalization, DAG-CBOR canonicalization, and
correct did:plc derivation) together with fluent builders for constructing
operations and DID documents. All of the following are exported from
package:did_plc/did_plc.dart:
OperationBuilder— fluent builder for a PLC operation map (OperationBuilder.create()/.update(), thenbuild()/buildUnsafe()).DidBuilder— fluent builder for a DID document.CryptoKey/KeyManager— key material anddid:keyhandling.KeyType.secp256k1andKeyType.p256are supported.PlcSigner— signs an operation (signOperation,signRawOperation).PlcVerifier— verifies a signature or an operation chain (verifyOperation,verifyRawOperation,verifyOperationChain,verifyAuditLog).
// Generate a signing key and derive its did:key.
final signingKey = CryptoKey.generate(KeyType.secp256k1);
final signer = PlcSigner();
final verifier = PlcVerifier();
// Build an unsigned operation.
final unsigned = OperationBuilder.create()
.rotationKeys([signingKey.toDidKey()])
.addVerificationMethod('atproto', signingKey.toDidKey())
.addAlsoKnownAs('at://alice.example.com')
.addService(
'atproto_pds',
{'type': 'AtprotoPersonalDataServer', 'endpoint': 'https://pds.example.com'},
)
.build();
// Sign the raw operation map and verify it against the rotation keys.
final signature = signer.signRawOperation(unsigned, signingKey);
final signed = {...unsigned, 'sig': signature};
final result = verifier.verifyRawOperation(signed, [signingKey.toDidKey()]);
print('Valid: ${result.isValid}');
See example/crypto_example.dart for a full walkthrough of analyzing keys and operations.
1.6. Performance Best Practices 🚄
1. Use Caching Effectively
// Configure appropriate cache settings
final plc = PLC(
cacheManager: CacheManager(
CachePolicy(
ttl: Duration(minutes: 10), // Adjust based on your use case
maxSize: 1000, // Prevent memory bloat
),
),
);
2. Batch Multiple Requests
// Instead of multiple individual requests
final doc1 = await plc.getDocument(did1);
final doc2 = await plc.getDocument(did2);
final doc3 = await plc.getDocument(did3);
// Use batch processing
final documents = await plc.getDocuments([did1, did2, did3]);
3. Use Streaming for Large Datasets
// For processing large amounts of data
await for (final operation in plc.exportOpsStream(count: 10000)) {
// Process incrementally to avoid memory issues
print('Processing: ${operation.did}');
}
4. Configure Retry Policies
final plc = PLC(
retryPolicy: RetryPolicy(
maxAttempts: 3,
initialDelay: Duration(milliseconds: 500),
backoffMultiplier: 2.0,
),
);
5. Proper Resource Management
Future<void> processDocuments() async {
final plc = PLC();
try {
// Your processing logic
await plc.getDocument(did);
} finally {
// Always close to free resources
plc.close();
}
}
1.7. Examples 💡
See the examples directory for complete working examples:
- Basic Usage - Simple DID document retrieval
- Caching Example - Advanced caching configuration
- Streaming Example - Large dataset processing
- Batch Processing - Efficient multiple DID handling
- Cryptographic Operations - Creating and verifying operations
- Error Handling - Comprehensive error management
For more detailed documentation, see: