Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(runtime): Allow connecting to locally running libSQL servers #223

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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([]);
}
}
Loading