diff --git a/examples/Cargo.toml b/examples/Cargo.toml index dcdd9686..d6d44860 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -1,2 +1,2 @@ [workspace] -members = ["canvas", "drag", "echo", "gamepad", "hasher", "minimal", "todomvc", "webgl", "wasm-bindgen-minimal"] +members = ["canvas", "drag", "echo", "gamepad", "hasher", "indexeddb", "minimal", "todomvc", "webgl", "wasm-bindgen-minimal"] diff --git a/examples/indexeddb/Cargo.toml b/examples/indexeddb/Cargo.toml new file mode 100644 index 00000000..be6e5e61 --- /dev/null +++ b/examples/indexeddb/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "indexeddb_example" +version = "0.1.0" +authors = ["Joe Jones "] + +[dependencies] +stdweb = { path = "../.." } +serde="*" +serde_derive="*" diff --git a/examples/indexeddb/src/main.rs b/examples/indexeddb/src/main.rs new file mode 100644 index 00000000..35b1da2f --- /dev/null +++ b/examples/indexeddb/src/main.rs @@ -0,0 +1,250 @@ +#[macro_use] +extern crate stdweb; + +#[macro_use] +extern crate serde_derive; + +extern crate serde; + +use std::cell::RefCell; + +use stdweb::traits::*; +use stdweb::web::{ + Element, + window, + document, + IEventTarget, +}; + +use stdweb::web::html_element::InputElement; + +use stdweb::web::event::IEvent; + +use stdweb::web::event::{ + IDBSuccessEvent, + IDBVersionChangeEvent, + IDBCompleteEvent, + IDBErrorEvent, + SubmitEvent, + ClickEvent +}; + +use stdweb::web::indexeddb::{ + IDBOpenDBRequest, + IDBDatabase, + IDBRequest, + IDBRequestSharedMethods, + DatabaseStorage, + IDBCursorWithValue, + IDBCursorSharedMethods, + IDBIndexParameters, + IDBTransactionMode +}; + +use stdweb::unstable::TryInto; + +#[derive(Serialize, Deserialize)] +struct Note { + title: String, + body: String +} + +js_serializable!( Note ); +js_deserializable!( Note ); + +thread_local!(static DB: RefCell> = RefCell::new(None)); + +fn display_data_inner(db: &IDBDatabase) { + let list = document().query_selector("ul").unwrap().unwrap(); + // Here we empty the contents of the list element each time the display is updated + // If you didn't do this, you'd get duplicates listed each time a new note is added + while list.first_child().is_some() { + list.remove_child(&list.first_child().unwrap()).unwrap(); + } + // Open our object store and then get a cursor - which iterates through all the + // different data items in the store + let object_store = db.transaction(vec!["notes"], IDBTransactionMode::ReadOnly).object_store("notes").unwrap(); + object_store.open_cursor(None, None).unwrap() + .add_event_listener( move |e: IDBSuccessEvent| { + // Get a reference to the cursor + let db_request: IDBRequest = e.target().unwrap().try_into().unwrap(); + let maybe_cursor: Result = db_request.result().unwrap().try_into(); + + // If there is still another data item to iterate through, keep running this code + if let Ok(cursor) = maybe_cursor { + // Create a list item, h3, and p to put each data item inside when displaying it + // structure the HTML fragment, and append it inside the list + let list_item = document().create_element("li").unwrap(); + let h3 = document().create_element("h3").unwrap(); + let para = document().create_element("p").unwrap(); + + list_item.append_child(&h3); + list_item.append_child(¶); + list.append_child(&list_item); + + let note: Note = cursor.value().try_into().unwrap(); + + // Put the data from the cursor inside the h3 and para + h3.set_text_content(¬e.title); + para.set_text_content(¬e.body); + + // Store the ID of the data item inside an attribute on the list_item, so we know + // which item it corresponds to. This will be useful later when we want to delete items + let id: u32 = cursor.key().try_into().unwrap(); + list_item.set_attribute("data-note-id", &format!("{}", id)).unwrap(); + // Create a button and place it inside each list_item + let delete_btn = document().create_element("button").unwrap(); + list_item.append_child(&delete_btn); + delete_btn.set_text_content("Delete"); + + // Set an event handler so that when the button is clicked, the deleteItem() + // function is run + delete_btn.add_event_listener( delete_item ); + + // Iterate to the next item in the cursor + cursor.advance(1).unwrap(); // Todo this was continue + + } else { + // Again, if list item is empty, display a 'No notes stored' message + if list.first_child().is_none() { + let list_item = document().create_element("li").unwrap(); + list_item.set_text_content("No notes stored."); + list.append_child(&list_item); + } + // if there are no more cursor items to iterate through, say so + console!(log, "Notes all displayed"); + } + });} + +fn display_data() { + DB.with(|db_cell| { + if let Some(ref db) = *db_cell.borrow_mut() { + display_data_inner(db); + }}) +} + +// Define the deleteItem() function +fn delete_item( e: ClickEvent ) { + // retrieve the name of the task we want to delete. We need + // to convert it to a number before trying it use it with IDB; IDB key + // values are type-sensitive. + let button: Element = e.target().unwrap().try_into().unwrap(); + let note: Element = button.parent_node().unwrap().try_into().unwrap(); + let note_id = note.get_attribute("data-note-id").unwrap().parse::().unwrap(); + + // open a database transaction and delete the task, finding it using the id we retrieved above + DB.with(|db_cell| { + if let Some(ref db) = *db_cell.borrow_mut() { + let transaction = db.transaction(vec!["notes"], IDBTransactionMode::ReadWrite); + let object_store = transaction.object_store("notes").unwrap(); + object_store.delete(note_id.try_into().unwrap()).unwrap(); + + // report that the data item has been deleted + transaction.add_event_listener( move |_e: IDBCompleteEvent| { + // delete the parent of the button + // which is the list item, so it is no longer displayed + note.parent_node().unwrap().remove_child(¬e).unwrap(); + console!(log, "Note ", note_id, "deleted."); + + // Again, if list item is empty, display a 'No notes stored' message + let list = document().query_selector("ul").unwrap().unwrap(); + if ! list.first_child().is_some() { + let list_item = document().create_element("li").unwrap(); + list_item.set_text_content("No notes stored."); + list.append_child(&list_item); + } + }); + }}); +} + +fn main() { + stdweb::initialize(); + + // Open our database; it is created if it doesn't already exist + // (see onupgradeneeded below) + let request = window().indexed_db().open_with_version("notes", 1); + + // onerror handler signifies that the database didn't open successfully + request.add_event_listener( | _e: IDBErrorEvent| { + js!( + console.log("Database failed to open"); + ); + }); + + // onsuccess handler signifies that the database opened successfully + request.add_event_listener( move |event: IDBSuccessEvent| { + js!( + console.log("Database opened succesfully"); + ); + + let db_request: IDBOpenDBRequest = event.target().unwrap().try_into().unwrap(); + // Store the opened database object in the db variable. This is used a lot below + let db : IDBDatabase = db_request.database_result().unwrap(); + + DB.with(|db_cell| { + db_cell.replace(Some(db)); + }); + // Run the displayData() function to display the notes already in the IDB + display_data(); + }); + + request.add_event_listener( |event: IDBVersionChangeEvent| { + let db_request: IDBOpenDBRequest = event.target().unwrap().try_into().unwrap(); + let db_: IDBDatabase = db_request.result().unwrap().try_into().unwrap(); + + // Create an object_store to store our notes in (basically like a single table) + let object_store = db_.create_object_store("notes", true, "").unwrap(); + + // Define what data items the object_store will contain + object_store.create_index("title", "title"); + + let body_options = IDBIndexParameters { unique: false, multi_entry: false }; + object_store.create_index_with_options("body", "body", body_options); + + js!( + console.log("Database setup complete"); + ); + + }); + + let form = document().query_selector("form").unwrap().unwrap(); + form.add_event_listener( move |e: SubmitEvent | { + // prevent default - we don't want the form to submit in the conventional way + e.prevent_default(); + + // grab the values entered into the form fields and store them in an object ready for being inserted into the DB + let title_input: InputElement = document().query_selector("#title").unwrap().unwrap().try_into().unwrap(); + let body_input: InputElement = document().query_selector("#body").unwrap().unwrap().try_into().unwrap(); + let new_item = Note{ title: title_input.raw_value(), body: body_input.raw_value() }; + + DB.with(|db_cell| { + if let Some(ref db) = *db_cell.borrow_mut() { + // open a read/write db transaction, ready for adding the data + let transaction = db.transaction(vec!["notes"], IDBTransactionMode::ReadWrite); + + // call an object store that's already been added to the database + let object_store = transaction.object_store("notes").unwrap(); + + // Make a request to add our new_item object to the object store + let request = object_store.add(new_item.try_into().unwrap(), None).unwrap(); + + request.add_event_listener( move |_e: IDBSuccessEvent| { + // Clear the form, ready for adding the next entry + title_input.set_raw_value(""); + body_input.set_raw_value(""); + }); + + // Report on the success of the transaction completing, when everything is done + transaction.add_event_listener( |_e: IDBCompleteEvent| { + console!(log, "Transaction completed: database modification finished."); + + // update the display of data to show the newly added item, by running displayData() again. + display_data(); + }); + + transaction.add_event_listener( |_e: IDBErrorEvent| { + console!(log, "Transaction not opened due to error"); + }); + }}); + }); +} diff --git a/examples/indexeddb/static/index.html b/examples/indexeddb/static/index.html new file mode 100644 index 00000000..5f1566ab --- /dev/null +++ b/examples/indexeddb/static/index.html @@ -0,0 +1,43 @@ + + + + + IndexedDB demo + + + + +
+

IndexedDB notes demo

+
+ +
+
+

Notes

+
    + +
+
+
+

Enter a new note

+
+
+ + +
+
+ + +
+
+ +
+
+
+
+ +
+

Copyright nobody. Use the code as you like.

+
+ + diff --git a/examples/indexeddb/static/style.css b/examples/indexeddb/static/style.css new file mode 100644 index 00000000..4893c292 --- /dev/null +++ b/examples/indexeddb/static/style.css @@ -0,0 +1,35 @@ +html { + font-family: sans-serif; +} + +body { + margin: 0 auto; + max-width: 800px; +} + +header, footer { + background-color: green; + color: white; + line-height: 100px; + padding: 0 20px; +} + +.new-note, .note-display { + padding: 20px; +} + +.new-note { + background: #ddd; +} + +h1 { + margin: 0; +} + +ul { + list-style-type: none; +} + +div { + margin-bottom: 10px; +} diff --git a/src/lib.rs b/src/lib.rs index 445823f5..39aa7c4c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -235,6 +235,32 @@ pub mod web { interval_buffered }; + + /// A module containing the IndexedDB API + pub mod indexeddb { + pub use webapi::indexeddb::{ + IDBOpenDBRequest, + IDBDatabase, + IDBRequestSharedMethods, + IDBRequest, + IDBIndex, + IDBObjectStore, + IDBTransaction, + IDBTransactionMode, + IDBFactory, + DatabaseStorage, + IDBCursorDirection, + IDBRequestReadyState, + IDBCursorSharedMethods, + IDBCursor, + IDBCursorWithValue, + IDBIndexParameters, + IDBAddError + }; + } + + pub use webapi::dom_string_list::DOMStringList; + pub use webapi::window::{ Window, window @@ -434,6 +460,13 @@ pub mod web { BlurEvent }; + pub use webapi::events::indexeddb:: { + IDBSuccessEvent, + IDBVersionChangeEvent, + IDBCompleteEvent, + IDBErrorEvent + }; + pub use webapi::events::gamepad::{ IGamepadEvent, GamepadConnectedEvent, diff --git a/src/webapi/dom_exception.rs b/src/webapi/dom_exception.rs index a94d132b..f9524ef0 100644 --- a/src/webapi/dom_exception.rs +++ b/src/webapi/dom_exception.rs @@ -143,6 +143,61 @@ impl IDomException for InvalidPointerId {} error_boilerplate! { InvalidPointerId, dom_exception = "InvalidPointerId" } +/// The cursor is currently being iterated or has iterated past its end. +// https://heycam.github.io/webidl/#transactioninactiveerror +#[derive(Clone, Debug, ReferenceType)] +#[reference(subclass_of(Error, DomException))] +pub struct TransactionInactiveError( Reference ); + +impl IError for TransactionInactiveError {} +impl IDomException for TransactionInactiveError {} + +error_boilerplate! { TransactionInactiveError, dom_exception = "TransactionInactiveError" } + +/// The key was not valid in the context it was used. +// https://heycam.github.io/webidl/#dataerror +#[derive(Clone, Debug, ReferenceType)] +#[reference(subclass_of(Error, DomException))] +pub struct DataError( Reference ); + +impl IError for DataError {} +impl IDomException for DataError {} + +error_boilerplate! { DataError, dom_exception = "DataError" } + +/// The transaction mode is read only. +// https://heycam.github.io/webidl/#readonlyerror +#[derive(Clone, Debug, ReferenceType)] +#[reference(subclass_of(Error, DomException))] +pub struct ReadOnlyError( Reference ); + +impl IError for ReadOnlyError {} +impl IDomException for ReadOnlyError {} + +error_boilerplate! { ReadOnlyError, dom_exception = "ReadOnlyError" } + +/// The data being stored could not be cloned by the internal structured cloning algorithm. +// https://heycam.github.io/webidl/#datacloneerror +#[derive(Clone, Debug, ReferenceType)] +#[reference(subclass_of(Error, DomException))] +pub struct DataCloneError( Reference ); + +impl IError for DataCloneError {} +impl IDomException for DataCloneError {} + +error_boilerplate! { DataCloneError, dom_exception = "DataCloneError" } + +/// An index or an object store already has this name +// https://heycam.github.io/webidl/#constrainterror +#[derive(Clone, Debug, ReferenceType)] +#[reference(subclass_of(Error, DomException))] +pub struct ConstraintError( Reference ); + +impl IError for ConstraintError {} +impl IDomException for ConstraintError {} + +error_boilerplate! { ConstraintError, dom_exception = "ConstraintError" } + /// Used to indicate that the operation was aborted. // https://heycam.github.io/webidl/#aborterror #[derive(Clone, Debug, ReferenceType)] diff --git a/src/webapi/dom_string_list.rs b/src/webapi/dom_string_list.rs new file mode 100644 index 00000000..65060c48 --- /dev/null +++ b/src/webapi/dom_string_list.rs @@ -0,0 +1,53 @@ +use webcore::value::Reference; +use webcore::try_from::TryInto; + +/// The `DOMStringList` interface is a non-fashionable retro way of representing a list of strings. +/// +/// [(JavaScript docs)](https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#the-domstringlist-interface) +#[derive(Clone, Debug, PartialEq, Eq, ReferenceType)] +#[reference(instance_of = "DOMStringList")] +pub struct DOMStringList( Reference ); + +impl DOMStringList { + + /// Returns the number of strings in the `DOMStringList`. + pub fn length( &self ) -> u32 { + js!( return @{self}.length; ).try_into().unwrap() + } + + /// Returns the string with index `index`. + pub fn item( &self, index: u32) -> Option { + js!( return @{self}.item(@{index}); ).try_into().unwrap() + } + + /// Returns true if the DOMStringList contains `string`, and false otherwise. + pub fn contains( &self, string: &str) -> bool { + js! ( return @{self}.container(@{string}); ).try_into().unwrap() + } + +} + +impl IntoIterator for DOMStringList { + type Item = String; + type IntoIter = DOMStringListIterator; + + fn into_iter(self) -> Self::IntoIter { + DOMStringListIterator { dom_string_list: self, index: 0 } + } +} + +#[derive(Debug)] +pub struct DOMStringListIterator { + dom_string_list: DOMStringList, + index: u32 +} + +impl Iterator for DOMStringListIterator { + type Item = String; + + fn next(&mut self) -> Option { + let result = self.dom_string_list.item(self.index); + self.index += 1; + result + } +} diff --git a/src/webapi/events/indexeddb.rs b/src/webapi/events/indexeddb.rs new file mode 100644 index 00000000..74fa1aea --- /dev/null +++ b/src/webapi/events/indexeddb.rs @@ -0,0 +1,74 @@ +use webcore::value::Reference; +use webapi::event::{IEvent, Event, ConcreteEvent}; +use webcore::try_from::TryInto; + +/// The `IDBSuccessEvent` handler is fired when a and Indexed DB request succeed. +/// +/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/Events/success) +#[derive(Clone, Debug, PartialEq, Eq, ReferenceType)] +#[reference(instance_of = "Event")] +#[reference(subclass_of(Event))] +pub struct IDBSuccessEvent( Reference ); + +impl IEvent for IDBSuccessEvent {} + +impl ConcreteEvent for IDBSuccessEvent { + const EVENT_TYPE: &'static str = "success"; +} + +/// This event is fired if a new verion of a database has been requested. +/// +/// [(JavaScript docs)](https://www.w3.org/TR/IndexedDB/#events) +#[derive(Clone, Debug, PartialEq, Eq, ReferenceType)] +#[reference(instance_of = "Event")] +#[reference(subclass_of(Event))] +pub struct IDBVersionChangeEvent( Reference ); + +impl IEvent for IDBVersionChangeEvent {} + +impl ConcreteEvent for IDBVersionChangeEvent { + const EVENT_TYPE: &'static str = "upgradeneeded"; +} + +impl IDBVersionChangeEvent { + /// Returns the previous version of the database. + pub fn old_version( &self ) -> u64 { + js! ( + return @{self.as_ref()}.oldVersion; + ).try_into().unwrap() + } + + /// Returns the new version of the database, or null if the database is being deleted. + pub fn new_version( &self ) -> Option { + js! ( + return @{self.as_ref()}.newVersion; + ).try_into().unwrap() + } + +} + +/// This event is fired when a transaction completes successfully. +/// https://www.w3.org/TR/IndexedDB/#transaction +#[derive(Clone, Debug, PartialEq, Eq, ReferenceType)] +#[reference(instance_of = "Event")] +#[reference(subclass_of(Event))] +pub struct IDBCompleteEvent( Reference ); + +impl IEvent for IDBCompleteEvent {} + +impl ConcreteEvent for IDBCompleteEvent { + const EVENT_TYPE: &'static str = "complete"; +} + +/// This event is fired when a transaction errors. +/// https://www.w3.org/TR/IndexedDB/#transaction +#[derive(Clone, Debug, PartialEq, Eq, ReferenceType)] +#[reference(instance_of = "Event")] +#[reference(subclass_of(Event))] +pub struct IDBErrorEvent( Reference ); + +impl IEvent for IDBErrorEvent {} + +impl ConcreteEvent for IDBErrorEvent { + const EVENT_TYPE: &'static str = "error"; +} diff --git a/src/webapi/events/mod.rs b/src/webapi/events/mod.rs index 2d298d95..4bbd2206 100644 --- a/src/webapi/events/mod.rs +++ b/src/webapi/events/mod.rs @@ -8,5 +8,6 @@ pub mod mouse; pub mod pointer; pub mod progress; pub mod socket; +pub mod indexeddb; pub mod slot; pub mod touch; diff --git a/src/webapi/indexeddb.rs b/src/webapi/indexeddb.rs new file mode 100644 index 00000000..008bde0d --- /dev/null +++ b/src/webapi/indexeddb.rs @@ -0,0 +1,1168 @@ +use webcore::value::{Value, Reference}; +use webcore::try_from::{TryFrom, TryInto}; +use webapi::event_target::{IEventTarget, EventTarget}; +use webapi::dom_exception::{DomException, InvalidStateError, TransactionInactiveError, DataError, InvalidAccessError, ReadOnlyError, DataCloneError, ConstraintError, NotFoundError}; +use webapi::error::TypeError; +use webapi::dom_string_list::DOMStringList; + +/// Used to represent the state of an IDBRequest. +/// +/// [(JavaScript docx)](https://developer.mozilla.org/en-US/docs/Web/API/IDBRequest/readyState) +#[derive(Debug)] +pub enum IDBRequestReadyState { + /// The request is pending. + Pending, + /// The request is done. + Done +} + +/// Represents the different types the source arrtibute of an IDBRequest +/// can take. +#[derive(Debug)] +pub enum IDBRequestSource { + /// Indicates no source exists, such as when calling `indexedDB.open` + Store(IDBObjectStore), + Index(IDBIndex), + Cursor(IDBCursor) +} + +/// IDBRequestSharedMethode represents the methode that are shared between +/// IDBOpenDBRequest and IDBRequest. +// https://w3c.github.io/IndexedDB/#idbrequest +pub trait IDBRequestSharedMethods : IEventTarget { + + /// The result read-only property of the `IDBRequest` interface returns the result of the request, + /// or if the request failed InvalidStateError. + /// + /// [(JavaScript docx)](https://developer.mozilla.org/en-US/docs/Web/API/IDBRequest/result) + fn result( &self ) -> Result { + js_try!( return @{self.as_ref()}.result; ).unwrap() + } + + /// Returns the error in the event of an unsuccessful request. + /// + /// [(JavaScript docx)](https://developer.mozilla.org/en-US/docs/Web/API/IDBRequest/error) + fn error(&self) -> Option { + js!( return @{self.as_ref()}.error; ).try_into().unwrap() + } + + /// Returns the source of the request. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBRequest/source) + fn source( &self ) -> Option { + let t: i32 = js!{ + if (@{self.as_ref()}.source instanceof IDBObjectStore) { + return 0; + } else if (@{self.as_ref()}.source instanceof IDBIndex) { + return 1; + } else if (@{self.as_ref()}.source instanceof IDBCursor) { + return 2; + } else { + panic!() + } + }.try_into().unwrap(); + match t { + 0 => Some(IDBRequestSource::Store(js!(return @{self.as_ref()}.source;).try_into().unwrap())), + 1 => Some(IDBRequestSource::Index(js!(return @{self.as_ref()}.source;).try_into().unwrap())), + 2 => Some(IDBRequestSource::Cursor(js!(return @{self.as_ref()}.source;).try_into().unwrap())), + _ => None + } + } + + /// The `transaction` read-only property of the `IDBRequest` interface + /// returns the transaction for the request, that is, the transaction + /// the request is being made inside. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBRequest/transaction) + fn transaction( &self ) -> Option { + let transaction : Value = js! ( + return @{self.as_ref()}.transaction; + ); + match transaction { + Value::Undefined => None, + Value::Null => None, + _ => Some(transaction.try_into().unwrap()) + } + } + + /// The `ready_state` read-only property of the `IDBRequest` interface + /// returns the state of the request. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBRequest/readyState) + fn ready_state( &self ) -> IDBRequestReadyState { + let ready_state : String = js! ( + return @{self.as_ref()}.readyState; + ).try_into().unwrap(); + + match ready_state.as_ref() { + "pending" => IDBRequestReadyState::Pending, + "done" => IDBRequestReadyState::Done, + _ => panic!("Got {} as an IDBRequestReadyState.", ready_state), + } + } + +} + +/// The `IDBRequest` interface of the IndexedDB API provides access to results +/// of asynchronous requests to databases and database objects using event +/// handlers. Events that are received are IDBSuccessEvent and IDBErrorEvent. +/// +/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBRequest) +#[derive(Clone, Debug, PartialEq, Eq, ReferenceType)] +#[reference(instance_of = "IDBRequest")] +#[reference(subclass_of(EventTarget))] +pub struct IDBRequest( Reference ); + +impl IEventTarget for IDBRequest {} +impl IDBRequestSharedMethods for IDBRequest {} + +/// Provides access to the results of requests to open or delete databases. +/// Receives `IDBBlockedEvent` and `IDBVersionChangeEvent` as well as events received by `IDBRequest`. +/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBOpenDBRequest) +#[derive(Clone, Debug, PartialEq, Eq, ReferenceType)] +#[reference(instance_of = "IDBOpenDBRequest")] +#[reference(subclass_of(EventTarget))] +pub struct IDBOpenDBRequest( Reference ); + +impl IEventTarget for IDBOpenDBRequest {} +impl IDBRequestSharedMethods for IDBOpenDBRequest {} + +impl IDBOpenDBRequest { + + /// Returns the value property as an `IDBDatabase`, or an `InvalidStateError`. + pub fn database_result(&self) -> Result { + match self.result() { + Ok(value) => Ok(value.try_into().unwrap()), + Err(error) => Err(error) + } + } +} + +/// The `IDBFactory` interface of the IndexedDB API lets applications +/// asynchronously access the indexed databases. The object that +/// implements the interface is `window.indexedDB`. +/// +/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBFactory) +// https://w3c.github.io/IndexedDB/#idbfactory +#[derive(Clone, Debug, PartialEq, Eq, ReferenceType)] +#[reference(instance_of = "IDBFactory")] +pub struct IDBFactory( Reference ); + +impl IDBFactory { + + /// Requests opening a connection to a database. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBFactory/open) + pub fn open( &self, name: &str) -> IDBOpenDBRequest { + js! ( + return @{self.as_ref()}.open(@{name}); + ).try_into().unwrap() + } + + /// Requests opening a connection to a database with a schema version. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBFactory/open) + pub fn open_with_version( &self, name: &str, version: u32) -> IDBOpenDBRequest { + js! ( + return @{self.as_ref()}.open(@{name}, @{version}); + ).try_into().unwrap() + } + + /// Requests the deletion of a database. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBFactory/deleteDatabase) + pub fn delete_database( &self, name: &str) -> IDBOpenDBRequest { + js! ( + return @{self.as_ref()}.deleteDatabase(@{name}); + ).try_into().unwrap() + } + + /// Compares two values as keys to determine equality and ordering for `IndexedDB` operations. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBFactory/cmp) + pub fn cmp( &self, first: Value, second: Value) -> i16 { + js!( + return @{self.as_ref()}.cmp(@{first.as_ref()}, @{second.as_ref()}); + ).try_into().unwrap() + } + +} + +/// The IDBCursorDirection enum indicates the direction in which a cursor is traversing the data. +/// +/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBCursor/direction) +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum IDBCursorDirection { + /// This direction causes the cursor to be opened at the start of the source. + Next, + /// This direction causes the cursor to be opened at the start of the source. For every key with duplicate values, only the first record is yielded. + NextUnique, + /// This direction causes the cursor to be opened at the end of the source. + Prev, + /// This direction causes the cursor to be opened at the end of the source. For every key with duplicate values, only the first record is yielded. + PrevUnique +} + +fn cursor_direction_to_string( direction: IDBCursorDirection) -> String { + match direction { + IDBCursorDirection::Next => "next".to_string(), + IDBCursorDirection::NextUnique => "nextunique".to_string(), + IDBCursorDirection::Prev => "prev".to_string(), + IDBCursorDirection::PrevUnique => "prevunique".to_string() + } +} + +fn string_to_cursor_direction( direction: &str) -> IDBCursorDirection { + match direction { + "next" => IDBCursorDirection::Next, + "nextunique" => IDBCursorDirection::NextUnique, + "prev" => IDBCursorDirection::Prev, + "prevunique" => IDBCursorDirection::PrevUnique, + _ => unreachable!("Unknown index direction \"{}\".", direction), + } +} + +/// This enum is used to represent the value of the soure property of +/// a `IDBCursor`. +#[derive(Debug)] +pub enum IDBCursorSource { + Store(IDBObjectStore), + Index(IDBIndex) +} + +error_enum_boilerplate! { + /// An enum of the exceptions that IDBCursorSharedMethods.advance() + /// and IDBCursorSharedMethods.next may throw. + IDBAdvanceError, + /// This IDBCursor's transaction is inactive. + TransactionInactiveError, + /// The value passed into the parameter was zero or a negative number. + TypeError, + /// The cursor is currently being iterated or has iterated past its end. + InvalidStateError +} + +error_enum_boilerplate! { + IDBContinuePrimaryKeyError, + /// This IDBCursor's transaction is inactive. + TransactionInactiveError, + /// The key parameter may have any of the following conditions: + /// * The key is not a valid key. + /// * The key is less than or equal to this cursor's position and the cursor's direction is next or nextunique. + /// * The key is greater than or equal to this cursor's position and this cursor's direction is prev or prevunique. + DataError, + /// The cursor is currently being iterated or has iterated past its end. + InvalidStateError, + /// The cursor's direction is not prev or next. + InvalidAccessError +} + +error_enum_boilerplate! { + IDBUpdateError, + /// This IDBCursor's transaction is inactive. + TransactionInactiveError, + /// The transaction mode is read only. + ReadOnlyError, + /// The cursor was created using IDBIndex.openKeyCursor, is currently being iterated, or has iterated past its end. + InvalidStateError, + /// The underlying object store uses in-line keys and the property in the value at the object store's key path does not match the key in this cursor's position. + DataError, + ///The data being stored could not be cloned by the internal structured cloning algorithm. + DataCloneError +} + +error_enum_boilerplate! { + /// Errors thrown when adding to the database. + IDBAddError, + /// This IDBCursor's transaction is inactive. + TransactionInactiveError, + /// The transaction mode is read only. + ReadOnlyError, + /// The cursor was created using IDBIndex.openKeyCursor, is currently being iterated, or has iterated past its end. + InvalidStateError, + /// The underlying object store uses in-line keys and the property in the value at the object store's key path does not match the key in this cursor's position. + DataError, + ///The data being stored could not be cloned by the internal structured cloning algorithm. + DataCloneError, + /// An operation failed because the primary key constraint was violated (due to an already existing record with the same primary key value). + ConstraintError +} + +error_enum_boilerplate! { + IDBCursorDeleteError, + /// This IDBCursor's transaction is inactive. + TransactionInactiveError, + /// The transaction mode is read-only. + ReadOnlyError, + /// The cursor was created using IDBindex.openKeyCursor, is currently being iterated, or has iterated past its end. + InvalidStateError +} + +error_enum_boilerplate! { + IDBClearError, + /// The transaction associated with this operation is in read-only mode. + ReadOnlyError, + /// This IDBObjectStore's transaction is inactive. + TransactionInactiveError +} + +/// This trait implements all the methods that are shared between +/// `IDBCursor` and `IDBCursorWithValue`. +pub trait IDBCursorSharedMethods: AsRef< Reference > { + + /// The source read-only property of the `IDBCursor` interface returns + /// the `IDBObjectStore` or `IDBIndex` that the cursor is iterating over. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBCursor/source) + fn source( &self ) -> IDBCursorSource { + let s = IDBCursorSource::Store(js!( return @{self.as_ref()}.source; ).try_into().unwrap()); + if js!( return @{self.as_ref()}.source instanceof IDBObjectStore; ).try_into().unwrap() { + s + } else if js!( return @{self.as_ref()}.source instanceof IDBIndex;).try_into().unwrap() { + s + } else { + panic!() + } + } + + /// The `direction` read-only property of the `IDBCursor` interface is + /// an enum that represents the direction of traversal of the + /// cursor (set using `IDBObjectStore.openCursor` for example). + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBCursor/direction) + fn direction( &self ) -> IDBCursorDirection { + let direction: String = js! ( return @{self.as_ref()}.direction; ).try_into().unwrap(); + return string_to_cursor_direction(&direction); + } + + /// The `key` read-only property of the `IDBCursor` interface returns the key + /// for the record at the cursor's position. If the cursor is outside its range, + /// this is set to undefined. The cursor's key can be any data type. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBCursor/key) + fn key( &self ) -> Value { + js!( + return @{self.as_ref()}.key; ) + .try_into().unwrap() + } + + /// The `primary_key` read-only property of the `IDBCursor` interface returns + /// the cursor's current effective key. If the cursor is currently being + /// iterated or has iterated outside its range, this is set to undefined. + ///The cursor's primary key can be any data type. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBCursor/primaryKey) + fn primary_key( &self ) -> Value { + js!( + return @{self.as_ref()}.primaryKey; ) + .try_into().unwrap() + } + + /// The advance() method of the IDBCursor interface sets the number of times + /// a cursor should move its position forward. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBCursor/advance) + fn advance( &self, count: u32) -> Result<(), IDBAdvanceError> { + js_try!( @{self.as_ref()}.advance(@{count}); ).unwrap() + } + + /// The next() method of the IDBCursor interface advances the cursor to the + /// next position along its direction, to the item whose key matches the optional + /// key parameter. If no key (None) is specified, the cursor advances to the immediate + /// next position, based on its direction. + /// + /// This function stands in for continue in the javascript interface. Continue + /// is a keyword in rust and so needed to be renamed. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBCursor/continue) + fn next>>( &self, key: K) -> Result<(), IDBAdvanceError> { + match key.into() { + None => js_try!( @{self.as_ref()}.continue(); ).unwrap(), + Some(key) => js_try! ( @{self.as_ref()}.continue(@{key.as_ref()}); ).unwrap() + } + } + + /// The continuePrimaryKey() method of the IDBCursor interface advances + /// the cursor to the to the item whose key matches the key parameter as + /// well as whose primary key matches the primary key parameter. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBCursor/continuePrimaryKey) + fn continue_primary_key( &self, key: Value, primary_key: Value) -> Result<(), IDBContinuePrimaryKeyError> { + js_try!( @{self.as_ref()}.continuePrimaryKey(@{key}, @{primary_key}); ).unwrap() + } + + /// The update() method of the IDBCursor interface returns an IDBRequest + /// object, and, in a separate thread, updates the value at the current + /// position of the cursor in the object store. If the cursor points to + /// a record that has just been deleted, a new record is created. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBCursor/update) + fn update( &self, value: Value) -> Result { + js_try!( return @{self.as_ref()}.update(@{value.as_ref()}); ).unwrap() + } + + /// The delete() method of the IDBCursor interface returns an IDBRequest + /// object, and, in a separate thread, deletes the record at the cursor's + /// position, without changing the cursor's position. Once the record is + /// deleted, the cursor's value is set to null. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBCursor/delete) + fn delete( &self ) -> Result { + js_try!( return @{self.as_ref()}.delete(); ).unwrap() + } +} + +/// The IDBCursor interface of the IndexedDB API represents a cursor for +/// traversing or iterating over multiple records in a database. +/// +/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBCursor) +// https://w3c.github.io/IndexedDB/#idbcursor +#[derive(Clone, Debug, PartialEq, Eq, ReferenceType)] +#[reference(instance_of = "IDBCursor")] +pub struct IDBCursor( Reference ); + +impl IDBCursorSharedMethods for IDBCursor {} + +/// The IDBCursorWithValue interface of the IndexedDB API represents a cursor +/// for traversing or iterating over multiple records in a database. It is +/// the same as the IDBCursor, except that it includes the value property. +/// +/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBCursorWithValue) +#[derive(Clone, Debug, PartialEq, Eq, ReferenceType)] +#[reference(instance_of = "IDBCursorWithValue")] +#[reference(subclass_of(IDBCursor))] +pub struct IDBCursorWithValue( Reference ); + +impl IDBCursorSharedMethods for IDBCursorWithValue {} + +impl IDBCursorWithValue { + + /// Returns the value of the current cursor. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBCursorWithValue/value) + pub fn value( &self ) -> Value { + js!( return @{self}.value; ).try_into().unwrap() + } +} + +/// The IDBKeyRange interface of the IndexedDB API represents a continuous interval +/// over some data type that is used for keys. Records can be retrieved from +/// IDBObjectStore and IDBIndex objects using keys or a range of keys. You can limit +/// the range using lower and upper bounds. For example, you can iterate over all +/// values of a key in the value range A–Z. +/// +/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBKeyRange) +// https://w3c.github.io/IndexedDB/#idbkeyrange +#[derive(Clone, Debug, PartialEq, Eq, ReferenceType)] +#[reference(instance_of = "IDBKeyRange")] +pub struct IDBKeyRange( Reference ); + +impl IDBKeyRange { + + // Static construction methods: + + /// The only() method of the IDBKeyRange interface creates a new key range + /// containing a single value. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBKeyRange/only) + pub fn only( value: Value ) -> Result { + js_try! ( return IDBKeyRange.only(@{value}); ).unwrap() + } + + /// The lower_bound() method of the IDBKeyRange interface creates a new key range + /// with only a lower bound. if open is false it includes the lower endpoint + /// value and is closed. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBKeyRange/lowerBound) + pub fn lower_bound( lower: Value, open: bool ) -> Result { + js_try! ( return IDBKeyRange.lowerBound(@{lower}, @{open}); ).unwrap() + } + + /// The upper_bound() method of the IDBKeyRange interface creates a new key range + /// with only an apper bound. if open is false it includes the upper endpoint + /// value and is closed. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBKeyRange/upperBound) + pub fn upper_bound( upper: Value, open: bool ) -> Result { + js_try! ( return IDBKeyRange.upperBound(@{upper}, @{open}); ).unwrap() + } + + /// The bound() method of the IDBKeyRange interface creates a new key range + /// with the specified upper and lower bounds. The bounds can be open (that + /// is, the bounds exclude the endpoint values) or closed (that is, the bounds + /// include the endpoint values). + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBKeyRange/bound) + pub fn bound (lower: Value, upper: Value, lower_open: bool, upper_open: bool) -> Result { + js_try! ( + return IDBKeyRange.boundound(@{lower}, @{upper}, @{lower_open}, @{upper_open}); + ).unwrap() + } + + /// Lower bound of the key range. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBKeyRange/lower) + pub fn lower( &self ) -> Value { + js!( return @{self}.lower; ).try_into().unwrap() + } + + /// Upper bound of the key range. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBKeyRange/upper) + pub fn upper( &self ) -> Value { + js!( return @{self}.upper; ).try_into().unwrap() + } + + /// Returns false if the lower-bound value is included in the key range. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBKeyRange/lowerOpen) + pub fn lower_open( &self ) -> bool { + js!( return @{self}.lowerOpen; ).try_into().unwrap() + } + + /// Returns false if the upper-bound value is included in the key range. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBKeyRange/upperOpen) + pub fn upper_open( &self ) -> bool { + js!( return @{self}.upperOpen; ).try_into().unwrap() + } + + /// The includes() method of the IDBKeyRange interface returns a boolean + /// indicating whether a specified key is inside the key range. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBKeyRange/includes) + pub fn includes( &self, value: Value ) -> Result { + js_try! ( return @{self}.includes(@{value}); ).unwrap() + } +} + +#[derive(Debug)] +pub enum IDBKeyOrKeyRange { + None, + Value(Value), + Range(IDBKeyRange) +} + +error_enum_boilerplate! { + IDBSetNameError, + + /// The index, or its object store, has been deleted; or the current transaction + /// is not an upgrade transaction. You can only rename indexes during upgrade + /// transactions; that is, when the mode is "versionchange". + InvalidStateError, + + /// The current transaction is not active. + TransactionInactiveError, + + /// An index is already using the specified name + ConstraintError +} + +error_enum_boilerplate! { + /// This Error is raised by various methods ised to query object stores + /// and indexes. + IDBQueryError, + + /// This IDBIndex's transaction is inactive. + TransactionInactiveError, + + /// The key or key range provided contains an invalid key. + DataError, + + /// The IDBIndex has been deleted or removed. + InvalidStateError +} + +error_enum_boilerplate! { + IDBIndexError, + /// The source object store has been deleted, or the transaction for the object store has finished. + InvalidStateError, + /// There is no index with the given name (case-sensitive) in the database. + NotFoundError + +} + +/// This trait contains methods that are identical in both IDBIndex and IDBObjectStore +pub trait DatabaseStorage: AsRef< Reference > { + + /// Returns the name of this index or object store. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/name) + fn name( &self ) -> String { + js! ( + return @{self.as_ref()}.name; + ).try_into().unwrap() + } + + /// Returns the name of this index or object store. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/name) + fn set_name( &self, name: &str) -> Result<(), IDBSetNameError> { + js_try! ( @{self.as_ref()}.name = @{name}; ).unwrap() + } + + /// The key_path read-only property of the IDBObjectStore interface returns the + /// key path of this object store. Or in the case of an IDBIndex, the current + /// object store. + fn key_path( &self ) -> Value { + js!( return @{self.as_ref()}.keyPath; ).try_into().unwrap() + } + + /// This is for retrieving specific records from an object store or index. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/get) + fn get>( &self, query: Q) -> Result { + match query.into() { + IDBKeyOrKeyRange::None => js_try! ( + return @{self.as_ref()}.get(); + ), + IDBKeyOrKeyRange::Value(value) => js_try! ( + return @{self.as_ref()}.get(@{value.as_ref()}); + ), + IDBKeyOrKeyRange::Range(range) => js_try! ( + return @{self.as_ref()}.get(@{range.as_ref()}); + ) + }.unwrap() + } + + /// Returns an IDBRequest object, and, in a separate thread retrieves and + /// returns the record key for the object matching the specified parameter. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/getKey) + fn get_key>( &self, query: Q) -> Result { + match query.into() { + IDBKeyOrKeyRange::None => js_try! ( + return @{self.as_ref()}.getKey(); + ), + IDBKeyOrKeyRange::Value(value) => js_try! ( + return @{self.as_ref()}.getKey(@{value.as_ref()}); + ), + IDBKeyOrKeyRange::Range(range) => js_try! ( + return @{self.as_ref()}.getKey(@{range.as_ref()}); + ) + }.unwrap() + } + + /// The get_all() method retrieves all objects that are inside the index or + /// object store. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBIndex/getAll) + fn get_all>( &self, query: Q, count: Option) -> Result { + match query.into() { + IDBKeyOrKeyRange::None => js_try! ( return @{self.as_ref()}.getAll(); ), + IDBKeyOrKeyRange::Value(value) => { + match count { + None => js_try! ( return @{self.as_ref()}.getAll(@{value.as_ref()}); ), + Some(count) => js_try! ( return @{self.as_ref()}.getAll(@{value.as_ref()}, @{count}); ) + } + }, + IDBKeyOrKeyRange::Range(range) => { + match count { + None => js_try! ( return @{self.as_ref()}.getAll(@{range.as_ref()}); ), + Some(count) => js_try! ( return @{self.as_ref()}.getAll(@{range.as_ref()}, @{count}); ) + } + } + }.unwrap() + } + + // Acording to the mozilla documentation the IDBIndex version does not + // Throw DataError. + /// The get_all_keys() method returns an IDBRequest object retrieves record keys + /// for all objects matching the specified parameter or all objects if no + /// parameters are given. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/getAllKeys) + fn get_all_keys, C: Into>>( &self, query: Q, count: C) -> Result { + match query.into() { + IDBKeyOrKeyRange::None => js_try! ( return @{self.as_ref()}.getAllKeys(); ), + IDBKeyOrKeyRange::Value(value) => { + match count.into() { + None => js_try! ( return @{self.as_ref()}.getAllKeys(@{value.as_ref()}); ), + Some(count) => js_try! ( return @{self.as_ref()}.getAllKeys(@{value.as_ref()}, @{count}); ) + } + }, + IDBKeyOrKeyRange::Range(range) => { + match count.into() { + None => js_try! ( return @{self.as_ref()}.getAllKeys(@{range.as_ref()}); ), + Some(count) => js_try! ( return @{self.as_ref()}.getAllKeys(@{range.as_ref()}, @{count}); ) + } + } + }.unwrap() + } + + /// Returns an IDBRequest object, and, in a separate thread, returns the total number of records that match the provided key or IDBKeyRange + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBIndex/count) + fn count>( &self, query: Q) -> Result { + match query.into() { + IDBKeyOrKeyRange::None => js_try! ( + return @{self.as_ref()}.count(); + ), + IDBKeyOrKeyRange::Value(value) => js_try! ( + return @{self.as_ref()}.count(@{value.as_ref()}); + ), + IDBKeyOrKeyRange::Range(range) => js_try! ( + return @{self.as_ref()}.count(@{range.as_ref()}); + ) + }.unwrap() + } + + /// The open_cursor() method returns an IDBRequest object, and, in a separate + /// thread, creates a cursor over the specified key range. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBIndex/openCursor) + fn open_cursor>>( &self, range: Q, direction: Option) -> Result { + match range.into() { + None => js_try! ( return @{self.as_ref()}.openCursor(); ), + Some(range) => { + match direction { + None => js_try! ( return @{self.as_ref()}.openCursor(@{range.as_ref()}); ), + Some(direction) => js_try! ( return @{self.as_ref()}.openCursor(@{range.as_ref()}, @{cursor_direction_to_string(direction)}); ) + } + } + }.unwrap() + } + + /// The open_key_cursor() method returns an IDBRequest object, and, in a + /// separate thread, creates a cursor over the specified key range, as arranged + /// by this index. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBIndex/openKeyCursor) + fn open_key_cursor>>(&self, range: Q, direction: Option) -> Result { + match range.into() { + None => js_try! ( return @{self.as_ref()}.openKeyCursor(); ), + Some(range) => { + match direction.into() { + None => js_try! ( return @{self.as_ref()}.openKeyCursor(@{range.as_ref()}); ), + Some(direction) => js_try! ( return @{self.as_ref()}.openKeyCursor(@{range.as_ref()}, @{cursor_direction_to_string(direction)}); ) + } + } + }.unwrap() + } + +} + +/// Provides asynchronous access to an index in a database. +/// +/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBIndex) +// https://w3c.github.io/IndexedDB/#idbindex +#[derive(Clone, Debug, PartialEq, Eq, ReferenceType)] +#[reference(instance_of = "IDBIndex")] +pub struct IDBIndex( Reference ); + +impl DatabaseStorage for IDBIndex {} + +impl IDBIndex { + + /// The object_store property of the IDBIndex interface returns the name of the object store referenced by the current index. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBIndex/objectStore) + pub fn object_store( &self ) -> IDBObjectStore { + js! ( return @{self.as_ref()}.objectStore ).try_into().unwrap() + } + + /// Affects how the index behaves when the result of evaluating the index's key path yields an array. If `true`, there is one record in the index for each item in an array of keys. If `false`, then there is one record for each key that is an array. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBIndex/multiEntry) + pub fn multi_entry( &self ) -> bool { + js! ( + return @{self.as_ref()}.multiEntry; + ).try_into().unwrap() + } + + /// If `true`, this index does not allow duplicate values for a key. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBIndex/unique) + pub fn unique( &self ) -> bool { + js! ( + return @{self.as_ref()}.unique; + ).try_into().unwrap() + } + +} + +error_enum_boilerplate! { + IDBObjectStoreDeleteError, + /// This object store's transaction is inactive. + TransactionInactiveError, + /// The object store's transaction mode is read-only. + ReadOnlyError, + /// The object store has been deleted. + InvalidStateError, + /// The key is not a valid key or a key range. + DataError +} + +/// The `IDBObjectStore` interface of the IndexedDB API represents an object store in a database +/// +/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore) +// https://w3c.github.io/IndexedDB/#idbobjectstore +#[derive(Clone, Debug, PartialEq, Eq, ReferenceType)] +#[reference(instance_of = "IDBObjectStore")] +pub struct IDBObjectStore( Reference ); + +impl DatabaseStorage for IDBObjectStore {} + +/// Options for creating an index. +// https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/createIndex +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct IDBIndexParameters { + /// If `true`, the index will not allow duplicate values for a single key. + pub unique: bool, + /// If `true`, the index will add an entry in the index for each array + /// element when the keyPath resolves to an Array. If `false`, it will + /// add one single entry containing the Array. + pub multi_entry: bool, +} + +impl From for Value { + fn from(options: IDBIndexParameters) -> Self { + let mut h = std::collections::HashMap::new(); + h.insert("unique", options.unique); + h.insert("multiEntry", options.multi_entry); + h.try_into().unwrap() + } +} + +impl IDBObjectStore { + + /// The index_names read-only property of the `IDBObjectStore` interface returns a list of the + /// names of indexes on objects in this object store. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/indexNames) + pub fn index_names( &self ) -> DOMStringList { + js! ( return @{self}.indexNames; ).try_into().unwrap() + } + + /// The `IDBTransaction` object to which this object store belongs. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/transaction) + pub fn transaction( &self ) -> IDBTransaction { + js! ( + return @{self.as_ref()}.transaction; + ).try_into().unwrap() + } + + /// Returns the value of the auto increment flag for this object store. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/autoIncrement) + pub fn auto_increment( &self ) -> bool { + js! ( + return @{self.as_ref()}.autoIncrement; + ).try_into().unwrap() + } + + /// Updates a given record in a database, or inserts a new record if the given item does not already exist. + /// The key is only needed if + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/put) + pub fn put( &self, value: Value, key: Option) -> Result { + match key { + None => js_try! ( + return @{self.as_ref()}.put(@{value.as_ref()}); + ), + Some(key) => js_try! ( + return @{self.as_ref()}.put(@{value.as_ref()}, @{key.as_ref()}); + ) + }.unwrap() + } + + /// Returns an `IDBRequest` object, and, in a separate thread, creates a structured clone of the value, and stores the cloned value in the object store. This is for adding new records to an object store. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/add) + pub fn add( &self, value: Value, key: Option) -> Result { + match key { + None => js_try! ( + return @{self.as_ref()}.add(@{value.as_ref()}); + ), + Some(key) => js_try! ( + return @{self.as_ref()}.add(@{value.as_ref()}, @{key.as_ref()}); + ) + }.unwrap() + } + + /// Returns an `IDBRequest` object, and, in a separate thread, deletes the specified record or records. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/delete) + pub fn delete( &self, query: Value) -> Result { + js_try! ( + return @{self.as_ref()}.delete(@{query.as_ref()}); + ).unwrap() + } + + /// Returns an IDBRequest object, and clears this object store in a separate thread + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/clear) + pub fn clear( &self ) -> Result { + js_try! ( + return @{self.as_ref()}.clear(); + ).unwrap() + } + + /// opens a named index in the current object store + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/index) + pub fn index( &self, name: &str) -> Result { + js_try! ( + return @{self.as_ref()}.index(@{name}); + ).unwrap() + } + + /// Creates and returns a new `IDBIndex` object in the connected database. + /// + /// Note that this method must be called only from a VersionChange + /// transaction mode callback. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/createIndex) + pub fn create_index( &self, name: &str, key_path: &str) -> IDBIndex { + let options = IDBIndexParameters { unique: false, multi_entry: false }; + self.create_index_with_options(name, key_path, options) + } + + /// Creates and returns a new `IDBIndex` object in the connected database. + /// + /// Note that this method must be called only from a VersionChange + /// transaction mode callback. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/createIndex) + pub fn create_index_with_options( &self, name: &str, key_path: &str, options: IDBIndexParameters) -> IDBIndex { + let options: Value = options.into(); + js! ( + return @{self.as_ref()}.createIndex(@{name}, @{key_path}, @{options.as_ref()}); + ).try_into().unwrap() + } + + /// Destroys the index with the specified name in the connected database, used during a version upgrade. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/deleteIndex) + pub fn delete_index( &self, name: &str) { + js! { + return @{self.as_ref()}.deleteIndex(@{name}); + } + } +} + +/// An IDBTransactionMode object defining the mode for isolating access to +/// data in the current object stores. +/// +/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction/mode) +#[derive(Debug)] +pub enum IDBTransactionMode { + /// Allows data to be read but not changed. + ReadOnly, + /// Allows reading and writing of data in existing data stores to be changed. + ReadWrite, + /// Allows any operation to be performed, including ones that delete and + /// create object stores and indexes. This mode is for updating the version + /// number of transactions that were started using IDBDatabase.set_version(). + /// Transactions of this mode cannot run concurrently with other transactions. + /// Transactions in this mode are known as "upgrade transactions." + VersionChange +} + +fn transaction_mode_to_string( mode: IDBTransactionMode ) -> String { + match mode { + IDBTransactionMode::ReadOnly => "readonly".to_string(), + IDBTransactionMode::ReadWrite => "readwrite".to_string(), + IDBTransactionMode::VersionChange => "versionchange".to_string() + } +} + +fn string_to_transaction_mode( mode: &str ) -> IDBTransactionMode { + match mode { + "readonly" => IDBTransactionMode::ReadOnly, + "readwrite" => IDBTransactionMode::ReadWrite, + "versionchange" => IDBTransactionMode::VersionChange, + _ => unreachable!("Unknown transaction mode \"{}\".", mode), + } +} + +error_enum_boilerplate! { + IDBObjectStoreError, + + /// The requested object store is not in this transaction's scope. + NotFoundError, + /// The request was made on a source object that has been deleted or removed, or + /// if the transaction has finished. + InvalidStateError +} + +/// The `IDBTransaction` interface of the IndexedDB API provides a static, asynchronous transaction on a database using event handlers. +/// +/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction) +#[derive(Clone, Debug, PartialEq, Eq, ReferenceType)] +#[reference(instance_of = "IDBTransaction")] +pub struct IDBTransaction( Reference ); + +impl IEventTarget for IDBTransaction {} + +impl IDBTransaction { + + /// The object_store_names read-only property of the IDBTransaction interface returns + /// a DOMStringList of names of IDBObjectStore objects. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction/objectStoreNames) + pub fn object_store_names( &self ) -> DOMStringList { + js! ( return @{self}.objectStoreNames ).try_into().unwrap() + } + + /// The mode read-only property of the `IDBTransaction` interface returns the + /// current mode for accessing the data in the object stores in the scope of the + /// transaction (i.e. is the mode to be read-only, or do you want to write to + /// the object stores?) The default value is readonly. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction/mode + pub fn mode( &self ) -> IDBTransactionMode { + let mode: String = js!( return @{self}.mode; ).try_into().unwrap(); + string_to_transaction_mode(&mode) + } + + /// Returns the database connection with which this transaction is associated. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction/db) + pub fn db( &self ) -> IDBDatabase { + js! ( + return @{self}.db(); + ).try_into().unwrap() + } + + /// The IDBTransaction.error property of the IDBTransaction interface returns + /// one of several types of error when there is an unsuccessful transaction. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction/error) + pub fn error( &self ) -> Option { + js!( return @{self}.error; ).try_into().unwrap() + } + + /// The object_store() method of the IDBTransaction interface returns an object + /// store that has already been added to the scope of this transaction. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction/objectStore) + pub fn object_store( &self, name: &str) -> Result { + js_try! ( + return @{self.as_ref()}.objectStore(@{name}); + ).unwrap() + } + + /// The abort() method of the IDBTransaction interface rolls back all the + /// changes to objects in the database associated with this transaction. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction/abort) + pub fn abort( &self ) -> Result<(), InvalidStateError> { + js_try! ( @{self}.abort(); ).unwrap() + } + +} + +error_enum_boilerplate! { + IDBCreateObjectStoreError, + + /// Occurs if the method was not called from a versionchange transaction + /// callback. For older WebKit browsers, you must call first. + InvalidStateError, + + /// Occurs if a request is made on a source database that doesn't exist (e.g. + /// has been deleted or removed.) + TransactionInactiveError, + + /// An object store with the given name (based on case-sensitive comparison) + /// already exists in the connected database. + ConstraintError, + + /// If autoIncrement is set to true and keyPath is either an empty string or an + /// array containing an empty string. + InvalidAccessError +} + +error_enum_boilerplate! { + IDBDeleteObjectStoreError, + + /// Occurs if the method was not called from a versionchange transaction callback. + /// For older WebKit browsers, you must call first. + InvalidStateError, + + /// Occurs if a request is made on a source database that doesn't exist (e.g. has + /// been deleted or removed.) + TransactionInactiveError, + + /// You are trying to delete an object store that does not exist. Names are case sensitive. + NotFoundError +} + +/// The `IDBDatabase` interface of the IndexedDB API provides a connection to a database. +/// +/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase) +#[derive(Clone, Debug, PartialEq, Eq, ReferenceType)] +#[reference(instance_of = "IDBDatabase")] +pub struct IDBDatabase( Reference ); + +impl IEventTarget for IDBDatabase {} + +impl IDBDatabase { + + /// Returns the the name of the connected database. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase/name) + pub fn name( &self ) -> String { + js! ( + return @{self.as_ref()}.name; + ).try_into().unwrap() + } + + /// Returns the version of the connected database. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase/version) + pub fn version( &self ) -> u64 { + js! ( + return @{self.as_ref()}.version; + ).try_into().unwrap() + } + + /// The objectStoreNames read-only property of the IDBDatabase interface is a + /// DOMStringList containing a list of the names of the object stores currently + /// in the connected database. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase/objectStoreNames) + pub fn object_store_names( &self ) -> DOMStringList { + js! ( return @{self}.objectStoreNames ).try_into().unwrap() + } + + /// Immediately returns a transaction object (`IDBTransaction`) containing + /// the `IDBTransaction.object_store` method, which you can use to access + /// your object store. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase/transaction) + pub fn transaction( &self, store_names: Vec<&str>, mode: IDBTransactionMode) -> IDBTransaction { + js! ( + return @{self.as_ref()}.transaction(@{store_names}, @{transaction_mode_to_string(mode)}); + ).try_into().unwrap() + } + + /// Returns immediately and closes the connection in a separate thread. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase/close) + pub fn close( &self ) { + js! { + @{self.as_ref()}.close(); + } + } + + /// Creates and returns a new object store. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase/createObjectStore) + pub fn create_object_store( &self, name: &str, auto_increment: bool, key_path: &str) -> Result { + js_try! ( + return @{self.as_ref()}.createObjectStore(@{name}, { autoIncrement: @{auto_increment}, key_path: @{key_path} } ); + ).unwrap() + } + + /// Destroys the object store with the given name. + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase/deleteObjectStore) + pub fn delete_object_store( &self, name: &str ) -> Result<(), IDBDeleteObjectStoreError> { + js_try! ( + @{self.as_ref()}.deleteObjectStore(@{name}); + ).unwrap() + } + +} diff --git a/src/webapi/mod.rs b/src/webapi/mod.rs index 777142b4..a2bdacff 100644 --- a/src/webapi/mod.rs +++ b/src/webapi/mod.rs @@ -25,6 +25,8 @@ pub mod array_buffer; pub mod typed_array; /// A module containing XMLHttpRequest and its ReadyState pub mod xml_http_request; +pub mod indexeddb; +pub mod dom_string_list; pub mod history; pub mod web_socket; pub mod rendering_context; diff --git a/src/webapi/window.rs b/src/webapi/window.rs index 2f9eaecc..9c2fcee0 100644 --- a/src/webapi/window.rs +++ b/src/webapi/window.rs @@ -8,6 +8,7 @@ use webapi::history::History; use webapi::selection::Selection; use webcore::once::Once; use webcore::value::Value; +use webapi::indexeddb::IDBFactory; /// A handle to a pending animation frame request. #[derive(Debug)] @@ -133,6 +134,13 @@ impl Window { RequestAnimationFrameHandle(values) } + /// Returns the the IndexedDB factory. + pub fn indexed_db( &self ) -> IDBFactory { + js! ( + return window.indexedDB; + ).try_into().unwrap() + } + /// Returns the global [History](struct.History.html) object, which provides methods to /// manipulate the browser history. ///