Installation
composer require joshcirre/duo
npm install duo
Add the Vite plugin to your vite.config.ts:
import duo from 'duo/vite';
export default defineConfig({
plugins: [duo()],
});
Enable Duo for a Livewire Component
Add the Duo trait to your Livewire component:
use Joshcirre\Duo\Livewire\Duo;
class UserList extends Component {
use Duo;
public function mount() {
$this->syncModel(User::class);
}
}
First Use Case
$this->getCachedUsers(); // Returns IndexedDB data if offline
$this->syncModel(User::class, ['id' => 1]); // Force sync for a specific record
Joshcirre\Duo\Livewire\Duonode_modules/duo/docs/vite.mdresources/js/types/duo.d.tsModel Syncing
// In Livewire component
use Duo;
public function mount() {
$this->syncModel(User::class); // Syncs entire table
$this->syncModel(User::class, ['id' => 1]); // Syncs specific record
}
Offline-First Data Access
// Returns cached data if offline, otherwise fetches fresh
$users = $this->getCachedUsers();
Optimistic UI Updates
// Update UI instantly, syncs in background
$this->updateUserOptimistically(1, ['name' => 'New Name']);
Volt Components: Duo works with Volt out-of-the-box. Add the trait to your Volt class:
use Joshcirre\Duo\Volt\Duo;
class UserVolt extends Component {
use Duo;
}
Custom Sync Logic
Override syncModel() to add pre/post hooks:
protected function syncModel(string $model, ?array $where = null): void {
// Custom logic before sync
parent::syncModel($model, $where);
// Custom logic after sync
}
TypeScript Usage Access generated types in your frontend:
import { User } from '@/types/duo';
const users: User[] = await duo.get('users');
Manual Sync Triggers Force sync via Alpine.js:
<button @click="duo.sync('users')">Refresh Data</button>
| Pattern | Implementation | Example |
|---|---|---|
| Initial Load | syncModel() in mount() |
$this->syncModel(User::class); |
| Optimistic Update | updateOptimistically() |
$this->updateUserOptimistically(1, [...]); |
| Offline Detection | duo.isOnline() in Alpine.js |
<span x-text="duo.isOnline() ? 'Online' : 'Offline'"> |
| Sync Status | <x-duo::sync-status /> Blade component |
Automatically shows sync state |
Schema Mismatches
php artisan duo:schema:update to regenerate types.Livewire Property Conflicts
$duoCachedUsers). Conflicts may arise with existing Livewire properties.protected scope:
protected $duoCachedUsers;
Offline Sync Queue
config/duo.php:
'sync_batch_size' => 50,
TypeScript Errors
resources/js/types/duo.d.ts and run:
npm run duo:types
Vite Hot Reload
vite.config.ts:
optimizeDeps({
exclude: ['duo'],
}),
Enable Logging
Add to .env:
DUO_LOG_LEVEL=debug
Logs appear in browser console and Laravel logs.
Check Sync Status Use the built-in debug component:
<x-duo::debug />
Clear IndexedDB For testing, clear Duo’s IndexedDB store via:
php artisan duo:clear
Or in browser dev tools: IndexedDB > Duo > Clear.
Custom Sync Handlers
Override Joshcirre\Duo\Sync\SyncHandler to add custom logic:
namespace App\Duo;
use Joshcirre\Duo\Sync\SyncHandler as BaseHandler;
class CustomSyncHandler extends BaseHandler {
protected function beforeSync() {
// Custom logic
}
}
Register in config/duo.php:
'sync_handler' => App\Duo\CustomSyncHandler::class,
Custom IndexedDB Schema Extend the schema generator:
namespace App\Duo;
use Joshcirre\Duo\Schema\SchemaGenerator;
class CustomSchemaGenerator extends SchemaGenerator {
protected function getCustomFields() {
return ['custom_field' => 'string'];
}
}
Register in config/duo.php:
'schema_generator' => App\Duo\CustomSchemaGenerator::class,
Alpine.js Extensions Extend Duo’s Alpine.js functionality:
duo.extend({
async customMethod() {
return await duo.get('custom_data');
}
});
Automatic Sync Interval
Defaults to 5 minutes when offline. Adjust in config/duo.php:
'offline_sync_interval' => 300, // seconds
Model Whitelisting Duo syncs all models by default. Restrict to specific models:
'enabled_models' => [
'App\Models\User',
'App\Models\Post',
],
Volt Integration
Ensure your Volt components extend Duo trait before Component:
use Joshcirre\Duo\Volt\Duo;
use Livewire\Component;
class UserVolt extends Component {
use Duo; // Must come before Component
}
Lazy-Load Large Tables Use pagination in your sync:
$this->syncModel(User::class, [], ['limit' => 100]);
Debounce Rapid Updates Add a debounce layer for frequent updates:
import { debounce } from 'lodash';
duo.on('update', debounce(async (data) => {
await duo.sync('users');
}, 500));
Compress Payloads
Enable payload compression in config/duo.php:
'compress_payloads' => true,
How can I help you explore Laravel packages today?