├── .gitignore
├── example
├── multiple_idb_stores
│ ├── index.html
│ └── index.dart
└── simple_run_through
│ ├── index.html
│ └── index.dart
├── analysis_options.yaml
├── pubspec.yaml
├── LICENSE
├── lib
├── src
│ ├── memory_store.dart
│ ├── local_storage_store.dart
│ ├── _map_store.dart
│ ├── indexeddb_store.dart
│ └── websql_store.dart
└── lawndart.dart
├── CHANGELOG.md
├── README.md
└── test
└── lawndart_test.dart
/.gitignore:
--------------------------------------------------------------------------------
1 | .children
2 | .project
3 | packages
4 | /pubspec.lock
5 | .buildlog
6 | build.snapshot
7 | out
8 | test/*.js*
9 | .idea
10 |
11 | .packages
12 | .pub
13 | .dart_tool
--------------------------------------------------------------------------------
/example/multiple_idb_stores/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | index
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | analyzer:
2 | # exclude:
3 | # - path/to/excluded/files/**
4 |
5 | # Lint rules and documentation, see http://dart-lang.github.io/linter/lints
6 | linter:
7 | rules:
8 | - cancel_subscriptions
9 | - close_sinks
10 | - hash_and_equals
11 | - iterable_contains_unrelated_type
12 | - list_remove_unrelated_type
13 | - test_types_in_equals
14 | - unrelated_type_equality_checks
15 | - valid_regexps
16 |
--------------------------------------------------------------------------------
/example/multiple_idb_stores/index.dart:
--------------------------------------------------------------------------------
1 | import 'package:lawndart/lawndart.dart';
2 | import 'dart:html';
3 |
4 | main() async {
5 | await window.indexedDB.deleteDatabase('temptestdb');
6 | Store store = await Store.open('temptestdb', 'store1');
7 | print('opened 1');
8 | await Store.open('temptestdb', 'store2');
9 | print('opened 2');
10 | await store.all().toList();
11 | print('all done');
12 | querySelector('#text').text = 'all done';
13 | }
14 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: lawndart
2 | version: 0.10.0
3 | author: Seth Ladd
4 | description: An easier way to access storage APIs in the browser.
5 | homepage: https://github.com/sethladd/lawndart
6 | documentation: http://sethladd.github.com/lawndart/
7 |
8 | environment:
9 | sdk: '>=2.0.0-dev.67.0 <3.0.0'
10 |
11 | dev_dependencies:
12 | build_runner: ^0.9.1
13 | build_test: ^0.10.3
14 | build_web_compilers: ^0.4.0+4
15 | test: ^1.3.0
16 |
17 |
18 |
--------------------------------------------------------------------------------
/example/simple_run_through/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Lawndart - Simple Run Through
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2013 Google
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
--------------------------------------------------------------------------------
/lib/src/memory_store.dart:
--------------------------------------------------------------------------------
1 | //Copyright 2012 Google
2 | //
3 | //Licensed under the Apache License, Version 2.0 (the "License");
4 | //you may not use this file except in compliance with the License.
5 | //You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | //Unless required by applicable law or agreed to in writing, software
10 | //distributed under the License is distributed on an "AS IS" BASIS,
11 | //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | //See the License for the specific language governing permissions and
13 | //limitations under the License.
14 |
15 | part of lawndart;
16 |
17 | class MemoryStore extends _MapStore {
18 | MemoryStore._() : super._();
19 |
20 | static Future open() async {
21 | var store = new MemoryStore._();
22 | await store._open();
23 | return store;
24 | }
25 |
26 | @override
27 | Map _generateMap() => new Map();
28 | }
29 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 0.10.0
2 |
3 | * Dart 2 compliance
4 |
5 | ## 0.9.0
6 |
7 | * BREAKING CHANGE: creating and opening a store are the same operation
8 | open() is a static method on Store
9 | * Using the new async/await/async*/yield features.
10 | * BREAKING CHANGE: No more generics. Stores are simply String => String stores now.
11 |
12 | ## 0.6.5
13 |
14 | * Reduce size of WebSQL down to 4MB, avoids permission check.
15 |
16 | ## 0.6.2
17 |
18 | * Update to SDK version 0.8.5
19 | * Remove old web_ui example
20 |
21 | ## 0.6.1
22 |
23 | * Fix bug with chained opens of multiple stores, followed by a read.
24 |
25 | ## 0.6.0
26 |
27 | * No more explicit version for indexed, it's automatically handled.
28 | * Better support for multiple store names per IndexedDB.
29 | * Thanks to https://github.com/davidB
30 |
31 | ## 0.5.0
32 |
33 | * Added factory constructor to automatically choose the best store.
34 | * Updated to hop standalone.
35 |
36 | ## 0.4.2
37 |
38 | * Added IndexedDbStore.supported
39 | * Added WebSqlStore.supported
40 | * Renamed all the adapters to stores
41 | * The TODO sample app now works in Safari, Chrome, and Firefox
42 |
--------------------------------------------------------------------------------
/example/simple_run_through/index.dart:
--------------------------------------------------------------------------------
1 | library index;
2 |
3 | import 'package:lawndart/lawndart.dart';
4 | import 'dart:html';
5 | import 'dart:web_sql';
6 | import 'dart:indexed_db';
7 |
8 | runThrough(Store store, String id) async {
9 | var elem = querySelector('#$id');
10 |
11 | try {
12 | await store.nuke();
13 | await store.save(id, "hello");
14 | await store.save("is fun", "dart");
15 | await for (var value in store.all()) {
16 | elem.appendText('$value, ');
17 | }
18 | elem.appendText('all done');
19 | } catch (e) {
20 | elem.text = e.toString();
21 | }
22 | }
23 |
24 | main() async {
25 | if (SqlDatabase.supported) {
26 | var store = await WebSqlStore.open('test', 'test');
27 | runThrough(store, 'websql');
28 | } else {
29 | querySelector('#websql').text = 'WebSQL is not supported in your browser';
30 | }
31 |
32 | if (IdbFactory.supported) {
33 | var store = await IndexedDbStore.open('test', 'test');
34 | runThrough(store, 'indexeddb');
35 | } else {
36 | querySelector('#indexeddb').text =
37 | 'IndexedDB is not supported in your browser';
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/lib/src/local_storage_store.dart:
--------------------------------------------------------------------------------
1 | //Copyright 2012 Google
2 | //
3 | //Licensed under the Apache License, Version 2.0 (the "License");
4 | //you may not use this file except in compliance with the License.
5 | //You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | //Unless required by applicable law or agreed to in writing, software
10 | //distributed under the License is distributed on an "AS IS" BASIS,
11 | //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | //See the License for the specific language governing permissions and
13 | //limitations under the License.
14 |
15 | part of lawndart;
16 |
17 | /**
18 | * Wraps the local storage API and exposes it as a [Store].
19 | * Local storage is a synchronous API, and generally not recommended
20 | * unless all other storage mechanisms are unavailable.
21 | */
22 | class LocalStorageStore extends _MapStore {
23 | LocalStorageStore._() : super._();
24 |
25 | static Future open() async {
26 | var store = new LocalStorageStore._();
27 | await store._open();
28 | return store;
29 | }
30 |
31 | @override
32 | Map _generateMap() => window.localStorage;
33 | }
34 |
--------------------------------------------------------------------------------
/lib/src/_map_store.dart:
--------------------------------------------------------------------------------
1 | //Copyright 2012 Google
2 | //
3 | //Licensed under the Apache License, Version 2.0 (the "License");
4 | //you may not use this file except in compliance with the License.
5 | //You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | //Unless required by applicable law or agreed to in writing, software
10 | //distributed under the License is distributed on an "AS IS" BASIS,
11 | //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | //See the License for the specific language governing permissions and
13 | //limitations under the License.
14 |
15 | part of lawndart;
16 |
17 | abstract class _MapStore extends Store {
18 | Map storage;
19 |
20 | _MapStore._() : super._();
21 |
22 | @override
23 | Future _open() async {
24 | storage = _generateMap();
25 | return true;
26 | }
27 |
28 | Map _generateMap();
29 |
30 | @override
31 | Stream keys() async* {
32 | for (var k in storage.keys) {
33 | yield k;
34 | }
35 | }
36 |
37 | @override
38 | Future save(String obj, String key) async {
39 | storage[key] = obj;
40 | return key;
41 | }
42 |
43 | @override
44 | Future batch(Map objs) async {
45 | for (var key in objs.keys) {
46 | storage[key] = objs[key];
47 | }
48 | return true;
49 | }
50 |
51 | @override
52 | Future getByKey(String key) async {
53 | return storage[key];
54 | }
55 |
56 | @override
57 | Stream getByKeys(Iterable keys) async* {
58 | var values = keys.map((key) => storage[key]).where((v) => v != null);
59 | for (var v in values) {
60 | yield v;
61 | }
62 | }
63 |
64 | @override
65 | Future exists(String key) async {
66 | return storage.containsKey(key);
67 | }
68 |
69 | @override
70 | Stream all() async* {
71 | for (var v in storage.values) {
72 | yield v;
73 | }
74 | }
75 |
76 | @override
77 | Future removeByKey(String key) async {
78 | storage.remove(key);
79 | return true;
80 | }
81 |
82 | @override
83 | Future removeByKeys(Iterable keys) async {
84 | keys.forEach((key) => storage.remove(key));
85 | return true;
86 | }
87 |
88 | Future nuke() async {
89 | storage.clear();
90 | return true;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Lawndart
2 |
3 | A unified, asynchronous, easy-to-use library for offline-enabled
4 | browser-based web apps. Kinda sorta a port of Lawnchair to Dart,
5 | but with Futures and Streams.
6 |
7 | Lawndart uses Futures to provide an asynchronous, yet consistent,
8 | interface to local storage, indexed db, and websql. This library is designed
9 | for simple key-value usage, and is not designed for complex transactional
10 | queries. This library prefers simplicity and uniformity over expressiveness.
11 |
12 | You can use this library to help deal with the wide array of client-side
13 | storage options. You should be able to write your code against the Lawndart
14 | interface and have it work across browsers that support at least one of the
15 | following: local storage, indexed db, and websql.
16 |
17 | # Example
18 |
19 | ```dart
20 | // Picks the best store available.
21 | var db = await Store.open('simple-run-through', 'test');
22 |
23 | await db.open();
24 | await db.nuke();
25 | await db.save('world', 'hello');
26 | await db.save('is fun', 'dart');
27 |
28 | var value = await db.getByKey('hello');
29 |
30 | querySelector('#text').text = value;
31 | ```
32 |
33 | See the example/ directory for more sample code.
34 |
35 | # Choosing the best storage option
36 |
37 | This is now made easy for you. Simply create a new instance of Store:
38 |
39 | ```dart
40 | var store = await Store.open('dbName', 'storeName');
41 | ```
42 |
43 | The factory constructor will try IndexedDB, then WebSQL, and then finally
44 | local storage. Of course, you can perform your own logic to choose which
45 | option works for you.
46 |
47 | # API
48 |
49 | `Future Store.open()`
50 | Opens the database and makes it available for reading and writing.
51 |
52 | `Future nuke()`
53 | Wipes the database clean. All records are deleted.
54 |
55 | `Future save(value, key)`
56 | Stores a value accessible by a key.
57 |
58 | `Future getByKey(key)`
59 | Retrieves a value, given a key.
60 |
61 | `Stream keys()`
62 | Returns all keys.
63 |
64 | `Stream all()`
65 | Returns all values.
66 |
67 | `Future batch(map)`
68 | Stores all values and their keys.
69 |
70 | `Stream getByKeys(keys)`
71 | Returns all values, given keys.
72 |
73 | `Future exists(key)`
74 | Returns true if the key exists, or false.
75 |
76 | `Future removeByKey(key)`
77 | Removes the value for the key.
78 |
79 | `Future removeByKeys(keys)`
80 | Removes all values for the keys.
81 |
82 |
83 | # Usage
84 |
85 | Most methods return a Future, like `open` and `save`.
86 | Methods that would return many things, like `all`, return a Stream.
87 |
88 | # Supported storage mechanisms
89 |
90 | * Indexed DB - Great choice for modern browsers
91 | * WebSQL - Well supported in mobile WebKit browsers, not in Firefox
92 | * Local Storage - Only 5MB, slow, more-or-less universal
93 | * Memory - Good for testing
94 |
95 | You can consult [Can I Use?](http://caniuse.com) for a breakdown of browser
96 | support for the various storage technologies.
97 |
98 | # Install
99 |
100 | Lawndart is a pub package. To install it, and link it into your app,
101 | add lawndart to your pubspec.yaml. For example:
102 |
103 | ```yaml
104 | name: your_cool_app
105 | dependencies:
106 | lawndart: any
107 | ```
108 |
109 | If you use Dart Editor, select your project from the Files view, then go
110 | to Tools, and run Pub Install.
111 |
112 | If you use the command line, ensure the Dart SDK is on your path, and
113 | the run: `pub install`
114 |
115 | # Support
116 |
117 | Lawndart is hosted at https://github.com/sethladd/lawndart
118 |
119 | You can file issues at https://github.com/sethladd/lawndart/issues
120 |
121 | API docs at http://sethladd.github.com/lawndart/
122 |
123 | This library is open source, pull requests welcome!
124 |
125 | # Authors
126 |
127 | * Seth Ladd (sethladd@gmail.com)
128 |
129 | # License
130 |
131 | ```no-highlight
132 | Copyright 2015 Google
133 |
134 | Licensed under the Apache License, Version 2.0 (the "License");
135 | you may not use this file except in compliance with the License.
136 | You may obtain a copy of the License at
137 |
138 | http://www.apache.org/licenses/LICENSE-2.0
139 |
140 | Unless required by applicable law or agreed to in writing, software
141 | distributed under the License is distributed on an "AS IS" BASIS,
142 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
143 | See the License for the specific language governing permissions and
144 | limitations under the License.
145 | ```
146 |
--------------------------------------------------------------------------------
/lib/src/indexeddb_store.dart:
--------------------------------------------------------------------------------
1 | //Copyright 2012 Google
2 | //
3 | //Licensed under the Apache License, Version 2.0 (the "License");
4 | //you may not use this file except in compliance with the License.
5 | //You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | //Unless required by applicable law or agreed to in writing, software
10 | //distributed under the License is distributed on an "AS IS" BASIS,
11 | //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | //See the License for the specific language governing permissions and
13 | //limitations under the License.
14 |
15 | part of lawndart;
16 |
17 | /**
18 | * Wraps the IndexedDB API and exposes it as a [Store].
19 | * IndexedDB is generally the preferred API if it is available.
20 | */
21 | class IndexedDbStore extends Store {
22 | static Map _databases = new Map();
23 |
24 | final String dbName;
25 | final String storeName;
26 |
27 | IndexedDbStore._(this.dbName, this.storeName) : super._();
28 |
29 | static Future open(String dbName, String storeName) async {
30 | var store = new IndexedDbStore._(dbName, storeName);
31 | await store._open();
32 | return store;
33 | }
34 |
35 | /// Returns true if IndexedDB is supported on this platform.
36 | static bool get supported => idb.IdbFactory.supported;
37 |
38 | Future _open() async {
39 | if (!supported) {
40 | throw new UnsupportedError('IndexedDB is not supported on this platform');
41 | }
42 |
43 | if (_db != null) {
44 | _db.close();
45 | }
46 |
47 | var db = await window.indexedDB.open(dbName);
48 |
49 | //print("Newly opened db $dbName has version ${db.version} and stores ${db.objectStoreNames}");
50 | if (!db.objectStoreNames.contains(storeName)) {
51 | db.close();
52 | //print('Attempting upgrading $storeName from ${db.version}');
53 | db = await window.indexedDB.open(dbName, version: db.version + 1,
54 | onUpgradeNeeded: (e) {
55 | //print('Upgrading db $dbName to ${db.version + 1}');
56 | idb.Database d = e.target.result;
57 | d.createObjectStore(storeName);
58 | });
59 | }
60 |
61 | _databases[dbName] = db;
62 | return true;
63 | }
64 |
65 | idb.Database get _db => _databases[dbName];
66 |
67 | @override
68 | Future removeByKey(String key) {
69 | return _runInTxn((store) => store.delete(key));
70 | }
71 |
72 | @override
73 | Future save(String obj, String key) {
74 | return _runInTxn(
75 | (store) async => (await store.put(obj, key)) as String);
76 | }
77 |
78 | @override
79 | Future getByKey(String key) {
80 | return _runInTxn(
81 | (store) async => (await store.getObject(key) as String), 'readonly');
82 | }
83 |
84 | @override
85 | Future nuke() {
86 | return _runInTxn((store) => store.clear());
87 | }
88 |
89 | Future _runInTxn(Future requestCommand(idb.ObjectStore store),
90 | [String txnMode = 'readwrite']) async {
91 | var trans = _db.transaction(storeName, txnMode);
92 | var store = trans.objectStore(storeName);
93 | var result = await requestCommand(store);
94 | await trans.completed;
95 | return result;
96 | }
97 |
98 | Stream _doGetAll(String onCursor(idb.CursorWithValue cursor)) async* {
99 | var trans = _db.transaction(storeName, 'readonly');
100 | var store = trans.objectStore(storeName);
101 | await for (var cursor in store.openCursor(autoAdvance: true)) {
102 | yield onCursor(cursor);
103 | }
104 | }
105 |
106 | @override
107 | Stream all() {
108 | return _doGetAll((idb.CursorWithValue cursor) => cursor.value);
109 | }
110 |
111 | @override
112 | Future batch(Map objs) {
113 | return _runInTxn((store) {
114 | objs.forEach((k, v) {
115 | store.put(v, k);
116 | });
117 | });
118 | }
119 |
120 | @override
121 | Stream getByKeys(Iterable keys) async* {
122 | for (var key in keys) {
123 | var v = await getByKey(key);
124 | if (v != null) yield v;
125 | }
126 | }
127 |
128 | @override
129 | Future removeByKeys(Iterable keys) {
130 | return _runInTxn((store) {
131 | for (var key in keys) {
132 | store.delete(key);
133 | }
134 | });
135 | }
136 |
137 | @override
138 | Future exists(String key) async {
139 | var value = await getByKey(key);
140 | return value != null;
141 | }
142 |
143 | @override
144 | Stream keys() {
145 | return _doGetAll((idb.CursorWithValue cursor) => cursor.key);
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/lib/lawndart.dart:
--------------------------------------------------------------------------------
1 | //Copyright 2012 Google
2 | //
3 | //Licensed under the Apache License, Version 2.0 (the "License");
4 | //you may not use this file except in compliance with the License.
5 | //You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | //Unless required by applicable law or agreed to in writing, software
10 | //distributed under the License is distributed on an "AS IS" BASIS,
11 | //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | //See the License for the specific language governing permissions and
13 | //limitations under the License.
14 |
15 | /**
16 | A unified, asynchronous, easy-to-use library for offline-enabled
17 | browser-based web apps. Kinda sorta a port of Lawnchair to Dart,
18 | but with Futures and Streams.
19 |
20 | Lawndart uses Futures to provide an asynchronous, yet consistent,
21 | interface to local storage, indexed db, and websql. This library is designed
22 | for simple key-value usage, and is not designed for complex transactional
23 | queries. This library prefers simplicity and uniformity over expressiveness.
24 |
25 | You can use this library to help deal with the wide array of client-side
26 | storage options. You should be able to write your code against the Lawndart
27 | interface and have it work across browsers that support at least one of the
28 | following: local storage, indexed db, and websql.
29 |
30 | # Example
31 |
32 | var db = new IndexedDbStore('simple-run-through', 'test');
33 | db.open()
34 | .then((_) => db.nuke())
35 | .then((_) => db.save("world", "hello"))
36 | .then((_) => db.save("is fun", "dart"))
37 | .then((_) => db.getByKey("hello"))
38 | .then((value) => query('#text').text = value);
39 |
40 | See the `example/` directory for more sample code.
41 |
42 | */
43 | library lawndart;
44 |
45 | import 'dart:html';
46 | import 'dart:indexed_db' as idb;
47 | import 'dart:web_sql';
48 | import 'dart:async';
49 |
50 | part 'src/indexeddb_store.dart';
51 | part 'src/_map_store.dart';
52 | part 'src/memory_store.dart';
53 | part 'src/local_storage_store.dart';
54 | part 'src/websql_store.dart';
55 |
56 | /**
57 | * Represents a Store that can hold key/value pairs. No order
58 | * is guaranteed for either keys or values.
59 | */
60 | abstract class Store {
61 | // For subclasses
62 | Store._();
63 |
64 | /**
65 | * Finds the best implementation. In order: IndexedDB, WebSQL, LocalStorage.
66 | */
67 | static Future open(String dbName, String storeName,
68 | [Map options]) async {
69 | Store store;
70 | if (IndexedDbStore.supported) {
71 | store = new IndexedDbStore._(dbName, storeName);
72 | } else if (WebSqlStore.supported) {
73 | if (options != null && options['estimatedSize']) {
74 | store = new WebSqlStore._(dbName, storeName,
75 | estimatedSize: options['estimatedSize']);
76 | } else {
77 | store = new WebSqlStore._(dbName, storeName);
78 | }
79 | } else {
80 | store = new LocalStorageStore._();
81 | }
82 |
83 | await store._open();
84 |
85 | return store;
86 | }
87 |
88 | /// Opens and initializes the database.
89 | Future _open();
90 |
91 | /// Returns all the keys as a stream. No order is guaranteed.
92 | Stream keys();
93 |
94 | /// Stores an [obj] accessible by [key].
95 | /// The returned Future completes with the key when the objects
96 | /// is saved in the store.
97 | Future save(String obj, String key);
98 |
99 | /// Stores all objects by their keys. This should happen in a single
100 | /// transaction if the underlying store supports it.
101 | /// The returned Future completes when all objects have been added
102 | /// to the store.
103 | Future batch(Map objectsByKey);
104 |
105 | /// Returns a Future that completes with the value for a key,
106 | /// or null if the key does not exist.
107 | Future getByKey(String key);
108 |
109 | /// Returns a Stream of all values for the keys.
110 | /// If a particular key is not found,
111 | /// no value will be returned, not even null.
112 | Stream getByKeys(Iterable keys);
113 |
114 | /// Returns a Future that completes with true if the key exists, or false.
115 | Future exists(String key);
116 |
117 | /// Returns a Stream of all values in no particular order.
118 | Stream all();
119 |
120 | /// Returns a Future that completes when the key's value is removed.
121 | Future removeByKey(String key);
122 |
123 | /// Returns a Future that completes when all the keys' values are removed.
124 | Future removeByKeys(Iterable keys);
125 |
126 | /// Returns a Future that completes when all values and keys
127 | /// are removed.
128 | Future nuke();
129 | }
130 |
--------------------------------------------------------------------------------
/test/lawndart_test.dart:
--------------------------------------------------------------------------------
1 | //Copyright 2014 Google
2 | //
3 | //Licensed under the Apache License, Version 2.0 (the "License");
4 | //you may not use this file except in compliance with the License.
5 | //You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | //Unless required by applicable law or agreed to in writing, software
10 | //distributed under the License is distributed on an "AS IS" BASIS,
11 | //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | //See the License for the specific language governing permissions and
13 | //limitations under the License.
14 |
15 | library store_tests;
16 |
17 | import 'dart:async';
18 | import 'dart:indexed_db';
19 | import 'dart:web_sql';
20 | import 'package:test/test.dart';
21 | import 'package:lawndart/lawndart.dart';
22 |
23 | typedef Future StoreGenerator();
24 |
25 | void run(StoreGenerator generator) {
26 | Store store;
27 |
28 | group('with no values', () {
29 | setUp(() async {
30 | store = await generator();
31 | await store.nuke();
32 | });
33 |
34 | test('keys is empty', () async {
35 | var keys = await store.keys().toList();
36 | expect(keys, hasLength(0));
37 | });
38 |
39 | test('get by key return null', () {
40 | Future future = store.getByKey("foo");
41 | expect(future, completion(null));
42 | });
43 |
44 | test('get by keys return empty collection', () async {
45 | var list = await store.getByKeys(["foo"]).toList();
46 | expect(list, hasLength(0));
47 | });
48 |
49 | test('save completes', () {
50 | Future future = store.save("value", "key");
51 | expect(future, completion("key"));
52 | });
53 |
54 | test('exists returns false', () {
55 | Future future = store.exists("foo");
56 | expect(future, completion(false));
57 | });
58 |
59 | test('all is empty', () {
60 | Future future = store.all().toList();
61 | expect(future, completion(hasLength(0)));
62 | });
63 |
64 | test('remove by key completes', () {
65 | Future future = store.removeByKey("foo");
66 | expect(future, completes);
67 | });
68 |
69 | test('remove by keys completes', () {
70 | Future future = store.removeByKeys(["foo"]);
71 | expect(future, completes);
72 | });
73 |
74 | test('nuke completes', () {
75 | Future future = store.nuke();
76 | expect(future, completes);
77 | });
78 |
79 | test('batch completes', () {
80 | Future future = store.batch({'foo': 'bar'});
81 | expect(future, completes);
82 | });
83 | });
84 |
85 | group('with a few values', () {
86 | setUp(() async {
87 | // ensure it's clear for each test, see http://dartbug.com/8157
88 | store = await generator();
89 |
90 | await store.nuke();
91 | await store.save("world", "hello");
92 | await store.save("is fun", "dart");
93 | });
94 |
95 | test('keys has them', () {
96 | Future future = store.keys().toList();
97 | future.then((Iterable keys) {
98 | expect(keys, hasLength(2));
99 | expect(keys, contains("hello"));
100 | expect(keys, contains("dart"));
101 | });
102 | expect(future, completes);
103 | });
104 |
105 | test('get by key', () {
106 | Future future = store.getByKey("hello");
107 | future.then((value) {
108 | expect(value, "world");
109 | });
110 | expect(future, completes);
111 | });
112 |
113 | test('get by keys', () {
114 | Future future = store.getByKeys(["hello", "dart"]).toList();
115 | future.then((values) {
116 | expect(values, hasLength(2));
117 | expect(values.contains("world"), true);
118 | expect(values.contains("is fun"), true);
119 | });
120 | expect(future, completes);
121 | });
122 |
123 | test('exists is true', () {
124 | Future future = store.exists("hello");
125 | future.then((exists) {
126 | expect(exists, true);
127 | });
128 | expect(future, completes);
129 | });
130 |
131 | test('all has everything', () {
132 | Future future = store.all().toList();
133 | future.then((all) {
134 | expect(all, hasLength(2));
135 | expect(all.contains("world"), true);
136 | expect(all.contains("is fun"), true);
137 | });
138 | expect(future, completes);
139 | });
140 |
141 | test('remove by key', () {
142 | Future future =
143 | store.removeByKey("hello").then((_) => store.all().toList());
144 | future.then((remaining) {
145 | expect(remaining, hasLength(1));
146 | expect(remaining.contains("world"), false);
147 | expect(remaining.contains("is fun"), true);
148 | });
149 | expect(future, completes);
150 | });
151 | });
152 | }
153 |
154 | void main() {
155 | group('memory', () {
156 | run(() => MemoryStore.open());
157 | });
158 |
159 | group('local storage', () {
160 | run(() => LocalStorageStore.open());
161 | });
162 |
163 | if (SqlDatabase.supported) {
164 | group('websql', () {
165 | run(() => WebSqlStore.open('test', 'test'));
166 | });
167 | }
168 |
169 | if (IdbFactory.supported) {
170 | group('indexed db store0', () {
171 | run(() => IndexedDbStore.open("test-db", "test-store0"));
172 | });
173 | group('indexed db store1', () {
174 | run(() => IndexedDbStore.open("test-db", "test-store1"));
175 | });
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/lib/src/websql_store.dart:
--------------------------------------------------------------------------------
1 | //Copyright 2012 Google
2 | //
3 | //Licensed under the Apache License, Version 2.0 (the "License");
4 | //you may not use this file except in compliance with the License.
5 | //You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | //Unless required by applicable law or agreed to in writing, software
10 | //distributed under the License is distributed on an "AS IS" BASIS,
11 | //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | //See the License for the specific language governing permissions and
13 | //limitations under the License.
14 |
15 | part of lawndart;
16 |
17 | /**
18 | * Wraps the WebSQL API and exposes it as a [Store].
19 | * WebSQL is a transactional database.
20 | */
21 | class WebSqlStore extends Store {
22 | static const String VERSION = "1";
23 | static const int INITIAL_SIZE = 4 * 1024 * 1024;
24 |
25 | String dbName;
26 | String storeName;
27 | int estimatedSize;
28 | SqlDatabase _db;
29 |
30 | WebSqlStore._(this.dbName, this.storeName, {this.estimatedSize: INITIAL_SIZE})
31 | : super._();
32 |
33 | static Future open(String dbName, String storeName,
34 | {int estimatedSize: INITIAL_SIZE}) async {
35 | final store =
36 | new WebSqlStore._(dbName, storeName, estimatedSize: estimatedSize);
37 | await store._open();
38 | return store;
39 | }
40 |
41 | /// Returns true if WebSQL is supported on this platform.
42 | static bool get supported => SqlDatabase.supported;
43 |
44 | @override
45 | Future _open() async {
46 | if (!supported) {
47 | throw new UnsupportedError('WebSQL is not supported on this platform');
48 | }
49 | _db = window.openDatabase(dbName, VERSION, dbName, estimatedSize);
50 | await _initDb();
51 | return true;
52 | }
53 |
54 | Future _initDb() {
55 | final sql =
56 | 'CREATE TABLE IF NOT EXISTS $storeName (id NVARCHAR(32) UNIQUE PRIMARY KEY, value TEXT)';
57 | return _runInTxn((txn, completer) {
58 | txn.executeSql(sql, []);
59 | });
60 | }
61 |
62 | @override
63 | Stream keys() {
64 | final sql = 'SELECT id FROM $storeName';
65 | return _runInTxnWithResults((txn, controller) async {
66 | final resultSet = await txn.executeSql(sql, []);
67 | for (var i = 0; i < resultSet.rows.length; ++i) {
68 | final row = resultSet.rows.item(i);
69 | controller.add(row['id']);
70 | }
71 | });
72 | }
73 |
74 | @override
75 | Future save(String obj, String key) {
76 | final upsertSql =
77 | 'INSERT OR REPLACE INTO $storeName (id, value) VALUES (?, ?)';
78 | return _runInTxn((txn, completer) async {
79 | await txn.executeSql(upsertSql, [key, obj]);
80 | completer.complete(key);
81 | });
82 | }
83 |
84 | @override
85 | Future exists(String key) async {
86 | final v = await getByKey(key);
87 | return v != null;
88 | }
89 |
90 | @override
91 | Future getByKey(String key) {
92 | final completer = new Completer();
93 | final sql = 'SELECT value FROM $storeName WHERE id = ?';
94 |
95 | _db.readTransaction((txn) async {
96 | final resultSet = await txn.executeSql(sql, [key]);
97 | if (resultSet.rows.isEmpty) {
98 | completer.complete(null);
99 | } else {
100 | final row = resultSet.rows.item(0);
101 | completer.complete(row['value']);
102 | }
103 | }, (error) => completer.completeError(error));
104 |
105 | return completer.future;
106 | }
107 |
108 | @override
109 | Future removeByKey(String key) {
110 | final sql = 'DELETE FROM $storeName WHERE id = ?';
111 |
112 | return _runInTxn((txn, completer) {
113 | txn.executeSql(sql, [key]);
114 | });
115 | }
116 |
117 | @override
118 | Future nuke() {
119 | final sql = 'DELETE FROM $storeName';
120 | return _runInTxn((txn, completer) {
121 | txn.executeSql(sql, []);
122 | });
123 | }
124 |
125 | @override
126 | Stream all() {
127 | final sql = 'SELECT id,value FROM $storeName';
128 |
129 | return _runInTxnWithResults((txn, controller) async {
130 | final resultSet = await txn.executeSql(sql, []);
131 | for (var i = 0; i < resultSet.rows.length; ++i) {
132 | final row = resultSet.rows.item(i);
133 | controller.add(row['value']);
134 | }
135 | });
136 | }
137 |
138 | @override
139 | Future batch(Map objs) {
140 | final upsertSql =
141 | 'INSERT OR REPLACE INTO $storeName (id, value) VALUES (?, ?)';
142 |
143 | return _runInTxn((txn, completer) {
144 | objs.forEach((key, value) {
145 | txn.executeSql(upsertSql, [key, value]);
146 | });
147 | });
148 | }
149 |
150 | @override
151 | Stream getByKeys(Iterable _keys) {
152 | final sql = 'SELECT value FROM $storeName WHERE id = ?';
153 | return _runInTxnWithResults((txn, controller) {
154 | _keys.forEach((key) async {
155 | final resultSet = await txn.executeSql(sql, [key]);
156 | if (resultSet.rows.isNotEmpty) {
157 | controller.add(resultSet.rows.item(0)['value']);
158 | }
159 | });
160 | });
161 | }
162 |
163 | @override
164 | Future removeByKeys(Iterable _keys) {
165 | final sql = 'DELETE FROM $storeName WHERE id = ?';
166 | return _runInTxn((txn, completer) {
167 | _keys.forEach((key) {
168 | txn.executeSql(sql, [key]);
169 | });
170 | });
171 | }
172 |
173 | Future _runInTxn(
174 | Future callback(SqlTransaction txn, Completer completer)) {
175 | final completer = new Completer();
176 |
177 | _db.transaction((txn) => callback(txn, completer),
178 | (error) => completer.completeError(error), () {
179 | if (!completer.isCompleted) {
180 | completer.complete();
181 | }
182 | });
183 |
184 | return completer.future;
185 | }
186 |
187 | Stream _runInTxnWithResults(
188 | Future callback(SqlTransaction txn, StreamController controller)) {
189 | final controller = new StreamController();
190 |
191 | _db.transaction((txn) => callback(txn, controller), (error) {
192 | controller.addError(error);
193 | controller.close();
194 | }, () => controller.close());
195 |
196 | return controller.stream;
197 | }
198 | }
199 |
--------------------------------------------------------------------------------