Rxdb Plugin: Using the RxCollectionBase#insert method in a plugin - rxdb

I am trying to create a plugin for rxdb.
I want to catch the exception raised by insert and return an hash with
{[fieldName: string] => [error:string]}
When using my new method though, I am getting an exception, and it seems like the method is getting called directly on the prototype rather than on each RxColletion<T, T2, T3> instance.
The error i am getting is:
TypeError: Cannot read property 'fillObjectWithDefaults' of undefined
which happens here: https://github.com/pubkey/rxdb/blob/ac9fc95b0eda276110f371afca985f949275c3f1/src/rx-collection.ts#L443
because this.schema is undefined.. The collection I am running this method on does have a schema though..
Here is my plugin code:
export const validatedInsertPlugin: RxPlugin = {
rxdb: true,
prototypes: {
RxCollection(proto: IRxCollectionBaseWithValidatedInsert) {
proto.validatedInsert = async function validatedInsert<T, D>(
doc: T
): Promise<Insert<T>> {
try {
// this is the line that raises:
const product = await proto.insert(doc);
return [true, product];
} catch (e) {
// extract errors
return [false, {} as Errors<T>];
}
};
},
},
overwritable: {},
hooks: {},
};

To answer my own question,
proto.insert is targeting the prototype, which is not what I want.
function(this: RxCollection) is what I want. I have to use this which will target the actual instance.
proto.validatedInsert = async function validatedInsert<T1>(
this: RxCollection,
doc: T1
): Promise<ValidatedInsert<T1>> {
try {
const product = await this.insert(doc); // this, not proto
return [true, product];
} catch (e) {
...

Related

Flutter - Dart : wait a forEach ends

I try to modify a string using data in each node I retrieve from Firebase database, and then to write a file with the modifed string (called "content").
Here is what I tried :
// Retrieve initial content from Firebase storage
var data = await FirebaseStorage.instance.ref().child("...").getData(1048576);
var content = new String.fromCharCodes(data);
// Edit content with each node from Firebase database
final response = await FirebaseDatabase.instance.reference().child('...').once();
response.value.forEach((jsonString) async {
...
// cacheManager.getFile returns a Future<File>
cacheManager.getFile(signedurl).then((file){
// Modify content
content=content.replaceAll('test',file.path);
});
});
// Finally write the file with content
print("test");
final localfile = File('index.html');
await localfile.writeAsString(content);
Result :
"test" is shown before the forEach ends.
I found that we can do in Dart (https://groups.google.com/a/dartlang.org/forum/#!topic/misc/GHm2cKUxUDU) :
await Future.forEach
but in my case if I do : await Future.response.value.forEach (sounds a bit weird)
then I get :
Getter not found: 'response'.
await Future.response.value.forEach((jsonString) async {
How to wait that forEach ends (with "content" modified) before to write the file with new content?
Any idea?
If you use for(... in ) instead of forEach you can use async/await
Future someMethod() async {
...
for(final jsonString in response.value) {
...
// cacheManager.getFile returns a Future<File>
cacheManager.getFile(signedurl).then((file){
// Modify content
content=content.replaceAll('test',file.path);
});
});
}
With forEach the calls fire away for each jsonString immediately and inside it await works, but forEach has return type void and that can't be awaited, only a Future can.
You defined the callback for forEach as async, which means that it runs asynchronously. In other words: the code inside of that callback runs independently of the code outside of the callback. That is exactly why print("test"); runs before the code inside of the callback.
The simplest solution is to move all code that needs information from within the callback into the callback. But there might also be a way to await all of the asynchronous callbacks, similar to how you already await the once call above it.
Update I got working what I think you want to do. With this JSON:
{
"data" : {
"key1" : {
"created" : "20181221T072221",
"name" : "I am key1"
},
"key2" : {
"created" : "20181221T072258",
"name" : "I am key 2"
},
"key3" : {
"created" : "20181221T072310",
"name" : "I am key 3"
}
},
"index" : {
"key1" : true,
"key3" : true
}
}
I can read the index, and then join the data with:
final ref = FirebaseDatabase.instance.reference().child("/53886373");
final index = await ref.child("index").once();
List<Future<DataSnapshot>> futures = [];
index.value.entries.forEach((json) async {
print(json);
futures.add(ref.child("data").child(json.key).once());
});
Future.wait(futures).then((List<DataSnapshot> responses) {
responses.forEach((snapshot) {
print(snapshot.value["name"]);
});
});
Have you tried:
File file = await cacheManager.getFile(signedurl);
content = content.replaceAll('test', file.path);
instead of:
cacheManager.getFile(signedurl).then((file){ ...});
EDIT:
Here's a fuller example trying to replicate what you have. I use a for loop instead of the forEach() method:
void main () async {
List<String> str = await getFuture();
print(str);
var foo;
for (var s in str) {
var b = await Future(() => s);
foo = b;
}
print('finish $foo');
}
getFuture() => Future(() => ['1', '2']);

Meteor 1.3 + React: detect subscription failure?

I have a simple Meteor subscription, and I display a loading message while the data is being loaded. But I don't know how to display error message if subscription failed.
export const MyAwesomeComponent = createContainer(() => {
let sub = Meteor.subscribe('some-data');
if (!sub.ready()) return { message: 'Loading...'};
if (sub.failed()) return { message: 'Failed.' }; // How to do this?
return {
data: Data.find().fetch()
}
}, MyInternalRenderComponent);
Problem is, the subscription object doesn't have a failed() method, only a ready() query. How to pass the failure of a subscription as props in a createContainer() method?
I know the Meteor.subscribe method has an onStop callback for this case, but I don't know how to glue it toghether that to pass a property.
After a lot of researching I managed to get this working and I think it answers your question.
Bear in mind I'm using Meteor 1.6, but it should give you the info to get it working on your side.
On the publication/publish:
try {
// get the data and add it to the publication
...
self.ready();
} catch (exception) {
logger.error(exception);
// send the exception to the client through the publication
this.error(new Meteor.Error('500', 'Error getting data from API', exception));
}
On the UI Component:
const errorFromApi = new ReactiveVar();
export default withTracker(({ match }) => {
const companyId = match.params._id;
let subscription;
if (!errorFromApi.get()) {
subscription = Meteor.subscribe('company.view', companyId, {
onStop: function (e) {
errorFromApi.set(e);
}
});
} else {
subscription = {
ready: () => {
return false;
}
};
}
return {
loading: !subscription.ready(),
company: Companies.findOne(companyId),
error: errorFromApi.get()
};
})(CompanyView);
From here all you need to do is get the error prop and render the component as desired.
This is the structure of the error prop (received on the onStop callback from subscribe):
{
error: String,
reason: String,
details: String
}
[Edit]
The reason there is a conditional around Meteor.subscribe() is to avoid an annoying infinite loop you'd get from the natural withTracker() updates, which would cause new subscriptions / new errors from the publication and so on.

redux-promise with Axios, and how do deal with errors?

So, I see on an error, redux-promise hands me back error: true, along with the payload, but that is once it hits the reducer... to me, decoupling the request AND error condition is a bit odd, and seems inappropriate. What is an effective way to also deal with error condition when using axios w/ reduc-promise (middleware).. here is the gist of what i have..
in action/
const request = axios(SOME_URL);
return {
type: GET_ME_STUFF,
payload: request
}
in reducer/
const startState = {
whatever: [],
error: false
}
case GET_ME_STUFF:
return {...state, startState, {stuff:action.payload.data, error: action.error? true : false}}
etc... then I can deal with the error.. so, my api call is now split into two seperate areas and that seems wrong.... there must be something I am missing here. I would think in the /actions I can pass in a callback that handles a new action etc.. or something, but not split it.
I've had to go through a similar situation. The challenge is that you likely won't be able to evaluate the results of the promise until it is at the reducer. You could handle your exceptions there but it's not the best pattern. From what I've read reducers are meant only to return appropriate pieces of state based on action.type and do nothing else.
So, enter an additional middleware, redux-thunk. Instead of returning an object, it returns a function, and it can coexist with promise.
It's explained quite well at http://danmaz74.me/2015/08/19/from-flux-to-redux-async-actions-the-easy-way/ [archived here]. Essentially, you can evaluate the promise here and dispatch through the other action creators before the promise result hits the reducers.
In your actions file, add additional action creators that would handle the success and error (and any other) states.
function getStuffSuccess(response) {
return {
type: GET_ME_STUFF_SUCCESS,
payload: response
}
}
function getStuffError(err) {
return {
type: GET_ME_STUFF_ERROR,
payload: err
}
}
export function getStuff() {
return function(dispatch) {
axios.get(SOME_URL)
.then((response) => {
dispatch(getStuffSuccess(response))
})
.catch((err) => {
dispatch(getStuffError(err))
})
}
}
return null
This is roughly to how you might translate your pseudocode to what is explained at the link. This handles evaluating the promise directly in your action creator and firing off the appropriate actions and payloads to your reducers which follows the convention of action -> reducer -> state -> component update cycle. I'm still pretty new to React/Redux myself but I hope this helps.
The accepted answer doesn't make use of redux-promise. Since the question is actually about handling errors using redux-promise I provide another answer.
In the reducer you should inspect the existence of the error attribute on the action object:
// This is the reducer
export default function(previousState = null, action) {
if (action.error) {
action.type = 'HANDLE_XHR_ERROR'; // change the type
}
switch(action.type) {
...
And change the type of the action, triggering a state change for an error handling component that you have set up for this.
You can read a bit more about it here on github.
It looks like you can catch the error where you make the dispatch, then make an separate error dispatch if it happens. It's a bit of a hack but it works.
store.dispatch (function (dispatch) {
dispatch ({
type:'FOO',
payload:axios.get(url)
})
.catch (function(err) {
dispatch ({
type:"FOO" + "_REJECTED",
payload:err
});
});
});
and in the reducer
const reducer = (state=initialState, action) => {
switch (action.type) {
case "FOO_PENDING": {
return {...state, fetching: true};
}
case "FOO_REJECTED": {
return {...state, fetching: false, error: action.payload};
}
case "FOO_FULFILLED": {
return {
...state,
fetching: false,
fetched: true,
data: action.payload,
};
}
}
return state;
};
Still using redux-promises you can do something like this which I think is an elegant way to deal with this problem.
First, set a property in the redux state that will hold any ajax errors that may occurred.
ajaxError: {},
Second, setup a reducer to handle ajax errors:
export default function ajaxErrorsReducer(state = initialState.ajaxError, action) {
if (action.error) {
const { response } = action.payload;
return {
status: response.status,
statusText: response.statusText,
message: response.data.message,
stack: response.data.stack,
};
}
return state;
}
Finally, create a very simple react component that will render errors if there are any (I am using the react-s-alert library to show nice alerts):
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import Alert from 'react-s-alert';
class AjaxErrorsHandler extends Component {
constructor(props, context) {
super(props, context);
this.STATUS_GATE_WAY_TIMEOUT = 504;
this.STATUS_SERVICE_UNAVAILABLE = 503;
}
componentWillReceiveProps(nextProps) {
if (this.props.ajaxError !== nextProps.ajaxError) {
this.showErrors(nextProps.ajaxError);
}
}
showErrors(ajaxError) {
if (!ajaxError.status) {
return;
}
Alert.error(this.getErrorComponent(ajaxError), {
position: 'top-right',
effect: 'jelly',
timeout: 'none',
});
}
getErrorComponent(ajaxError) {
let customMessage;
if (
ajaxError.status === this.STATUS_GATE_WAY_TIMEOUT ||
ajaxError.status === this.STATUS_SERVICE_UNAVAILABLE
) {
customMessage = 'The server is unavailable. It will be restored very shortly';
}
return (
<div>
<h3>{ajaxError.statusText}</h3>
<h5>{customMessage ? customMessage : ajaxError.message}</h5>
</div>
);
}
render() {
return (
<div />
);
}
}
AjaxErrorsHandler.defaultProps = {
ajaxError: {},
};
AjaxErrorsHandler.propTypes = {
ajaxError: PropTypes.object.isRequired,
};
function mapStateToProps(reduxState) {
return {
ajaxError: reduxState.ajaxError,
};
}
export default connect(mapStateToProps, null)(AjaxErrorsHandler);
You can include this component in your App component.
This might not be the best approach but it works for me. I pass the 'this' of my component as var context. Then when i get response back i just execute the methods defined in my components context. In my component i have successHdl and errorHdl. From there i can trigger more redux actions as normal. I checked all the previous answers and seem too daunting for such a trivial task.
export function updateJob(payload, context){
const request = axios.put(UPDATE_SOMETHING, payload).then(function (response) {
context.successHdl(response);
})
.catch(function (error) {
context.errorHdl(error);
});;
return {
type: UPDATE_SOMETHING,
payload: payload,
}
}
Don't use redux-promise. It overcomplicates something that's actually super simple to do yourself.
Instead read the redux docs: http://redux.js.org/docs/advanced/AsyncActions.html
It'll give you a much better understanding of how to handle this kind of interactions and you'll learn how to write something (better than) redux-promise yourself.

SQLite with Cordova: Unable to initialize database on other pages

I'm playing around SQLite in Cordova as part of an upskilling process for work and I'm hitting a brick wall. The various articles I've read around initializing the SQLite plugin from Chris Brody is to always call it in after device ready, but all examples are around the index page. What if I need to populate data on the products.html page, without also calling all other initialization calls to the database?
What I mean is, given the following JS file, called core.js:
var db,
app = {
// Application Constructor
initialize: function() {
this.bindEvents();
},
// Bind Event Listeners
//
// Bind any events that are required on startup. Common events are:
// 'load', 'deviceready', 'offline', and 'online'.
bindEvents: function() {
document.addEventListener('deviceready', this.onDeviceReady, false);
},
// deviceready Event Handler
//
// The scope of 'this' is the event. In order to call the 'receivedEvent'
// function, we must explicitly call 'app.receivedEvent(...);'
onDeviceReady: function () {
app.receivedEvent('deviceready');
},
// Update DOM on a Received Event
receivedEvent: function (id) {
app.initdb();
console.log('Received Event: ' + id);
},
initdb: function () {
try {
db = window.sqlitePlugin.openDatabase({ name: 'meatblock.db' });
if (!db) {
console.error('Database unable to initialize, it either does not exist or is null');
return false;
}
else {
return true;
}
}
catch (err) {
console.error('Database initialization error: ' + err);
}
}
};
In the receivedEvent, which bubbles up, I call my initdb() function that calls the plugin and opens up the database.
The process works like a charm, in this method I can write my SQL SELECT statement to retrieve data and display it on the page without error.
As soon as I mode the TX script outside of this, it does not work. I even call the initdb() function before it, and still, I get an error saying that it cannot open database on undefined.
in core.js, at the top, I define db globally, as some have suggested in various other blogs, but the following code, out side of the receivedEvent just does not work:
jQuery(document).ready(function ($) {
app.initdb();
db.transaction(function (tx) {
tx.executeSql('SELECT * FROM table_1', [], function (tx, results) {
var _data = results;
for (var i = 0; i < results.rows.length; i++) {
var row = results.rows.item(i);
$li = $('<li></li>').text(row);
$('.table-output').append($li);
}
}, function (e) {
alert('an error occurred trying to retrieve database from table_1');
});
}, function (e) {
alert('an error occurd');
}, function () {
alert('all done');
});
});
after calling app.initdb() just before I handle a TX, my assumption is that it would open the database again, as at this point, right? Even if I don't use jQuery's ready statement, it just does not work, without jQuery:
app.initdb();
db.transaction(function (tx) {
tx.executeSql('SELECT * FROM table_1', [], function (tx, results) {
var _data = results;
for (var i = 0; i < results.rows.length; i++) {
var row = results.rows.item(i);
$li = jQuery('<li></li>').text(row);
jQuery('.table-output').append($li);
}
}, function (e) {
alert('an error occurred trying to retrieve database from table_1');
});
}, function (e) {
alert('an error occurd');
}, function () {
alert('all done');
});
I'm sure there is something that I'm not getting about this. Is it impossible to open the database and retrieve data outside of the device ready statement?

Collection2, insert using method, exception from unique constraint not caught

I create a new project:
$ mrt create sandbox
$ mrt remove autopublish
$ mrt add collection2
And use the following code to create a simple collection with a unique constraint on a key
SandBoxCollection = new Meteor.Collection('sandboxcoll', {
schema: new SimpleSchema({
title: {
type: String,
min: 3,
unique: true,
index: true
}
})
});
if (Meteor.isServer) {
Meteor.publish('sandboxpub', function() {
return SandBoxCollection.find();
});
}
if (Meteor.isClient) {
Meteor.subscribe('sandboxpub');
}
Meteor.methods({
create: function(doc) {
var docId = SandBoxCollection.insert(doc, {validationContext: 'create'}, function(err, res) {
if (err) {
throw new Meteor.Error(333, SandBoxCollection.simpleSchema().namedContext('create').invalidKeys());
}
return res;
});
return docId;
}
});
I set up a simple collection, pub/sub and a method that I can use for inserts.
Then I use the browser console to issue the following commands
Let's first create a document:
Meteor.call('create', {title: 'abcd01'}, function(e,r){
console.log(e ? e : r);
});
Now let's try inserting a duplicate directly using collection.insert():
SandBoxCollection.insert({title: 'abcd01'}, function(e,r) {
console.log('error: ');
console.log(e);
console.log('errorkeys: ');
console.log(SandBoxCollection.simpleSchema().namedContext().invalidKeys());
console.log('result: ');
console.log(r);
});
We can see a proper 333 error handled by the callback and logged to the console.
Now try inserting a duplicate using the method:
Meteor.call('create', {title: 'abcd01'}, function(e,r){
console.log(e ? e : r);
});
Notice that, unlike the direct insert, the method throws an uncaught exception! Furthermore, the error is thrown from our custom throw and it has error code 333.
Why is this not handled properly? What can I do to mitigate this so that I can do something with the error (notify the user, redirect to the original documents page etc)
As of February 2014, this is an enhancement request on collection2 issue tracker at https://github.com/aldeed/meteor-collection2/issues/59
The current workaround (on the server) is to catch the error separately and feed it into a custom Meteor.Error as in:
if (Meteor.isServer) {
Meteor.methods({
insertDocument: function(collection, document) {
check(collection, String);
check(document, Object);
var documentId = '',
invalidKeys = [];
function doInsert() {
documentId = SandboxProject.Collections[collection + 'Collection'].insert(document, {validationContext: collection + 'Context'});
}
try {
doInsert();
} catch (error) {
invalidKeys = SandboxProject.Collections[collection + 'Collection'].simpleSchema().namedContext(collection + 'Context').invalidKeys();
error.invalidKeys = invalidKeys;
throw new Meteor.Error(333, error);
}
return documentId;
}
});
}
Note: This is a generic insert method that takes the namespaced collection name as a parameter and a document. It is intended to be called from the client side with a callback function which returns either the result as a document id or an error object.

Resources