Skip to content

Commit

Permalink
chore(runtime): Allow connecting to locally running libSQL servers (#223
Browse files Browse the repository at this point in the history
)
  • Loading branch information
dnys1 authored Oct 16, 2024
1 parent 17b76ba commit 3b3a4ac
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 11 deletions.
1 change: 1 addition & 0 deletions packages/celest/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## 1.0.1-wip

- chore: Remove assumptions about where the project is deployed
- chore: Allow connecting to locally running libSQL servers

## 1.0.0

Expand Down
28 changes: 17 additions & 11 deletions packages/celest/lib/src/runtime/data/connect.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import 'package:celest/src/runtime/data/connect.io.dart'
if (dart.library.js_interop) 'package:celest/src/runtime/data/connect.web.dart';
import 'package:drift/drift.dart';
import 'package:drift_hrana/drift_hrana.dart';
import 'package:logging/logging.dart';

final Logger _logger = Logger('Celest.Data');

/// Checks the connection to the database by running a simple query.
Future<Database> _checkConnection<Database extends GeneratedDatabase>(
Database db,
) async {
await db.transaction(() async {
await db.customSelect('SELECT 1').get();
});
await db.customSelect('SELECT 1').get();
return db;
}

Expand All @@ -31,16 +32,21 @@ Future<Database> connect<Database extends GeneratedDatabase>(
}
final host = context.get(hostnameVariable);
final token = context.get(tokenSecret);
if (host == null || token == null) {
if (host == null) {
throw StateError(
'Missing database hostname or token for $name. '
'Please set the `$hostnameVariable` and `$tokenSecret` values '
'in the environment or Celest configuration file.',
'Missing database hostname $name. '
'Set the `$hostnameVariable` value in the environment or Celest '
'configuration file to connect.',
);
}
final hostUri = Uri.tryParse(host) ?? Uri(scheme: 'libsql', host: host);
if (token == null) {
_logger.fine(
'Missing database token for $name. Expecting a secret named '
'`$tokenSecret` in the environment or Celest configuration file.',
);
_logger.fine('Connecting to $hostUri without a token.');
}
final connector = HranaDatabase(
Uri(scheme: 'libsql', host: host),
jwtToken: token,
);
final connector = HranaDatabase(hostUri, jwtToken: token);
return _checkConnection(factory(connector));
}
71 changes: 71 additions & 0 deletions packages/celest/test/runtime/data/connect_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
@TestOn('vm')
@Tags(['e2e'])
library;

import 'dart:io';

import 'package:celest/src/config/config_values.dart';
import 'package:celest/src/core/context.dart';
import 'package:celest/src/runtime/data/connect.dart';
import 'package:drift/drift.dart';
import 'package:drift_hrana/drift_hrana.dart';
import 'package:platform/platform.dart';
import 'package:test/fake.dart';
import 'package:test/test.dart';

Future<int> _findOpenPort() async {
final server = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0);
final port = server.port;
await server.close();
return port;
}

Future<Uri> _startLibSqlServer() async {
final port = await _findOpenPort();
final process = await Process.start('turso', ['dev', '--port', '$port']);
addTearDown(process.kill);
return Uri.parse('ws://localhost:$port');
}

void main() {
group('connect', () {
test('allows local libSQL URIs', () async {
final host = await _startLibSqlServer();
final platform = FakePlatform(
environment: {
'CELEST_DATABASE_HOST': host.toString(),
},
);
Context.current.put(env.environment, 'production');
Context.current.put(ContextKey.platform, platform);
await connect(
Context.current,
name: 'test',
factory: expectAsync1((executor) {
expect(executor, isA<HranaDatabase>());
return _FakeDatabase();
}),
hostnameVariable: const env('CELEST_DATABASE_HOST'),
tokenSecret: const secret('CELEST_DATABASE_TOKEN'),
);
});
});
}

final class _FakeDatabase extends Fake implements GeneratedDatabase {
@override
Selectable<QueryRow> customSelect(
String query, {
List<Variable<Object>> variables = const [],
Set<ResultSetImplementation<dynamic, dynamic>> readsFrom = const {},
}) {
return _FakeSelectable();
}
}

final class _FakeSelectable extends Fake implements Selectable<Never> {
@override
Future<List<Never>> get() {
return Future.value([]);
}
}

0 comments on commit 3b3a4ac

Please sign in to comment.