Extract TImestamp from the result of a Function - javascript

So I have an interface like this:
interface Test {
// ...
created_at: Timestamp;
// ...
}
and I return an object from a function like this:
export const getTest = functions.https.onCall(async (data, context) => {
if (!context.auth) {
return null;
}
let snap = await admin
.firestore()
.collection('test')
.get();
return snap.docs.map(r => ({ // Add id to the output
id: r.id,
...r.data()
})[0];
});
So I should be getting the timestamp object in my application, but this is what I get in the chrome console, when I try to get the object from the function like so:
// Using AngularFireFunctions
let result: Test = await this.fns.httpsCallable<void, Test>('getTest')().toPromise();
This is the whole content of result.created_at:
When I call result.created_at.toDate() I get an ....created_at.toDate is not a function error.
What can I do to change this?
This is what I am stuck with right now:
result.created_at = new Date(result.created_at._seconds * 1000);

It looks like you’re expecting the callable function to retain the data type of the objects returned from it. That’s not quite how it works. The function will serialize the passed objects as JSON, losing all object type information, including the Timestamp type. The Timestamp is being serialized using its internal representation, which is what you’re seeing in the log output. I wouldn’t write code that depends on this, as it's obviously using hidden implementation details.
What you should probably do instead is convert that Timestamp into a normal JavaScript object, and use that in the returned result. Then, on the client side, you will need to write code that understands how you’ve chosen to represent that timestamp. It is extra work, but it insulates the client from knowing those private implementation details.
I suggest putting the seconds and nanoseconds part of the Timestamp into a plain old object, replacing the existing Timestamp field, then converting that object back into a Timestamp on the client using the Timestamp constructor that takes the seconds and nanos components as arguments.

Related

Cant read data from collection in MongoDB Atlas Trigger

New to MongoDB, very new to Atlas. I'm trying to set up a trigger such that it reads all the data from a collection named Config. This is my attempt:
exports = function(changeEvent) {
const mongodb = context.services.get("Cluster0");
const db = mongodb.db("TestDB");
var collection = db.collection("Config");
config_docs = collection.find().toArray();
console.log(JSON.stringify(config_docs));
}
the function is part of an automatically created realm application called Triggers_RealmApp, which has Cluster0 as a named linked data source. When I go into Collections in Cluster0, TestDB.Config is one of the collections.
Some notes:
it's not throwing an error, but simply returning {}.
When I change context.services.get("Cluster0"); to something else, it throws an error
When I change "TestDB" to a db that doesnt exist, or "Config" to a collection which doesn't exist, I get the same output; {}
I've tried creating new Realm apps, manually creating services, creating new databases and new collections, etc. I keep bumping into the same issue.
The mongo docs reference promises and awaits, which I haven't seen in any examples (link). I tried experimenting with that a bit and got nowhere. From what I can tell, what I've already done is the typical way of doing it.
Images:
Collection:
Linked Data Source:
I ended up taking it up with MongoDB directly, .find() is asynchronous and I was handling it incorrectly. Here is the reply straight from the horses mouth:
As I understand it, you are not getting your expected results from the query you posted above. I know it can be confusing when you are just starting out with a new technology and can't get something to work!
The issue is that the collection.find() function is an asynchronous function. That means it sends out the request but does not wait for the reply before continuing. Instead, it returns a Promise, which is an object that describes the current status of the operation. Since a Promise really isn't an array, your statment collection.find().toArray() is returning an empty object. You write this empty object to the console.log and end your function, probably before the asynchronous call even returns with your data.
There are a couple of ways to deal with this. The first is to make your function an async function and use the await operator to tell your function to wait for the collection.find() function to return before continuing.
exports = async function(changeEvent) {
const mongodb = context.services.get("Cluster0");
const db = mongodb.db("TestDB");
var collection = db.collection("Config");
config_docs = await collection.find().toArray();
console.log(JSON.stringify(config_docs));
};
Notice the async keyword on the first line, and the await keyword on the second to last line.
The second method is to use the .then function to process the results when they return:
exports = function(changeEvent) {
const mongodb = context.services.get("Cluster0");
const db = mongodb.db("TestDB");
var collection = db.collection("Config");
collection.find().toArray().then(config_docs => {
console.log(JSON.stringify(config_docs));
});
};
The connection has to be a connection to the primary replica set and the user log in credentials are of a admin level user (needs to have a permission of cluster admin)

How to modify array data with foreach

I took data from MongoDB and executed it with forEach statement.
However, I want to change the date data from 2018-08-01T00:00:00.000Z' to 'YYYY-MM.
When I ran console.log using the Moment package of Nodejs, the data I wanted was printed out.
console.log(moment(repo.created_at).format("YYYY-MM")) code normally outputs the data I want, but the original data did not change when saved as repo.created.
I don't know how to solve this problem.
/* GET MyPage Page */
router.get(`/:userId/admin/mypage`, function (req, res, next) {
let userId = req.params.userId; // UserID Variable
Repo.find({ // MongoDB
'owner.login': userId
}, function (err, repo) {
if (err) throw err;
// console.log(repo);
repo.forEach(function (repo) {
repo.created_at = moment(repo.created_at).format("YYYY-MM");
console.log(repo.created_at)
repo.updated_at = moment(repo.updated_at).format("YYYY-MM");
console.log(repo.updated_at)
})
res.render('mypage/main', {
userId: userId,
dataArray: repo,
})
})
When I console.log it it's still like this.
2018-08-01T00:00:00.000Z
2018-09-01T00:00:00.000Z
2018-09-01T00:00:00.000Z
2018-09-01T00:00:00.000Z
2018-09-01T00:00:00.000Z
2018-09-01T00:00:00.000Z
2018-08-01T00:00:00.000Z
2018-09-01T00:00:00.000Z
2017-08-01T00:00:00.000Z
But with console.log(moment(repo.created_at).format("YYYY-MM")) code. it works fine
2018-09
2018-09
2018-09
2018-09
2018-09
2018-09
2018-08
I recommend to use a map function instead of forEach, because it is normally more performant and doesn't mutate the original array. (A short demonstration about the differences between map and forEach is given in the article "JavaScript — Map vs. ForEach
What’s the difference between Map and forEach in JavaScript?" at codeburst.io.)
Furthermore you should use a different name for your parameter names, because it can cause confusion about what is meant. This is called "scope" in JavaScript and it is also an example of "variable shadowing" or "name shadowing" and can cause subtle bugs. Therefor, you should use this with caution. In your case, you could name the loop parameter holding the array entries of your repo something like "repoElement", "repoItem", "repoEntry" or you find a more descriptive name.
You also say
but the original data did not change when saved as repo.created
If you do something like repoItem.created = moment(repoItem.created_at).format("YYYY-MM");, you add a new property to the repoItem-object, but the created_at property remains untouched. In the following example, I have named the formatted dates createdAt and updatedAt, so that these new properties get created. But every new repoItem will also have the untouched created_at and updated_at properties available next to the new properties createdAt and updatedAt.
// ...
const dataArray = repo.map(repoItem => ({
...repoItem,
createdAt: moment(repoItem.created_at).format("YYYY-MM"),
updatedAt: moment(repoItem.updated_at).format("YYYY-MM")
});
res.render('mypage/main', {
userId,
dataArray
})
// ...
If you don't want to include the originally formatted data, you have to make another step and destruct the data that you want before adding it to the new object. You can also destruct everything and then delete created_at and updated_at. This can be done like this:
const newRepoItem = { ...repoItem };
delete newRepoItem.created_at;
delete newRepoItem.updated_at;
or by using the "rest" parameter like this:
const { created_at, updated_at, ...newRepoItem } = repoItem;
// now, you can use the new variable newRepoItem.
More information about removing unwanted properties from an object can be found here: Remove value from object without mutation.
I would use the version that is better understood by you and your team. In my opinion, the keyword delete is the solution that is easier to understand for most people, because it literally says what it does. But if you like destructuring and are used to it, this can be the more compact and readable solution. Don't forget, that it could also be possible, that you only have a few properties in your repoItem that you care about. In this case, you can simply destructure the items that you want to keep. This could look like in the following example:
// ...
const dataArray = repo.map(repoItem => {
const { firstPropertyToKeep, secondPropertyToKeep } = repoItem;
return ({
firstPropertyToKeep,
secondPropertyToKeep,
createdAt: moment(repoItem.created_at).format("YYYY-MM"),
updatedAt: moment(repoItem.updated_at).format("YYYY-MM")
});
res.render('mypage/main', {
userId,
dataArray
})
I have create a REPL where you can see the difference between forEach and map: https://repl.it/#helgedrews/forEach-vs-map
I hope this helps a bit.
Just use .map()
Example:
const updatedRepos = repos.map((repo)=>{
return {
...repo,
created_at: moment(repo.created_at).format("YYYY-MM");
updated_at: moment(repo.updated_at).format("YYYY-MM");
}
})
use [].map instead or set variable to save the result

In Typescript, how do I maintain an objects type when storing and then accessing said object from an array?

In typescript, I have noticed that when I take a complex object and put it in an array, when I attempt to access that object from the array it loses its type and instead simply becomes of type object.
For example
let myArray = return await this.browser.evaluate((sel: string) => Array.from(document.querySelectorAll(sel)), selector)
document.querySelectorAll(sel) returns a NodeList<Element> which is ArrayLike. Array.from should convert the NodeList into an array of elements, but once the array is formed all of the array elements lose their Element type
I have a function that will only accept parameters of type Element, but when I try to pass in myArray[0] as a parameter to said function, I get the error: Error: Unsupported target type: object
I have tried so many different things to try and get the array to maintain its object type that it would be difficult to explain each and every one of them. I am wondering how can I create an array of Elements and have them continue to be Elements when accessed later instead of generic objects
Here is a little more context in the testing I've done
I am going to this page: https://www.w3schools.com/html/html_tables.asp
and the selector I am passing into evaluate is table[id="customers"] tbody tr This should match with the 6 rows that appear in the table.
let test = await this.browser.evaluate((sel: string) =>
Array.from(document.querySelectorAll(sel)), selector)
console.log('testy1: ', test)
console.log('testy2: ', test[0])
console.log('testy3: ', typeof(test[0]))
When I run the above code this is the output I get in the console log:
testy1: [ {}, {}, {}, {}, {}, {}, {} ]
testy2: {}
testy3: object
It seems to be matching grabbing the elements from the page because it is correctly returning 6 elements. but maybe the issue is that the objects returned are empty? I am not sure.
I think my problem may be related to this question: puppeteer page.evaluate querySelectorAll return empty objects
but the solution to that question doesn't work for me because href isn't a property of object type Element
The problem here is that the function you are passing to page.evaluate is run inside the browser context (inside the browser page). To send the results from the browser context to the Node.js environment, the results are serialized.
See the return type in the docs for page.evaluate:
returns: Promise<Serializable> Promise which resolves to the return value of pageFunction
The Serializable here means that your data will be passed to the Node.js environment via JSON.stringify and there automatically parsed for you. This process will however remove any non-serializable properties of objects. This is the reason why you end up with many empty objects.
Get element handles in puppeteer
To get an handle of an element on the page, you need to use the page.$, which creates an object (in your Node.js environment) that is linked to an element inside the browser context. These kind of handles can also be passed to page.evaluate calls. To query for multiple element, you can use the function page.$$.
Code sample
Here is an example, which first queries an element and then passes the element handle to an evaluate function to read an attribute.
const elementHandle = await page.$('a');
const result = await page.evaluate(el => el.href, elementHandle);
Usage of TypeScript
The problem regarding TypeScript is, that TypeScript is not able to predict the type correctly in this scenario. For the TypeScript compiler this looks like a normal function call while in reality, the function is send to the client to be executed. Therefore, you have to cast the type yourself in this case as otherwise Typescript will just assume any as argument type:
const elementHandle = await page.$('a');
const result = await page.evaluate((el: { href: string }) => el.href, elementHandle);
i'm facing the same problem with types definition, and Element is not exported from any Lib.
To get apropriate intelisense with el methods, i changed my code with below example:
From:
...
await page.waitForTimeout(3000)
await page.waitForSelector('button[type=submit]')
await page.$eval('button[type=submit]', el => el.click())
await page.waitForNavigation({waitUntil: "load"})
await page.goto(url)
...
To this:
...
await page.waitForTimeout(3000)
await page.waitForSelector('button[type=submit]')
await page.$('button[type=submit]').then(el => {
el.click()
})
await page.waitForNavigation({waitUntil: "load"})
await page.goto(url)
...
puppeteer element intelisense example
PS: I found the issue in definition types on DefinedType Github https://github.com/DefinitelyTyped/DefinitelyTyped/issues/24419

Calling object functions with variables

I'm building a simple node.js websocket server and I want to be able to send a request from a client to the server and have it just take care of things (nothing that could cause harm). Ideally the client will pass the server an object with 2 variables, one of them for the object and the other for the specific function in that object to call. Something like this:
var callObject = {
'obj': 'testObject',
'func':'testFunc'
}
var testObject = {
func: function(){
alert('it worked');
}
}
// I would expect to be able to call it with sometihng like.
console.log( window[callObject.obj] );
console.log( window[callObject.obj][callObject.func] );
I tried calling it with global (since node.js doesn't uses it instead of a browsers window) but it won't work, it always tells me that it can't find callObject.func of undefined. If I call a console.log on callObject.obj it shows the objects variable, as a string, as expected. If run a console.log on the object itself I get the object back.
I'm guessing this is something rather simple, but my Google-fu has failed me.
My recommendation is to resist that pattern and not have client code pick any function to call. If you are not careful you have built yourself a nice large security hole. Especially if you are considering using eval.
Instead have a more explicit mapping between data sent by the client and server code. (Similar to what routes in express what give you).
You might have something like this
const commands = { doSomething() { ... } );
// Then you should be able to say:
let clientCommand = 'doSomething'; // from client
commands[clientCommand](param);
This should be pretty close to what you want to achieve.
Just make sure doSomething validates any parameters passed in.
For two levels of indirection:
const commandMap = { room: { join() { ...} }, chat: { add() { ... } }};
// note this is ES6 syntax
let clientCmd = 'room';
let clientFn = 'join';
commandMap[clientCmd][clientFn]();
I think you might just have to find the right place to put the command map. Show your web socket handler code.

How can I invoke a class method in Swift if I only have the string?

I am writing a cordova plugin for HealthKit and my task is to make sure that we can dynamically read more or less data from HK. Im doing the native part in swift. But I mostly write JavaScript so I am a bit lost in with the Swift part.
I want to be able to dynamically invoke methods having only a string.
let store = HKHealthStore()
do {
let bloodType: HKBloodTypeObject = try store.bloodType()
...
That is an example to read blood type. I am not a Swift developer, but is there any way I can do this dynamically, like I would do in javascript:
... // assuming this function receives a string as paramater
let param[0] = try store[param[0]]() // <-- how to do this in Swift?
then i can talk to a server and get a list of characteristics and load them dynamically from healthkit without having to update my code and hardcode for each possible characteristic.
Create a mapping from your parameter strings to an API enumeration.
enum API: String {
case function1 = "function1"
case function2 = "functionTwo"
case function3 = "threethreethree"
}
Create a relay function that maps the API enumeration to the Swift function.
func callTheCorrectFunction(_ api: API) {
switch api {
case .function1: updateBlood()
case .function2: spinAround()
case .function3: jump()
}
}
Construct your function call using enum's rawValue initializer.
let param = fromSomeJsonStrings[0]
let api = API(rawValue: param)
callTheCorrectFunction(api)
Alternatively, you could use a dictionary mapping from [String: Function] in much the same way.
typealias Function = () -> ()
let dict = {
"function1": updateBlood,
"function2": spinAround,
}
dict[param[0]]()

Categories

Resources