running queries in firebase using async / await - node.js

Appreciating that firebase has added support for promises, is there a way to run a query like the following inside of an async function?:
const eventref = this.db.ref('cats/whiskers');
const value = await eventref.once('value')
Running the above returns a promise for value, I'm hoping to get the json blob that is stored at cats/whiskers.

The result of value is a snapshot, we need 1 more step to get the value. This should be like:
const eventref = this.db.ref('cats/whiskers');
const snapshot = await eventref.once('value');
const value = snapshot.val();

Related

what is the argument "arguments" passed to exec function in mongoose?

i've been trying to use cache with redis in my nodejs mongodb application but i didn't find any tutorial on how to do that except few which are using logic that seems to not be explained in mongoose documentation
const exec = mongoose.Query.prototype.exec;
mongoose.Query.prototype.exec = async
function (){
// our caching logic
return await exec.apply(this, arguments);
}
where does arguments come from ? because it seems to be undefined yet it is used
mongoose.Query.prototype.exec = async function(){
const collectionName = this.mongooseCollection.name;
if(this.cacheMe){
// You can't insert json straight to redis needs to be a string
const key = JSON.stringify({...this.getOptions(),
collectionName : collectionName, op : this.op});
const cachedResults = await redis.HGET(collectionName,key);
// this.op is the method which in our case is "find"
if (cachedResults){
// if you found cached results return it;
const result = JSON.parse(cachedResults);
return result;
}
//else
// get results from Database then cache it
const result = await exec.apply(this,arguments);
redis.HSET(collectionName, key, JSON.stringify(result) , "EX",this.cacheTime);
//Blogs - > {op: "find" , ... the original query} -> result we got from database
return result;
}
clearCachedData(collectionName, this.op);
return exec.apply(this,arguments);
}
and what is this.getOptions()?
i would be thankful if any one can explain me this logic, because i did not find any help in the documentations nor internet blogs and articls
The arguments object is a local variable that is available inside every function in JavaScript and contains the values of the arguments passed to the function.
this.getOptions() is the local method that returns the options to the query.
// A key example for mongoose Redis integration
const key = JSON.stringify({
collectionName: this.mongooseCollection.name,
op: this.op,
options: this.getOptions(),
filter: this.getFilter(),
projection: this.projection(),
populatedPaths: this.getPopulatedPaths(),
});
There are a lot of similar packages on NPM, but I highly recommend standard mongoose and redis ones to get up and running. I assumed your initialization point was similar to this post. This can also be a relevant source.

Firestore query sometimes doesn't contain updated data

I'm using firestore's node sdk. In my code I call function1, which updates a table in firestore. When that function ends I call function2, which runs a query to get a reference of the table. About 80% of the time it works, but sometimes the data I need, which was added to the document in function1 doesn't come back in the snapshot so an error is thrown.
I added an await keyword before the update, but that doesn't seem to make the code wait for the firestore update to complete.
I suppose I could also return the data I was going to update in function1 and pass it into function2, but that feels kind of hacky, though I suppose I'd save a little money because I wouldn't have to get 1 document anymore. I could also make it one big function, but that would make it an 100 line function.
Here's an abbreviated version of my code:
const function1 = async (tableId) => {
const firestore = admin.firestore();
const tableSnapshot = await firestore.collection('tables').doc(tableId).get();
await tableSnapshot.ref.update({ smallBlind: {seat: 1, amount: 5000} }) // the seat number and amount number wont always be 1 and 5000. Otherwise I wouldn't need to look it up in function2
}
const function2 = async (tableId) => {
const firestore = admin.firestore();
const tableSnapshot = await firestore.collection('tables').doc(tableId).get();
const tableData = tableSnapshot.data();
const smallBlind = tableSnapshot.data().smallBlind; // the smallBlind data is not there. so smallBlind is undefined
}
const startStuff = async () => {
await function1(42); // example tableId is passed in
await function2(42);
}
startStuff()
The above code has no async issues. I had a different async issue in another portion of my code, which caused my problem.

Azure Cosmos + Gremlin NodeJS, how to submit fluent query as script (not bytecode -- i know its not supported yet)

I'm trying to write fluent gremlin queries in nodejs for cosmos db, even though they get submitted as string. I've been through the documentation and I've seen it mentioned in a few github threads that although bytecode isn't yet supported, it is possible to submit it as scrip.
The code I have so far:
Configuring the client function:
export const CosmosConn = async (): Promise<driver.Client> => {
try {
const cosmosKey: string = await GetSecret('cosmos-key');
const cosmosEndpoint: string = await GetSecret('cosmos-endpoint');
const authenticator: driver.auth.PlainTextSaslAuthenticator = new gremlin.driver.auth.PlainTextSaslAuthenticator(
'/dbs/main/colls/main',
cosmosKey
);
const client: driver.Client = new gremlin.driver.Client(cosmosEndpoint, {
authenticator,
traversalsource: 'g',
rejectUnauthorized: true,
mimeType: 'application/vnd.gremlin-v2.0+json'
});
return client;
} catch (err) {
console.error(err);
}
};
Now these two below are temporary as i'll await that CosmosConn several times for every query, but this is for an Azure Function so i'm not optimizing yet:
export const Graph = async (query: gremlin.process.Bytecode): Promise<any> => {
const db = await CosmosConn();
const translator = new gremlin.process.Translator(
new gremlin.process.AnonymousTraversalSource()
);
return db.submit(translator.translate(query));
};
export const getGremlin = async () => {
const db = await CosmosConn();
return gremlin.process.traversal().withRemote(db);
};
Now when I try to use it:
const g = await getGremlin();
const query = g
.V()
.hasLabel('client')
.getBytecode();
const test = await Graph(query);
This of course throws out an error:
Gremlin Query Syntax Error: Script compile error: Unexpected token: 'Object'; in input: '[objectObject'. # line 1, column 9.
Have you tried to print the translator.translate(query) prior submitting?
From my experience, the translator is very limited in its support for non-trivial queries.
According to Microsoft, they plan to support fluent API on Dec 19', so probably better to wait for official support.
It was the types preventing me from initialising my translator in a way that works with CosmosDB.
const translator = new gremlin.process.Translator('g' as any);
works.
Dawn below is an example of using Translator in TypeScript to convert bytecode query to string query for CosmosDB. I don't recommend this solution, like the other response pointed out: it's limited. Use AWS Neptune instead or wait until MS implements bytecode queries in CosmosDB.
async function test(): Promise<void> {
// Connection:
const traversal = Gremlin.process.AnonymousTraversalSource.traversal;
const DriverRemoteConnection = Gremlin.driver.DriverRemoteConnection;
const g = traversal().withRemote(new DriverRemoteConnection("ws://localhost:8182/gremlin"));
// Create translator
const translator = new Gremlin.process.Translator(g);
// Convert bytecode query to string query for CosmosDB:
console.log(translator.translate(g.V().hasLabel('person').values('name').getBytecode()))
}
test();
Here is the link to tests cases to get getbytecode translation working.
https://github.com/apache/tinkerpop/blob/master/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/translator-test.js#L31
Edit:-
Here is sample test case from above link
it('should produce valid script representation from bytecode glv steps', function () {
const g = new graph.Graph().traversal();
const script = new Translator('g').translate(g.V().out('created').getBytecode());
assert.ok(script);
assert.strictEqual(script, 'g.V().out(\'created\')');
});

Cannot get FieldValue.increment working in Firestore emulator with Admin SDK

I am trying to get a simple atomic increase/decrease working in Firebase Cloud Functions with Firestore triggers.
To test these, I want to execute local tests with the Firestore emulator.
But I always get the error
FirebaseError: Function DocumentReference.update() called with invalid data. Unsupported field value: a custom object (found in field myfield)
My code is as simple as following
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
admin.initializeApp();
export const mytest = functions.firestore.document('my/{doc}').onUpdate(async (change, context) => {
return perform();
});
export async function perform(): Promise<FirebaseFirestore.WriteResult> {
const myDocRef = .... // this is a FirebaseFirestore.DocumentReference
const decrement = admin.firestore.FieldValue.increment(1);
return myDocRef.update({
myfield: decrement
});
}
In my tests I just call the perform function and then I receive the above error. It's definitely the FieldValue.increment that causes the error because if I just put a hardcoded number instead of the decrement in the update call, it works (updates to that hardcoded number).
I am on Firebase CLI 7.2.1 and emulator 1.6.2 which - according to Most efficient way to increment a value of everything in Firebase and https://github.com/firebase/firebase-js-sdk/issues/1799 should support FieldValue.increment in the emulator.
What am I doing wrong?
What version of firebase-admin are you using?
From your description it sounds like you're calling perform() directly from a Node.js script. I would recommend re-working your setup to more closely match what will actually happen in production: a client will connect directly to Firestore, create a document, and your function will be triggered.
Here's an example you can run that does this:
const admin = require('firebase-admin');
const functions = require('firebase-functions');
admin.initializeApp();
exports.createItem = functions.https.onRequest(async (request, response) => {
const item = await admin.firestore().collection("items").add({ addedAt: Date.now() });
response.send(`added ${item.id}\n`);
});
const counter = admin.firestore().collection("counters").doc("items");
exports.increment = functions.firestore.document('items/{itemId}').onCreate(async (change, context) => {
await counter.set({ total: admin.firestore.FieldValue.increment(1) }, { merge: true });
const current = await counter.get();
console.log(`incremented counter to ${current.data().total}`);
});
The first function, createItem, is an HTTP trigger that adds a document to the items/ collection. The second function is a Firestore trigger that increments a field in the counters/items document whenever a document is created.
When I run
curl -X POST http://localhost:5001/ryanpbrewster-test/us-central1/createItem
it creates a document and sends a response like
added LGOCapHSQtlXKIMEA8Do
This triggers the other function, which I can see in the emulator logs:
I function: Beginning execution of "increment"
> incremented counter to 6
I function: Finished "increment" in ~1s

combining results from several async/await calls

I want to return the consolidated results from calling many async/await functions. Consider my (wrong) pseudocode below
const getRemoteData = async function(uri) {
// get result from remote server via XMLHttpRequest
return result;
}
const finalResult {};
const result1 = getRemoteData('http://…');
result1.forEach(async record => {
// each record has a key for another URI
const result2 = await getRemoteData(record["uri"]);
// result2 has a key for yet another URI
const result3 = await getRemoteData(result2["uri"]);
finalResult[ result2["uri"] ] = result3;
})
console.log(finalResult);
Of course, since console.log(finalResult) is called before all the interim calls in the forEachloop have completed, finalResult is empty. How do I return finalResult after all the entries in result1 have been processed? I am using the latest version of nodejs and, if possible, would rather not use any other Promise library. From what I understand, the new async/await capabilities should enable me to accomplish what I want.
You need to use .map instead of .forEach in order to .map each item in result1 to a Promise that resolves once the associated result3 has been assigned to finalResult. Then, use Promise.all on that array, which will resolve once all of its Promises have resolved, after which finalResult will be populated with everything you need:
const getRemoteData = function(uri) {
// pretty sure you didn't mean to recursively call getRemoteData:
return someApiCall(uri);
}
const result1 = await getRemoteData('http://…');
const finalResult = {};
await Promise.all(result1.map(async (record) => {
const result2 = await getRemoteData(record.uri);
const result3 = await getRemoteData(result2.uri);
finalResult[result2.uri] = result3;
}));
console.log(finalResult);
(note that instead of an async function that await's something and returns it immediately, you may as well just return the Promise directly instead - also, dot notation is usually preferable to bracket notation, bracket notation is when you need to use a variable property name)

Resources