Skip to content

Commit

Permalink
[sembast_web] web and js_interop support
Browse files Browse the repository at this point in the history
  • Loading branch information
alextekartik committed Feb 27, 2024
1 parent 66f80bb commit 0c1be50
Show file tree
Hide file tree
Showing 12 changed files with 270 additions and 4 deletions.
2 changes: 1 addition & 1 deletion sembast_test/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ version: 0.3.0
publish_to: none

environment:
sdk: '>=3.0.0 <4.0.0'
sdk: '>=3.3.0 <4.0.0'

dependencies:
path: '>=1.6.0'
Expand Down
23 changes: 23 additions & 0 deletions sembast_test/test/web/idb_web_interop_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
@TestOn('browser')
library;

import 'package:idb_shim/idb_client_native_web.dart';
import 'package:sembast_test/all_jdb_test.dart' as all_jdb_test;
import 'package:sembast_test/all_test.dart';
import 'package:sembast_test/jdb_test_common.dart';
import 'package:sembast_test/src/import_jdb.dart';
import 'package:sembast_test/test_common.dart';
import 'package:sembast_web/src/jdb_factory_idb.dart';
import 'package:test/test.dart';

Future main() async {
var jdbFactory = JdbFactoryIdb(idbFactoryNative);
var factory = DatabaseFactoryJdb(jdbFactory);

var testContext = DatabaseTestContextJdb()..factory = factory;

group('idb_native', () {
defineTests(testContext);
all_jdb_test.defineTests(testContext);
});
}
4 changes: 4 additions & 0 deletions sembast_web/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.3.0-0

* web and js_interop support

## 2.2.0

* Dart 3 only
Expand Down
7 changes: 7 additions & 0 deletions sembast_web/lib/sembast_web_interop.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import 'package:sembast/sembast.dart';
import 'package:sembast_web/src/web_interop/sembast_web.dart' as src;

/// Sembast factory for the Web.
///
/// Build on top of IndexedDB and localStorage.
DatabaseFactory get databaseFactoryWeb => src.databaseFactoryWeb;
90 changes: 90 additions & 0 deletions sembast_web/lib/src/web_interop.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import 'dart:async';
import 'dart:js_interop';

import 'package:idb_shim/idb_client_native_web.dart';
import 'package:sembast_web/src/jdb_factory_idb.dart';
import 'package:sembast_web/src/jdb_import.dart';
import 'package:sembast_web/src/web_defs.dart';
import 'package:web/web.dart' as web;

/// The native jdb factory
var jdbFactoryIdbNative = JdbFactoryWeb();

/// The sembast idb native factory with web.
var databaseFactoryWeb = DatabaseFactoryWeb();

/// Web jdb factory.
class JdbFactoryWeb extends JdbFactoryIdb {
/// Web jdb factory.
JdbFactoryWeb() : super(idbFactoryNative);

StreamSubscription? _revisionSubscription;

@override
void start() {
stop();
_revisionSubscription = storageRevisionStream.listen((storageRevision) {
var list = databases[storageRevision.name]!;
for (var jdbDatabase in list) {
jdbDatabase.addRevision(storageRevision.revision);
}
});
}

@override
void stop() {
_revisionSubscription?.cancel();
_revisionSubscription = null;
}

/// Notify other app (web only))
@override
void notifyRevision(StorageRevision storageRevision) {
addStorageRevision(storageRevision);
}
}

/// Web factory.
class DatabaseFactoryWeb extends DatabaseFactoryJdb {
/// Web factory.
DatabaseFactoryWeb() : super(jdbFactoryIdbNative);
}

String _sembastStorageKeyPrefix = 'sembast_web/revision:';

/// add a storage revision
void addStorageRevision(StorageRevision storageRevision) {
if (debugStorageNotification) {
print('adding storage revision $storageRevision');
}
var key = '$_sembastStorageKeyPrefix${storageRevision.name}';
if (storageRevision.revision != 0) {
web.window.localStorage.setItem(key, storageRevision.revision.toString());
} else {
web.window.localStorage.removeItem(key);
}
}

StreamController<StorageRevision>? _storageRevisionController;

/// Storage revision notification from all tabs
Stream<StorageRevision> get storageRevisionStream {
_storageRevisionController ??=
StreamController<StorageRevision>.broadcast(onListen: () {
web.window.onstorage = (web.StorageEvent event) {
if (debugStorageNotification) {
print('getting ${event.key}: ${event.newValue}');
}
if (event.key?.startsWith(_sembastStorageKeyPrefix) ?? false) {
var name = event.key!.substring(_sembastStorageKeyPrefix.length);
var revision =
event.newValue == null ? 0 : (int.tryParse(event.newValue!) ?? 0);
_storageRevisionController?.add(StorageRevision(name, revision));
}
}.toJS;
}, onCancel: () {
web.window.onstorage = null;
_storageRevisionController = null;
});
return _storageRevisionController!.stream;
}
3 changes: 3 additions & 0 deletions sembast_web/lib/src/web_interop/sembast_web.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export 'sembast_web_stub.dart'
if (dart.library.html) 'sembast_web_impl.dart'
if (dart.library.io) 'sembast_web_io.dart';
7 changes: 7 additions & 0 deletions sembast_web/lib/src/web_interop/sembast_web_impl.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import 'package:sembast/sembast.dart';
import 'package:sembast_web/src/web_interop.dart' as src;

/// Sembast factory for the Web.
///
/// Build on top of IndexedDB and localStorage.
DatabaseFactory get databaseFactoryWeb => src.databaseFactoryWeb;
11 changes: 11 additions & 0 deletions sembast_web/lib/src/web_interop/sembast_web_io.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import 'package:sembast/sembast.dart';

/// Sembast factory for the Web.
///
/// Build on top of IndexedDB and localStorage.
DatabaseFactory get databaseFactoryWeb => _stub(
'databaseFactoryWeb not support on Flutter/VM. Use `sembast_sqflite` or `sembast` io implementation');

T _stub<T>(String message) {
throw UnimplementedError(message);
}
10 changes: 10 additions & 0 deletions sembast_web/lib/src/web_interop/sembast_web_stub.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import 'package:sembast/sembast.dart';

/// Sembast factory for the Web.
///
/// Build on top of IndexedDB and localStorage.
DatabaseFactory get databaseFactoryWeb => _stub('databaseFactoryWeb');

T _stub<T>(String message) {
throw UnimplementedError(message);
}
7 changes: 4 additions & 3 deletions sembast_web/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
name: sembast_web
description: NoSQL persistent embedded database for the Web on top of IndexedDB
version: 2.2.0
version: 2.3.0-0
homepage: https://github.com/tekartik/sembast.dart/tree/master/sembast_web
topics:
- nosql
- database

environment:
sdk: '>=3.0.0 <4.0.0'
sdk: '>=3.3.0 <4.0.0'

dependencies:
web: '>=0.5.0 <2.0.0'
sembast: '>=3.4.3 <5.0.0'
idb_shim: '>=2.3.0+2 <4.0.0'
idb_shim: '>=2.4.0-0 <4.0.0'
synchronized: '>=3.0.1 <5.0.0'
dev_dependencies:
lints: '>=1.0.1'
Expand Down
75 changes: 75 additions & 0 deletions sembast_web/test/web/idb_web_interop_simple_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
@TestOn('browser')
library;

import 'package:sembast/sembast.dart';
import 'package:sembast_web/sembast_web_interop.dart';
import 'package:sembast_web/src/sembast_import.dart';
import 'package:test/test.dart';
import 'package:web/web.dart';

import '../multiplatform/idb_jdb_test.dart' as idb_jdb_test;
import '../multiplatform/idb_jdb_test.dart';

var testPath = '.dart_tool/sembast_web_interop/databases';

Future main() async {
var factory = databaseFactoryWeb;

group('idb_native', () {
test('doc', () async {
// Declare our store (records are mapd, ids are ints)
var store = intMapStoreFactory.store();
var factory = databaseFactoryWeb;

// Open the database
var db = await factory.openDatabase('test');

// Add a new record
var key =
await store.add(db, <String, Object?>{'name': 'Table', 'price': 15});

// Read the record
var value = await store.record(key).get(db);

// Print the value
print(value);

// Close the database
await db.close();
});

test('open', () async {
var store = StoreRef<String, String>.main();
var record = store.record('key');
await factory.deleteDatabase('test');
var db = await factory.openDatabase('test');
expect(await record.get(db), isNull);
await record.put(db, 'value');
expect(await record.get(db), 'value');
await db.close();

db = await factory.openDatabase('test');
await record.put(db, 'value');
expect(await record.get(db), 'value');
await db.close();
});

test('storage_notification', () async {
var store = StoreRef<String, String>.main();
await factory.deleteDatabase('test');
var db = await factory.openDatabase('test');
expect(window.localStorage['sembast_web/revision:test'], isNull);
var record = store.record('my_key');
await record.put(db, 'my_value');
expect(window.localStorage['sembast_web/revision:test'], '1');
await db.close();
expect(window.localStorage['sembast_web/revision:test'], '1');
// Make sure the storage gets clears on deletion
await factory.deleteDatabase('test');
expect(window.localStorage['sembast_web/revision:test'], isNull);
});

idb_jdb_test.defineTests(
asJdbJactoryIdb(asDatabaseFactoryIdb(databaseFactoryWeb).jdbFactory));
});
}
35 changes: 35 additions & 0 deletions sembast_web/test/web/src_web_interop_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
@TestOn('browser')
library;

import 'package:sembast/sembast.dart';
import 'package:sembast_web/src/web_interop.dart';
import 'package:test/test.dart';

var testPath = '.dart_tool/sembast_web/databases';

Future main() async {
var factory = databaseFactoryWeb;

group('web', () {
test('notification', () async {
var revisionFuture = storageRevisionStream.first;
var store = StoreRef<String, String>.main();
var record = store.record('key');
await factory.deleteDatabase('test');
var db = await factory.openDatabase('test');
expect(await record.get(db), isNull);
await record.put(db, 'value');
expect(await record.get(db), 'value');

try {
var storageRevision =
await revisionFuture.timeout(const Duration(seconds: 10));
expect(storageRevision.name, 'test');
expect(storageRevision.revision, greaterThanOrEqualTo(1));
} catch (e) {
print(e);
}
await db.close();
});
});
}

0 comments on commit 0c1be50

Please sign in to comment.