Add config-based database attach API
Implements config-based ATTACH API with explicit attach()/detach() methods.
This supersedes #327 and implements the config-based approach suggested by maintainers in the previous PR discussion.
Changes
Core API (libsql-core)
AttachConfiginterface withaliasandpathfieldsattach?: AttachConfig[]option increateClient()config for static attachmentsclient.attach(alias, path)andclient.detach(alias)methods for dynamic attachments
Implementation (Sqlite3Client)
- Config-based static attachments applied at client creation
- Explicit attach/detach methods for runtime attachment
- Automatic persistence across
transaction()andreconnect() - Graceful handling of missing databases (logs warnings, doesn't crash)
- Read-only mode support via
file:///path?mode=roURIs
Remote Clients (HTTP/WebSocket)
- Stub implementations that throw
ATTACH_NOT_SUPPORTEDerror - ATTACH only works with local SQLite files
Tests
- 13 comprehensive tests covering all use cases
- Config-based attachments
- Explicit attach/detach methods
- Persistence across transactions and reconnects
- Read-only mode
- Cross-database JOINs
- Error handling (duplicate aliases, missing files)
- Backward compatibility
Design Principles
Zero overhead: No SQL parsing or regex matching. Users explicitly control when and what to attach.
Persistence: Attachments survive transaction() (which nulls the connection) and reconnect() by re-applying from stored config.
Unified storage: Single internal array stores both config and explicit attachments, ensuring consistent behavior.
Addresses feedback from #327
- ✅ Zero overhead (no SQL parsing)
- ✅ User-controlled explicit API
- ✅ Supports both static (config) and dynamic (explicit) attachments
- ✅ Read-only mode for preventing write locks
- ✅ Graceful degradation (missing DBs log warnings)
Example Usage
Static attachments:
const client = createClient({
url: 'file:///path/to/main.db',
attach: [
{ alias: 'archive', path: 'file:///path/to/archive.db?mode=ro' },
{ alias: 'analytics', path: 'file:///path/to/analytics.db' }
]
});
// Attachments persist across transactions
const tx = await client.transaction();
await tx.execute('SELECT * FROM archive.old_messages');
Dynamic attachments:
const client = createClient({ url: 'file:///path/to/main.db' });
// Attach at runtime
await client.attach('logs', 'file:///path/to/logs.db?mode=ro');
// Use in queries
await client.execute('SELECT * FROM logs.entries WHERE date > ?', [yesterday]);
// Detach when done
await client.detach('logs');
Closes #327
@penberg this supersedes #327 based on your suggestions - can you review?
Hey @enki! The API looks great. Let's get the build errors fixed and happy to merge