Skip to content

Commit face45b

Browse files
committed
feat: add SQLite local cache for wiki structure
- Add Database class with PDO for lightweight SQLite operations - Add bookstack:sync command to sync wiki structure from API - Add bookstack:db command to query and manage local cache - Add content_hash tracking for change detection (xxh3) - Add local_path mapping for file-to-page relationships - Integrate with SyncService for automatic cache updates - Add database configuration options (enabled, path) - Add comprehensive test coverage for Database class
1 parent d883959 commit face45b

10 files changed

Lines changed: 2227 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,34 @@
22

33
All notable changes to `bookstack-sync` will be documented in this file.
44

5-
## [Unreleased]
5+
## [1.0.0] - 2025-12-25
6+
7+
### Added
8+
9+
- **SQLite Local Cache**: New local database for caching wiki structure
10+
- Reduces API calls by storing shelves, books, chapters, and pages locally
11+
- Tracks sync state with `synced_at` and `is_deleted` flags
12+
- Maps local files to remote pages with `local_path`
13+
- Detects content changes efficiently with `content_hash` (xxh3)
14+
- New Artisan commands for database management:
15+
- `bookstack:sync` - Sync wiki structure from BookStack API to local SQLite cache
16+
- `bookstack:db stats` - Show database statistics
17+
- `bookstack:db shelves|books|chapters|pages` - List cached entities
18+
- `bookstack:db path` - Show database file path and size
19+
- `bookstack:db delete` - Delete the local database
20+
- Configuration options for local database:
21+
- `BOOKSTACK_LOCAL_DB` - Enable/disable local database (default: true)
22+
- `BOOKSTACK_DB_PATH` - Custom database path (default: storage/bookstack-sync.sqlite)
23+
- Database class with PDO for lightweight SQLite operations
24+
- Content hash generation using xxh3 algorithm for change detection
25+
- Integration with SyncService for automatic local_path and content_hash tracking
26+
27+
### Changed
28+
29+
- SyncService now uses local cache to skip unchanged files during sync
30+
- BookStackSync facade includes database access via `database()` method
31+
32+
## [0.1.0] - 2025-12-14
633

734
### Added
835

README.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ A Laravel package for synchronizing Markdown documentation with [BookStack](http
1515
## Features
1616

1717
- **Bidirectional Sync**: Push local Markdown files to BookStack or pull BookStack content to local files
18+
- **Local SQLite Cache**: Reduces API calls by caching wiki structure locally with change detection
1819
- **Bookmark Conversion**: Automatically converts AI-generated bookmarks (Claude, Cursor, etc.) to BookStack's URL-encoded format
1920
- **Spanish Character Support**: Full support for UTF-8 characters (á, é, í, ó, ú, ñ, ç, ü, etc.)
2021
- **Artisan Commands**: Easy-to-use CLI commands for all operations
@@ -115,6 +116,41 @@ php artisan bookstack:search "configuration"
115116
php artisan bookstack:search "instalación" --limit=50
116117
```
117118

119+
#### Sync Wiki Structure to Local Cache
120+
121+
```bash
122+
# Sync all wiki structure to local SQLite database
123+
php artisan bookstack:sync
124+
125+
# Fresh sync (deletes existing database first)
126+
php artisan bookstack:sync --fresh
127+
128+
# Sync only specific entities
129+
php artisan bookstack:sync --no-shelves --no-chapters
130+
```
131+
132+
#### Query Local Cache
133+
134+
```bash
135+
# Show database statistics
136+
php artisan bookstack:db stats
137+
138+
# List cached entities
139+
php artisan bookstack:db shelves
140+
php artisan bookstack:db books
141+
php artisan bookstack:db chapters --book=5
142+
php artisan bookstack:db pages --book=5 --chapter=10
143+
144+
# Include deleted items
145+
php artisan bookstack:db pages --deleted
146+
147+
# Show database path and size
148+
php artisan bookstack:db path
149+
150+
# Delete local database
151+
php artisan bookstack:db delete --force
152+
```
153+
118154
### Programmatic Usage
119155

120156
```php
@@ -216,6 +252,37 @@ Configure sync behavior in `config/bookstack-sync.php`:
216252
],
217253
```
218254

255+
## Local Database Configuration
256+
257+
The package includes a local SQLite cache to reduce API calls and enable change detection:
258+
259+
```php
260+
'database' => [
261+
// Enable/disable local database caching
262+
'enabled' => env('BOOKSTACK_LOCAL_DB', true),
263+
264+
// Database path (relative to storage/ or absolute path)
265+
'path' => env('BOOKSTACK_DB_PATH', 'bookstack-sync.sqlite'),
266+
],
267+
```
268+
269+
### Environment Variables
270+
271+
```env
272+
# Enable local SQLite cache (default: true)
273+
BOOKSTACK_LOCAL_DB=true
274+
275+
# Custom database path (default: storage/bookstack-sync.sqlite)
276+
BOOKSTACK_DB_PATH=bookstack-sync.sqlite
277+
```
278+
279+
### How It Works
280+
281+
1. Run `php artisan bookstack:sync` to populate the local cache
282+
2. The cache stores all shelves, books, chapters, and pages with their metadata
283+
3. During push/pull operations, the package uses content hashes to skip unchanged files
284+
4. Deleted items in BookStack are marked with `is_deleted` flag for tracking
285+
219286
## Spanish Character Encoding Reference
220287

221288
| Character | URL Encoded |

config/bookstack-sync.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,28 @@
103103
'enabled' => env('BOOKSTACK_LOGGING', true),
104104
'channel' => env('BOOKSTACK_LOG_CHANNEL', 'stack'),
105105
],
106+
107+
/*
108+
|--------------------------------------------------------------------------
109+
| Local Database Configuration
110+
|--------------------------------------------------------------------------
111+
|
112+
| SQLite database for caching wiki structure locally.
113+
| This reduces API calls and enables offline querying of wiki structure.
114+
|
115+
| Features:
116+
| - Cache shelves, books, chapters, pages structure
117+
| - Track synchronization state (synced_at, is_deleted)
118+
| - Map local files to remote pages (local_path)
119+
| - Detect content changes efficiently (content_hash)
120+
|
121+
*/
122+
123+
'database' => [
124+
// Enable local SQLite cache
125+
'enabled' => env('BOOKSTACK_LOCAL_DB', true),
126+
127+
// Path to SQLite database file (relative to storage_path or absolute)
128+
'path' => env('BOOKSTACK_DB_PATH', 'bookstack-sync.sqlite'),
129+
],
106130
];

src/BookStackSync.php

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use AichaDigital\BookStackSync\Api\BookStackClient;
88
use AichaDigital\BookStackSync\Contracts\BookStackClientInterface;
9+
use AichaDigital\BookStackSync\Database\Database;
910
use AichaDigital\BookStackSync\DTOs\BookDTO;
1011
use AichaDigital\BookStackSync\DTOs\ChapterDTO;
1112
use AichaDigital\BookStackSync\DTOs\PageDTO;
@@ -33,9 +34,12 @@ class BookStackSync
3334

3435
private ?SyncService $syncService = null;
3536

36-
public function __construct(?BookStackClientInterface $client = null)
37+
private ?Database $database = null;
38+
39+
public function __construct(?BookStackClientInterface $client = null, ?Database $database = null)
3740
{
3841
$this->client = $client ?? $this->createDefaultClient();
42+
$this->database = $database;
3943
$this->parser = new MarkdownParser(
4044
config('bookstack-sync.markdown.convert_bookmarks', true),
4145
config('bookstack-sync.markdown.encoding', 'UTF-8')
@@ -45,6 +49,29 @@ public function __construct(?BookStackClientInterface $client = null)
4549
);
4650
}
4751

52+
/**
53+
* Get the database instance.
54+
*/
55+
public function database(): ?Database
56+
{
57+
return $this->database;
58+
}
59+
60+
/**
61+
* Set the database instance.
62+
*/
63+
public function setDatabase(?Database $database): self
64+
{
65+
$this->database = $database;
66+
67+
// Update sync service if already created
68+
if ($this->syncService !== null) {
69+
$this->syncService->setDatabase($database);
70+
}
71+
72+
return $this;
73+
}
74+
4875
/**
4976
* Get the API client instance.
5077
*/
@@ -240,6 +267,11 @@ public function sync(): SyncService
240267
if (config('bookstack-sync.sync.dry_run', false)) {
241268
$this->syncService->setDryRun(true);
242269
}
270+
271+
// Inject database if available
272+
if ($this->database !== null) {
273+
$this->syncService->setDatabase($this->database);
274+
}
243275
}
244276

245277
return $this->syncService;

src/BookStackSyncServiceProvider.php

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@
55
namespace AichaDigital\BookStackSync;
66

77
use AichaDigital\BookStackSync\Api\BookStackClient;
8+
use AichaDigital\BookStackSync\Commands\BookStackDbCommand;
89
use AichaDigital\BookStackSync\Commands\BookStackExportCommand;
910
use AichaDigital\BookStackSync\Commands\BookStackPullCommand;
1011
use AichaDigital\BookStackSync\Commands\BookStackPushCommand;
1112
use AichaDigital\BookStackSync\Commands\BookStackSearchCommand;
1213
use AichaDigital\BookStackSync\Commands\BookStackStatusCommand;
14+
use AichaDigital\BookStackSync\Commands\BookStackSyncCommand;
1315
use AichaDigital\BookStackSync\Contracts\BookStackClientInterface;
16+
use AichaDigital\BookStackSync\Database\Database;
1417
use Spatie\LaravelPackageTools\Package;
1518
use Spatie\LaravelPackageTools\PackageServiceProvider;
1619

@@ -27,6 +30,8 @@ public function configurePackage(Package $package): void
2730
BookStackPullCommand::class,
2831
BookStackExportCommand::class,
2932
BookStackSearchCommand::class,
33+
BookStackSyncCommand::class,
34+
BookStackDbCommand::class,
3035
]);
3136
}
3237

@@ -43,12 +48,41 @@ public function packageRegistered(): void
4348
);
4449
});
4550

51+
// Register the Database class as singleton (if enabled)
52+
$this->app->singleton(Database::class, function () {
53+
if (! config('bookstack-sync.database.enabled', true)) {
54+
return null;
55+
}
56+
57+
$path = config('bookstack-sync.database.path', 'bookstack-sync.sqlite');
58+
59+
// If path is relative, prepend storage_path
60+
if (! str_starts_with($path, '/')) {
61+
$path = storage_path($path);
62+
}
63+
64+
return new Database($path);
65+
});
66+
4667
// Register the main BookStackSync class as singleton
4768
$this->app->singleton(BookStackSync::class, function ($app) {
48-
return new BookStackSync($app->make(BookStackClientInterface::class));
69+
$database = null;
70+
if (config('bookstack-sync.database.enabled', true)) {
71+
try {
72+
$database = $app->make(Database::class);
73+
} catch (\Throwable) {
74+
// Database not available, continue without it
75+
}
76+
}
77+
78+
return new BookStackSync(
79+
$app->make(BookStackClientInterface::class),
80+
$database
81+
);
4982
});
5083

5184
// Alias for convenience
5285
$this->app->alias(BookStackSync::class, 'bookstack-sync');
86+
$this->app->alias(Database::class, 'bookstack-db');
5387
}
5488
}

0 commit comments

Comments
 (0)