Skip to content

Commit

Permalink
Add support for Turso's embedded replica feature
Browse files Browse the repository at this point in the history
  • Loading branch information
richan-fongdasen committed Apr 14, 2024
1 parent 6721b0e commit 4f66009
Show file tree
Hide file tree
Showing 18 changed files with 561 additions and 70 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ phpunit.xml
phpstan.neon
testbench.yaml
vendor
package-lock.json
node_modules
*.swp
*.sqlite
*.sqlite-shm
*.sqlite-wal
*.sqlite-client_wal_index
100 changes: 88 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
[![codecov](https://codecov.io/gh/richan-fongdasen/turso-laravel/graph/badge.svg?token=eKJSttyUGc)](https://codecov.io/gh/richan-fongdasen/turso-laravel)
[![Total Downloads](https://img.shields.io/packagist/dt/richan-fongdasen/turso-laravel.svg?style=flat-square)](https://packagist.org/packages/richan-fongdasen/turso-laravel)

This package provides a Turso database driver for Laravel. It allows you to use Turso database as your database driver in Laravel application. The database driver is implemented using HTTP client to communicate with the Turso database server.
This package provides a Turso database driver for Laravel, allowing you to use Turso as your database backend in Laravel applications. The driver communicates with the Turso database server using an HTTP client.

## Unsupported Features

There are some features that are not supported by this package yet. Here are the list of unsupported features:
Some features are not yet supported by this package:

- Creating and dropping database
- [Database Transactions](https://turso.tech/blog/bring-your-own-sdk-with-tursos-http-api-ff4ccbed)
Expand All @@ -25,48 +25,124 @@ There are some features that are not supported by this package yet. Here are the

- PHP 8.2 or higher
- Laravel 11.0 or higher
- Node.js 16 or higher

## Installation

You can install the package via composer:
You can install the package via Composer:

```bash
composer require richan-fongdasen/turso-laravel
```

To use Turso as your database driver in Laravel, you need to append the following configuration to the `connections` array in your `config/database.php` file:
To use Turso as your database driver in Laravel, append the following configuration to the `connections` array in your `config/database.php` file:

```php
'turso' => [
'driver' => 'turso',
'turso_url' => env('DB_URL', 'http://localhost:8080'),
'database' => null,
'db_url' => env('DB_URL', 'http://localhost:8080'),
'access_token' => env('DB_ACCESS_TOKEN'),
'db_replica' => env('DB_REPLICA'),
'database' => null, // Leave this null
'prefix' => env('DB_PREFIX', ''),
'access_token' => env('DB_ACCESS_TOKEN', null),
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
],
```

### Publishing Configuration and Sync Script

Publish the configuration file and sync script by running the following command:

```bash
php artisan vendor:publish --provider="RichanFongdasen\Turso\TursoLaravelServiceProvider"
```

The above command publishes the following files:

- `config/turso-laravel.php`
- `turso-sync.mjs`

### Installing Node.js Dependencies

The Turso database driver requires Node.js to run the sync script. Install the Node.js dependencies by running the following command:

```bash
npm install @libsql/client
```

## Configuration

In Laravel application, The database driver configuration is stored in your `.env` file. Here is the list of available configuration for Turso database driver:
In Laravel applications, the database driver configuration is stored in your `.env` file. Here are the available configurations for the Turso database driver:

```bash
DB_CONNECTION=turso
DB_URL=http://localhost:8080
DB_PREFIX=
DB_ACCESS_TOKEN=
DB_REPLICA=
DB_PREFIX=
DB_FOREIGN_KEYS=true
```

| ENV Variable Name | Description |
| :---------------- | :--------------------------------------------------------------------------------------------- |
| DB_URL | The Turso database server URL. E.g: `https://[databaseName]-[organizationName].turso.io` |
| DB_ACCESS_TOKEN | (Optional) The access token to access the Turso database server. |
| DB_REPLICA | (Optional) The full path to the local embedded replica database file. E.g: `/tmp/turso.sqlite` |
| DB_PREFIX | (Optional) The database table prefix. |
| DB_FOREIGN_KEYS | Enable or disable foreign key constraints, default is `true`. |

## Usage

For local development, you can use the local Turso database server that is provided by the Turso database team for development purposes. You can find the instruction to run the local Turso database server in the [Turso CLI documentation](https://docs.turso.tech/local-development#turso-cli).
For local development, you can use the local Turso database server provided by the Turso team. Refer to the [Turso CLI documentation](https://docs.turso.tech/local-development#turso-cli) for instructions on running the local Turso database server.

The Turso database driver should work as expected with Laravel's Query Builder and Eloquent ORM.

The driver supports the embedded replica feature. If you're unfamiliar with this feature, refer to the [Turso embedded replica article](https://turso.tech/blog/introducing-embedded-replicas-deploy-turso-anywhere-2085aa0dc242) for more information.

### Running the sync script from artisan command

The Turso database driver should work as expected with Laravel Query Builder and Eloquent ORM.
Run the sync script manually using the following Artisan command:

```bash
php artisan turso:sync
```

### Running the sync script programmatically

Run the sync script programmatically using the following code:

```php
use Illuminate\Support\Facades\DB;
use RichanFongdasen\Turso\Facades\Turso;

if ( DB::hasUpdated() ) {
// Run the sync script immediately
Turso::sync();

// Run the sync script in the background
Turso::backgroundSync();
}
```

### Disabling/enabling the read replica

If you have configured the read replica in your database configuration, the database connection to the Turso embedded read replica is enabled by default. However, you may want to disable it when performing chained read-write operations directly on the remote database. You can disable the read replica using the following code:

```php
use RichanFongdasen\Turso\Facades\Turso;

// Disable the read replica
Turso::disableReadReplica();

// Re-enable the read replica
// Note that the replica may not contain the latest data
// related to recent write operations performed on the remote database
Turso::enableReadReplica();
```

## Debugging

There is a way to debug the HTTP request and response that is sent and received by the Turso database client. Here is the example of how to enable the debugging feature:
To debug the HTTP requests and responses sent and received by the Turso database client, enable the debugging feature as follows:

```php
Turso::enableQueryLog();
Expand Down
8 changes: 7 additions & 1 deletion config/turso-laravel.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
<?php

// config for RichanFongdasen/TursoLaravel
return [];
return [
'sync' => [
'script_filename' => 'turso-sync.mjs',
'script_path' => realpath(__DIR__ . '/..'),
'timeout' => 60,
],
];
9 changes: 9 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"private": true,
"scripts": {
"sync": "node turso-sync.js"
},
"devDependencies": {
"@libsql/client": "^0.6.0"
}
}
45 changes: 45 additions & 0 deletions src/Commands/TursoSyncCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace RichanFongdasen\Turso\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Process;

class TursoSyncCommand extends Command
{
public $signature = 'turso:sync';

public $description = 'Sync changes from the remote database to the local replica manually.';

protected function compileRunProcess(): string
{
return sprintf(
'node %s "%s" "%s" "%s"',
config('turso-laravel.sync.script_filename'),
config('database.connections.turso.db_url'),
config('database.connections.turso.access_token'),
config('database.connections.turso.db_replica'),
);
}

public function handle(): int
{
$timeout = (int) config('turso-laravel.sync.timeout');

$result = Process::timeout($timeout)
->path(config('turso-laravel.sync.script_path') ?? base_path())
->run($this->compileRunProcess());

if ($result->failed()) {
$this->error($result->errorOutput());

return self::FAILURE;
}

$this->info($result->output());

return self::SUCCESS;
}
}
52 changes: 52 additions & 0 deletions src/Database/TursoConnection.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

class TursoConnection extends SQLiteConnection
{
protected bool $hasUpdated = false;

public function __construct(TursoPDO $pdo, string $database = ':memory:', string $tablePrefix = '', array $config = [])
{
parent::__construct($pdo, $database, $tablePrefix, $config);
Expand Down Expand Up @@ -67,4 +69,54 @@ protected function getDefaultPostProcessor(): TursoQueryProcessor
{
return new TursoQueryProcessor();
}

/**
* Run an insert statement against the database.
*
* @param string $query
* @param array $bindings
*
* @return bool
*/
public function insert($query, $bindings = [])
{
$this->hasUpdated = true;

return parent::insert($query, $bindings);
}

/**
* Run an update statement against the database.
*
* @param string $query
* @param array $bindings
*
* @return int
*/
public function update($query, $bindings = [])
{
$this->hasUpdated = true;

return parent::update($query, $bindings);
}

/**
* Run a delete statement against the database.
*
* @param string $query
* @param array $bindings
*
* @return int
*/
public function delete($query, $bindings = [])
{
$this->hasUpdated = true;

return parent::delete($query, $bindings);
}

public function hasUpdated(): bool
{
return $this->hasUpdated;
}
}
8 changes: 4 additions & 4 deletions src/Facades/Turso.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@
namespace RichanFongdasen\Turso\Facades;

use Illuminate\Support\Facades\Facade;
use RichanFongdasen\Turso\TursoClient;
use RichanFongdasen\Turso\TursoManager;

/**
* @see \RichanFongdasen\Turso\TursoClient
* @see \RichanFongdasen\Turso\TursoHttpClient
*
* @mixin \RichanFongdasen\Turso\TursoClient
* @mixin \RichanFongdasen\Turso\TursoHttpClient
*/
class Turso extends Facade
{
protected static function getFacadeAccessor(): string
{
return TursoClient::class;
return TursoManager::class;
}
}
28 changes: 28 additions & 0 deletions src/Jobs/TursoSyncJob.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace RichanFongdasen\Turso\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Artisan;

class TursoSyncJob implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
use SerializesModels;

/**
* Execute the job.
*/
public function handle(): void
{
Artisan::call('turso:sync');
}
}
Loading

0 comments on commit 4f66009

Please sign in to comment.