Skip to content

Commit 028f651

Browse files
committed
Allow passing options to workers when opening databases
1 parent 135bbb5 commit 028f651

File tree

9 files changed

+111
-48
lines changed

9 files changed

+111
-48
lines changed

sqlite3_web/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.3.0-dev
2+
3+
- Allow passing data to worker when opening databases.
4+
15
## 0.2.2
26

37
- Recover from worker errors at startup.

sqlite3_web/example/controller.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ final class ExampleController extends DatabaseController {
1212
}
1313

1414
@override
15-
Future<WorkerDatabase> openDatabase(
16-
WasmSqlite3 sqlite3, String path, String vfs) async {
15+
Future<WorkerDatabase> openDatabase(WasmSqlite3 sqlite3, String path,
16+
String vfs, JSAny? additionalOptions) async {
1717
return ExampleDatabase(database: sqlite3.open(path, vfs: vfs));
1818
}
1919
}

sqlite3_web/lib/src/client.dart

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -403,14 +403,16 @@ final class DatabaseClient implements WebSqlite {
403403
WorkerConnection connection) async {
404404
SimpleSuccessResponse response;
405405
try {
406-
response = await connection.sendRequest(
407-
CompatibilityCheck(
408-
requestId: 0,
409-
type: MessageType.dedicatedCompatibilityCheck,
410-
databaseName: databaseName,
411-
),
412-
MessageType.simpleSuccessResponse,
413-
);
406+
response = await connection
407+
.sendRequest(
408+
CompatibilityCheck(
409+
requestId: 0,
410+
type: MessageType.dedicatedCompatibilityCheck,
411+
databaseName: databaseName,
412+
),
413+
MessageType.simpleSuccessResponse,
414+
)
415+
.timeout(_workerInitializationTimeout);
414416
} on Object {
415417
return;
416418
}
@@ -451,14 +453,16 @@ final class DatabaseClient implements WebSqlite {
451453
Future<void> sharedCompatibilityCheck(WorkerConnection connection) async {
452454
SimpleSuccessResponse response;
453455
try {
454-
response = await connection.sendRequest(
455-
CompatibilityCheck(
456-
requestId: 0,
457-
type: MessageType.sharedCompatibilityCheck,
458-
databaseName: databaseName,
459-
),
460-
MessageType.simpleSuccessResponse,
461-
);
456+
response = await connection
457+
.sendRequest(
458+
CompatibilityCheck(
459+
requestId: 0,
460+
type: MessageType.sharedCompatibilityCheck,
461+
databaseName: databaseName,
462+
),
463+
MessageType.simpleSuccessResponse,
464+
)
465+
.timeout(_workerInitializationTimeout);
462466
} on Object {
463467
return;
464468
}
@@ -522,7 +526,7 @@ final class DatabaseClient implements WebSqlite {
522526

523527
@override
524528
Future<Database> connect(String name, StorageMode type, AccessMode access,
525-
{bool onlyOpenVfs = false}) async {
529+
{bool onlyOpenVfs = false, JSAny? additionalOptions}) async {
526530
await startWorkers();
527531

528532
WorkerConnection connection;
@@ -553,6 +557,7 @@ final class DatabaseClient implements WebSqlite {
553557
databaseName: name,
554558
storageMode: type.resolveToVfs(shared),
555559
onlyOpenVfs: onlyOpenVfs,
560+
additionalData: additionalOptions,
556561
),
557562
MessageType.simpleSuccessResponse,
558563
);
@@ -564,7 +569,7 @@ final class DatabaseClient implements WebSqlite {
564569

565570
@override
566571
Future<ConnectToRecommendedResult> connectToRecommended(String name,
567-
{bool onlyOpenVfs = false}) async {
572+
{bool onlyOpenVfs = false, JSAny? additionalOptions}) async {
568573
final probed = await runFeatureDetection(databaseName: name);
569574

570575
// If we have an existing database in storage, we want to keep using that
@@ -593,8 +598,8 @@ final class DatabaseClient implements WebSqlite {
593598

594599
final (storage, access) = availableImplementations.firstOrNull ??
595600
(StorageMode.inMemory, AccessMode.inCurrentContext);
596-
final database =
597-
await connect(name, storage, access, onlyOpenVfs: onlyOpenVfs);
601+
final database = await connect(name, storage, access,
602+
onlyOpenVfs: onlyOpenVfs, additionalOptions: additionalOptions);
598603

599604
return ConnectToRecommendedResult(
600605
database: database,
@@ -621,6 +626,8 @@ final class DatabaseClient implements WebSqlite {
621626
// because we can actually share database resources between tabs.
622627
return a.$2.index.compareTo(b.$2.index);
623628
}
629+
630+
static const _workerInitializationTimeout = Duration(seconds: 1);
624631
}
625632

626633
extension on StorageMode {

sqlite3_web/lib/src/database.dart

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,12 @@ abstract base class DatabaseController {
2323
///
2424
/// This should virtually always call `sqlite3.open(path, vfs: vfs)` and wrap
2525
/// the result in a [WorkerDatabase] subclass.
26+
///
27+
/// The [additionalData] can be set by clients when opening the database. It
28+
/// might be useful to transport additional options relevant when opening the
29+
/// database.
2630
Future<WorkerDatabase> openDatabase(
27-
WasmSqlite3 sqlite3, String path, String vfs);
31+
WasmSqlite3 sqlite3, String path, String vfs, JSAny? additionalData);
2832

2933
/// Handles custom requests from clients that are not bound to a database.
3034
///
@@ -190,8 +194,12 @@ abstract class WebSqlite {
190194
/// on the worker when it's first used.
191195
/// Only opening the VFS can be used to, for instance, check if the database
192196
/// already exists and to initialize it manually if it doesn't.
197+
///
198+
/// The optional [additionalOptions] must be sendable over message ports and
199+
/// is passed to [DatabaseController.openDatabase] on the worker opening the
200+
/// database.
193201
Future<Database> connect(String name, StorageMode type, AccessMode access,
194-
{bool onlyOpenVfs = false});
202+
{bool onlyOpenVfs = false, JSAny? additionalOptions});
195203

196204
/// Starts a feature detection via [runFeatureDetection] and then [connect]s
197205
/// to the best database available.
@@ -202,8 +210,12 @@ abstract class WebSqlite {
202210
/// on the worker when it's first used.
203211
/// Only opening the VFS can be used to, for instance, check if the database
204212
/// already exists and to initialize it manually if it doesn't.
213+
///
214+
/// The optional [additionalOptions] must be sendable over message ports and
215+
/// is passed to [DatabaseController.openDatabase] on the worker opening the
216+
/// database.
205217
Future<ConnectToRecommendedResult> connectToRecommended(String name,
206-
{bool onlyOpenVfs = false});
218+
{bool onlyOpenVfs = false, JSAny? additionalOptions});
207219

208220
/// Entrypoints for workers hosting datbases.
209221
static void workerEntrypoint({
@@ -224,7 +236,10 @@ abstract class WebSqlite {
224236
DatabaseController? controller,
225237
}) {
226238
return DatabaseClient(
227-
worker, wasmModule, controller ?? const _DefaultDatabaseController());
239+
worker,
240+
wasmModule,
241+
controller ?? const _DefaultDatabaseController(),
242+
);
228243
}
229244

230245
/// Connects to an endpoint previously obtained with [Database.additionalConnection].
@@ -255,8 +270,8 @@ final class _DefaultDatabaseController extends DatabaseController {
255270
}
256271

257272
@override
258-
Future<WorkerDatabase> openDatabase(
259-
WasmSqlite3 sqlite3, String path, String vfs) async {
273+
Future<WorkerDatabase> openDatabase(WasmSqlite3 sqlite3, String path,
274+
String vfs, JSAny? additionalOptions) async {
260275
return _DefaultWorkerDatabase(sqlite3.open(path, vfs: vfs));
261276
}
262277
}

sqlite3_web/lib/src/protocol.dart

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ enum MessageType<T extends Message> {
4747
/// Since we're using unsafe JS interop here, these can't be mangled by dart2js.
4848
/// Thus, we should keep them short.
4949
class _UniqueFieldNames {
50-
static const action = 'a';
50+
static const action = 'a'; // Only used in StreamRequest
51+
static const additionalData = 'a'; // only used in OpenRequest
5152
static const buffer = 'b';
5253
static const columnNames = 'c';
5354
static const databaseId = 'd';
@@ -214,12 +215,16 @@ final class OpenRequest extends Request {
214215
final FileSystemImplementation storageMode;
215216
final bool onlyOpenVfs;
216217

218+
/// Additional data passsed to `DatabaseController.openDatabase`.
219+
final JSAny? additionalData;
220+
217221
OpenRequest({
218222
required super.requestId,
219223
required this.wasmUri,
220224
required this.databaseName,
221225
required this.storageMode,
222226
required this.onlyOpenVfs,
227+
this.additionalData,
223228
});
224229

225230
factory OpenRequest.deserialize(JSObject object) {
@@ -230,9 +235,11 @@ final class OpenRequest extends Request {
230235
wasmUri:
231236
Uri.parse((object[_UniqueFieldNames.wasmUri] as JSString).toDart),
232237
requestId: object.requestId,
238+
// The onlyOpenVfs and transformedVfsName fields were not set in earlier
239+
// clients.
233240
onlyOpenVfs:
234-
// The onlyOpenVfs field was not set in earlier clients.
235241
(object[_UniqueFieldNames.onlyOpenVfs] as JSBoolean?)?.toDart == true,
242+
additionalData: object[_UniqueFieldNames.additionalData],
236243
);
237244
}
238245

@@ -246,6 +253,7 @@ final class OpenRequest extends Request {
246253
object[_UniqueFieldNames.storageMode] = storageMode.toJS;
247254
object[_UniqueFieldNames.wasmUri] = wasmUri.toString().toJS;
248255
object[_UniqueFieldNames.onlyOpenVfs] = onlyOpenVfs.toJS;
256+
object[_UniqueFieldNames.additionalData] = additionalData;
249257
}
250258
}
251259

@@ -647,6 +655,7 @@ final class StreamRequest extends Request {
647655
/// updates.
648656
final bool action;
649657

658+
@override
650659
final MessageType<Message> type;
651660

652661
StreamRequest({

sqlite3_web/lib/src/worker.dart

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -213,8 +213,8 @@ final class _ClientConnection extends ProtocolChannel
213213
_ConnectionDatabase? connectionDatabase;
214214

215215
try {
216-
database =
217-
_runner.findDatabase(request.databaseName, request.storageMode);
216+
database = _runner.findDatabase(request.databaseName,
217+
request.storageMode, request.additionalData);
218218

219219
await (request.onlyOpenVfs ? database.vfs : database.opened);
220220

@@ -392,6 +392,7 @@ final class DatabaseState {
392392
final int id;
393393
final String name;
394394
final FileSystemImplementation mode;
395+
final JSAny? additionalOptions;
395396
int refCount = 1;
396397

397398
Future<WorkerDatabase>? _database;
@@ -402,11 +403,13 @@ final class DatabaseState {
402403
/// the database is closed.
403404
FutureOr<void> Function()? closeHandler;
404405

405-
DatabaseState(
406-
{required this.id,
407-
required this.runner,
408-
required this.name,
409-
required this.mode});
406+
DatabaseState({
407+
required this.id,
408+
required this.runner,
409+
required this.name,
410+
required this.mode,
411+
required this.additionalOptions,
412+
});
410413

411414
String get vfsName => 'vfs-web-$id';
412415

@@ -456,7 +459,7 @@ final class DatabaseState {
456459
// We still provide support for multiple databases by keeping multiple
457460
// VFS instances around.
458461
'/database',
459-
vfsName,
462+
vfsName, additionalOptions,
460463
);
461464
});
462465

@@ -599,7 +602,8 @@ final class WorkerRunner {
599602
}
600603
}
601604

602-
DatabaseState findDatabase(String name, FileSystemImplementation mode) {
605+
DatabaseState findDatabase(
606+
String name, FileSystemImplementation mode, JSAny? additionalOptions) {
603607
for (final existing in openedDatabases.values) {
604608
if (existing.refCount != 0 &&
605609
existing.name == name &&
@@ -615,6 +619,7 @@ final class WorkerRunner {
615619
runner: this,
616620
name: name,
617621
mode: mode,
622+
additionalOptions: additionalOptions,
618623
);
619624
}
620625

sqlite3_web/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: sqlite3_web
22
description: Utilities to simplify accessing sqlite3 on the web, with automated feature detection.
3-
version: 0.2.2
3+
version: 0.3.0-dev
44
homepage: https://github.com/simolus3/sqlite3.dart/tree/main/sqlite3_web
55
repository: https://github.com/simolus3/sqlite3.dart
66

sqlite3_web/web/controller.dart

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,20 @@ final class ExampleController extends DatabaseController {
1616
}
1717

1818
@override
19-
Future<WorkerDatabase> openDatabase(
20-
WasmSqlite3 sqlite3, String path, String vfs) async {
19+
Future<WorkerDatabase> openDatabase(WasmSqlite3 sqlite3, String path,
20+
String vfs, JSAny? additionalData) async {
2121
final raw = sqlite3.open(path, vfs: vfs);
2222
raw.createFunction(
2323
functionName: 'database_host',
2424
function: (args) => isInWorker ? 'worker' : 'document',
2525
argumentCount: const AllowedArgumentCount(0),
2626
);
27+
raw.createFunction(
28+
functionName: 'additional_data',
29+
function: (args) => (additionalData as JSString).toDart,
30+
argumentCount: const AllowedArgumentCount(0),
31+
);
32+
2733
return ExampleDatabase(database: raw);
2834
}
2935
}

sqlite3_web/web/main.dart

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import 'controller.dart';
1111
final sqlite3WasmUri = Uri.parse('sqlite3.wasm');
1212
final workerUri = Uri.parse('worker.dart.js');
1313
const databaseName = 'database';
14+
const additionalOptions = 'test-additional-options';
1415

1516
WebSqlite? webSqlite;
1617

@@ -160,12 +161,19 @@ Future<JSAny?> _open(String? implementationName, bool onlyOpenVfs) async {
160161
if (implementationName != null) {
161162
final split = implementationName.split(':');
162163

163-
db = await sqlite.connect(databaseName, StorageMode.values.byName(split[0]),
164-
AccessMode.values.byName(split[1]),
165-
onlyOpenVfs: onlyOpenVfs);
164+
db = await sqlite.connect(
165+
databaseName,
166+
StorageMode.values.byName(split[0]),
167+
AccessMode.values.byName(split[1]),
168+
onlyOpenVfs: onlyOpenVfs,
169+
additionalOptions: additionalOptions.toJS,
170+
);
166171
} else {
167-
final result = await sqlite.connectToRecommended(databaseName,
168-
onlyOpenVfs: onlyOpenVfs);
172+
final result = await sqlite.connectToRecommended(
173+
databaseName,
174+
onlyOpenVfs: onlyOpenVfs,
175+
additionalOptions: additionalOptions.toJS,
176+
);
169177
db = result.database;
170178
returnValue = '${result.storage.name}:${result.access.name}';
171179
}
@@ -174,7 +182,16 @@ Future<JSAny?> _open(String? implementationName, bool onlyOpenVfs) async {
174182

175183
// Make sure it works!
176184
if (!onlyOpenVfs) {
177-
await db.select('SELECT database_host()');
185+
final rows = await db
186+
.select('SELECT database_host() as host, additional_data() as data;');
187+
if (rows.length != 1) {
188+
throw 'unexpected row count';
189+
}
190+
final row = rows[0];
191+
if (row['data'] != additionalOptions) {
192+
throw 'unexpected data: $row';
193+
}
194+
178195
listenForUpdates();
179196
}
180197

0 commit comments

Comments
 (0)