Cannot call toArray() on QueryIterator using #azure/cosmos NPM - node.js

I'm querying a local emulated Cosmos DB instance using the JS #azure/cosmos package. I'm using version 3.1.1 (according to the package-lock.json) and I cannot call the toArray() function on the items for a container.
let databaseID = "database";
let collectionID = "collection";
const endpoint = "https://localhost:8081";
const key = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
const client = new CosmosClient({
endpoint,
key,
agent: new https.Agent({
rejectUnauthorized: false
})
});
let database = await client.databases.createIfNotExists({ id: databaseID });
let container = await database.containers.createIfNotExists({id: collectionID});
let items = container.items;
let readItems = items.readAll(); // crash
I get this crash:
UnhandledPromiseRejectionWarning: TypeError: container.items.readAll(...).toArray is not a function
The samples say I should be able to do this but I can't see the toArray() function in the official documentation. Interestingly toArray()is mentioned in the documentdb documentation. Maybe this function hasn't be re-implemented, or am I doing something wrong?

toArray() listed in the official github source code clearly,so i think it is supported.
const { result: results } = await container.items.query(querySpec).toArray();
if (results.length == 0) {
throw "No items found matching";
} else if (results.length > 1) {
throw "More than 1 item found matching";
}
I would suggest you putting the readAll() in the async function and use with await.(Follow this thread:Cosmos DB Query Works in Data Explorer But Not Node.js)
My sample code:
const cosmos = require('#azure/cosmos');
const CosmosClient = cosmos.CosmosClient;
const endpoint = "https://***.documents.azure.com:443/"; // Add your endpoint
const key = "***"; // Add the masterkey of the endpoint
const client = new CosmosClient({
endpoint,
key
});
const databaseId = "db";
const containerId = "coll";
async function run() {
const { container, database } = await init();
const querySpec = {
query: "SELECT r.id,r._ts FROM root r"
};
const queryOptions = {
maxItemCount : -1
}
const { result: results } = await container.items.query(querySpec).toArray();
if (results.length == 0) {
throw "No items found matching";
} else if (results.length > 1) {
throw "More than 1 item found matching";
}
}
async function init() {
const { database } = await client.databases.createIfNotExists({ id: databaseId });
const { container } = await database.containers.createIfNotExists({ id: containerId });
return { database, container };
}
run().catch(err => {
console.error(err);
});
Update Answer:
I have to say sorry that i'm mislead by the MS official document.If i navigate to Query Documents github source code by the link in the MS document:
I could found such sample code :
However, that's the master branch! Not the latest 3.1.1 version! If i switch the version at the same above page,it shows 404:https://github.com/Azure/azure-cosmos-js/blob/v3.1.1/samples/ItemManagement/app.js
More evidence, some comments:
So i believe that the #azure/cosmos V3 has been updated many things(such as toArray() method has been dropped),meanwhile the official link has not been updated.
Now,as you mentioned in your question,you could use fetchAll() to get the array of results:
const { resources: items } = await container.items.query(querySpec).fetchAll();
console.log(items)
Output:
If any more concern,please let me know.

Related

node.js console app code to manage Azure Cosmos db tutorial code throwing error as undefined

I'm running a node.js console app from the official tutorial in the azure documentation. Please find the link below:
https://learn.microsoft.com/en-us/azure/cosmos-db/sql/sql-api-nodejs-get-started
The code is below:
const cosmos = require("#azure/cosmos");
const CosmosClient = cosmos.CosmosClient;
const endpoint = "******"; // Add your endpoint
const masterKey = "******"; // Add the masterkey of the endpoint
const client = new CosmosClient({ endpoint, auth: { masterKey } });
const databaseId = "brondbid"; // Add the Database ID
const containerId = "broncollectid"; // Add the Container ID
const querySpec = {
query: "SELECT * FROM c"
};
async function run() {
const { result: results } = await client.database(databaseId).container(containerId).items.query(querySpec, { enableCrossPartitionQuery: true }).toArray();
for (var queryResult of results) {
let resultString = JSON.stringify(queryResult);
console.log(`\tQuery returned ${resultString}\n`);
}
}
async function handleError(error) {
console.log("\nAn error with code '" + error.code + "' has occurred:");
console.log("\t" + error.body || error);
}
run().catch(handleError);
When I'm running node app.js at the terminal, I'm getting this error.
An error with code 'undefined' has occurred:
undefined
Please try this code:
const cosmos = require("#azure/cosmos");
const CosmosClient = cosmos.CosmosClient;
const endpoint = "******"; // Add your endpoint
const masterKey = "******"; // Add the masterkey of the endpoint
const client = new CosmosClient({ endpoint, key: masterKey });
const databaseId = "brondbid"; // Add the Database ID
const containerId = "broncollectid"; // Add the Container ID
const querySpec = {
query: "SELECT * FROM c"
};
async function run() {
const database = client.database(databaseId);
const container = database.container(containerId);
const { resources: results } = await container.items.query(querySpec, { enableCrossPartitionQuery: true }).fetchAll();
for (var queryResult of results) {
let resultString = JSON.stringify(queryResult);
console.log(`\tQuery returned ${resultString}\n`);
}
}
function handleError(error) {
console.log(error);
console.log("\nAn error with code '" + error.code + "' has occurred:");
console.log("\t" + error.body || error);
}
run().catch(handleError);
Essentially your code was failing because there is no .toArray() method available on the query result. Since it is not a REST API error, you are getting the undefined for error code.
Also, the query returns the data in resources key and not result key like you are using.

firebase 9 query realtime database for key

I want to query realtime database using firebase 9, I previously used admin sdk, i have this example that works but I am having problems adjusting to version 9. I cant find any solid examples on how to use those modular methods to query. Any examples would be appreciated.
version 8
return admin
.database()
.ref("Countries")
.orderByChild("name")
.equalTo(query)
.once("value")
.then((data) => {
if (data.exists()) {
const obj = Object.keys(data.val())[0];
const country = data.val()[obj];
return country;
} else {
return null;
}
})
.catch((error) => {
console.log("error finding country name", error);
return null;
});
what I have tried
const { getDatabase, child, get, ref ,orderByChild,equalTo} = require("firebase/database");
const toFind = "USA"
const dbRef = ref(getDatabase());
const query = await get(child(dbRef, `countries`));
//orderByChild
//equalTo
UPDATED SOLUTION:
const dbRef = ref(db, "/countries");
const queryConstraints = [orderByChild("name"), equalTo(name)];
const country = await get(query(dbRef, ...queryConstraints));
if (country.exists()) {
console.log("found by name", country.val());
} else {
console.log("No data available");
return null;
}
} catch (error) {
console.log("====================================");
console.log("error getting country", error);
console.log("====================================");
}
You can keep adding those QueryConstraints in query():
const db = getDatabase();
const dbRef = ref(db, "/Countries")
const queryConstraints = [orderByChild("name"), equalTo('value')]
const dataSnapshot = await get(query(dbRef, ...queryConstraints))
Also checkout:
How to read, write and query data in Firebase Realtime Database using Firebase SDK v9 (Modular)
Working with lists on web

Unable to update an item in CosmosDB using the replace method with JavaScript

I am trying to create a basic REST API using Azure functions and the cosmosDB client for JavaScript. I have been successful with all the actions except the UPDATE. The cosmosDB client uses conainter.item(id,category).replace(newObject) I am unable to get the container.item().replace method to work. When I test the function in the portal or using Postman, I get a 500 error and in the portal, I get the error: Result: Failure Exception: Error: invalid input: input is not string Stack: Error: invalid input: input is not string at trimSlashFromLeftAndRight.
Example of my basic document/item properties
{
id:002,
project:"Skip rope",
category:"task",
completed: false
}
const config = require("../sharedCode/config");
const { CosmosClient } = require("#azure/cosmos");
module.exports = async function (context, req) {
const endpoint = config.endpoint;
const key = config.key;
const client = new CosmosClient({ endpoint, key });
const database = client.database(config.databaseId);
const container = database.container(config.containerId);
const theId = req.params.id;
// I am retrieving the document/item that I want to update
const { resource: docToUpdate } = await container.item(theId).read();
// I am pulling the id and category properties from the retrieved document/item
// they are used as part of the replace method
const { id, category } = docToUpdate;
// I am updating the project property of the docToUpdate document/item
docToUpdate.project = "Go fly a kite";
// I am replacing the item referred to with the ID with the updated docToUpdate object
const { resource: updatedItem } = await container
.item(id, category)
.replace(docToUpdate);
const responseMessage = {
status: 200,
message: res.message,
data: updatedItem,
};
context.res = {
// status: 200, /* Defaults to 200 */
body: responseMessage,
};
};
I Googled the heck out of this and been through the Microsoft Azure CosmosDB documents from top-to-bottom, but I can't figure out how to get this to work. I can get the other CRUD operations to work based on the examples Microsoft docs provide, but not this. Any help would be greatly appreciated.
I believe the reason you’re getting this error is because the data type of your “id” field is numeric. The data type of “id” field should be string.
UPDATE
So I tried your code and was able to run it successfully. There was one issue I noticed in your code though:
const { resource: docToUpdate } = await container.item(theId).read();
In the above line of code, you are not specifying the partition key value. If you don't specify the value, then your docToUpdate would come as undefined. In my code I used task as partition key value (I created a container with /category as the partition key).
This is the code I wrote:
const { CosmosClient } = require("#azure/cosmos");
const endpoint = 'https://account.documents.azure.com:443/';
const key = 'accountkey==';
const databaseId = 'database-name';
const containerId = 'container-name';
// const docToUpdate = {
// 'id':'e067cbae-1700-4016-bc56-eb609fa8189f',
// 'project':"Skip rope",
// 'category':"task",
// 'completed': false
// };
async function readAndUpdateDocument() {
const client = new CosmosClient({ endpoint, key });
const database = client.database(databaseId);
const container = database.container(containerId);
const theId = 'e067cbae-1700-4016-bc56-eb609fa8189f';
const { resource: docToUpdate } = await container.item(theId, 'task').read();
console.log(docToUpdate);
console.log('==============================');
const { id, category } = docToUpdate;
docToUpdate.project = "Go fly a kite";
console.log(docToUpdate);
console.log('==============================');
const { resource: updatedItem } = await container
.item(id, category)
.replace(docToUpdate);
console.log(updatedItem);
console.log('==============================');
}
readAndUpdateDocument();
Can you try by using this code?

How to initialize cosmosDB databases and containers in azure functions using the node sdk?

In my current implementation I have database initialization code that gets run on every function request, which is bad for performance reasons.
How to check if a container exists in cosmos DB using the node sdk?
It's best to create static connections on app initialization as described here:
https://learn.microsoft.com/en-us/azure/azure-functions/manage-connections#static-clients
but I'm having a bit of trouble with the initialization. Here is how the documentation describes it in javascript.
const cosmos = require('#azure/cosmos');
const endpoint = process.env.COSMOS_API_URL;
const key = process.env.COSMOS_API_KEY;
const { CosmosClient } = cosmos;
const client = new CosmosClient({ endpoint, key });
// All function invocations also reference the same database and container.
const container = client.database("MyDatabaseName").container("MyContainerName");
module.exports = async function (context) {
const { resources: itemArray } = await container.items.readAll().fetchAll();
context.log(itemArray);
}
The issues/questions I'm having are how do I do error handling if the database does not exist or if the container does not exist.
Do I need to separate my "createIfNotExists" logic from the functions app entirely?
If I try to run the createIfNotExists code on startup, I'm not able to do top level awaits and I have been getting promise rejections errors.
I'd like to do something like the following:
try
{
const cosmos = require('#azure/cosmos');
const endpoint = process.env.COSMOS_API_URL;
const key = process.env.COSMOS_API_KEY;
const { CosmosClient } = cosmos;
const client = new CosmosClient({ endpoint, key });
const db = await client.databases.createIfNotExists({id: "databaseId"});
const container1 = await db.container.createIfNotExists(containerDefinition1)
const container2 = await db.container.createIfNotExists(containerDefinition2)
}
catch(err)
{
handleError(err)
}
...
module.exports = async function (context) {
...
const {resources: items } = await container1.items.query(querySpec).fetchAll();
}
What's the best way to implement this? Any help is appreciated!
I think you need to handle each individually, for example
async onApplicationLoad() {
// Create DB if it doesn't exist
try {
await this.client.databases.createIfNotExists({ id: this.mDBId });
} catch (error) {
Logger.log(`Error creating database: ${error}`, 'AzureCosmosDbService');
}
// Create the containers if they don't exist
try {
await this.client.database(this.mDBId).containers.createIfNotExists({ id: this.mNoteContainerId });
await this.client.database(this.mDBId).containers.createIfNotExists({ id: this.mReportedNotesContainerId });
const iterator = this.client.database(this.mDBId).containers.readAll();
const { resources: containersList } = await iterator.fetchAll();
} catch (error) {
Logger.log(`Error creating containers: ${error}`, 'AzureCosmosDbService');
}
return;
}

How to use MongoDB locally and directline-js for state management in Bot Framework using NodeJs and Mongoose?

I am maintaining the bot state in a local MongoDB storage. When I am trying to hand-off the conversation to an agent using directline-js, it shows an error of BotFrameworkAdapter.sendActivity(): Missing Conversation ID. The conversation ID is being saved in MongoDB
The issue is arising when I change the middle layer from Array to MongoDB. I have already successfully implemented the same bot-human hand-off using directline-js with an Array and the default Memory Storage.
MemoryStorage in BotFramework
const { BotFrameworkAdapter, MemoryStorage, ConversationState, UserState } = require('botbuilder')
const memoryStorage = new MemoryStorage();
conversationState = new ConversationState(memoryStorage);
userState = new UserState(memoryStorage);
Middle Layer for Hand-Off to Agent
case '#connect':
const user = await this.provider.connectToAgent(conversationReference);
if (user) {
await turnContext.sendActivity(`You are connected to
${ user.userReference.user.name }\n ${ JSON.stringify(user.messages) }`);
await this.adapter.continueConversation(user.userReference, async
(userContext) => {
await userContext.sendActivity('You are now connected to an agent!');
});
}
else {
await turnContext.sendActivity('There are no users in the Queue right now.');
}
The this.adapter.continueConversation throws the error when using MongoDB.
While using Array it works fine. The MongoDB and Array object are both similar in structure.
Since this works with MemoryStorage and not your MongoDB implementation, I'm guessing that there's something wrong with your MongoDB implementation. This answer will focus on that. If this isn't the case, please provide your MongoDb implementation and/or a link to your repo and I can work off that.
Mongoose is only necessary if you want to use custom models/types/interfaces. For storage that implements BotState, you just need to write a custom Storage adapter.
The basics of this are documented here. Although written for C#, you can still apply the concepts to Node.
1. Install mongodb
npm i -S mongodb
2. Create a MongoDbStorage class file
MongoDbStorage.js
var MongoClient = require('mongodb').MongoClient;
module.exports = class MongoDbStorage {
constructor(connectionUrl, db, collection) {
this.url = connectionUrl;
this.db = db;
this.collection = collection;
this.mongoOptions = {
useNewUrlParser: true,
useUnifiedTopology: true
};
}
async read(keys) {
const client = await this.getClient();
try {
var col = await this.getCollection(client);
const data = {};
await Promise.all(keys.map(async (key) => {
const doc = await col.findOne({ _id: key });
data[key] = doc ? doc.document : null;
}));
return data;
} finally {
client.close();
}
}
async write(changes) {
const client = await this.getClient();
try {
var col = await this.getCollection(client);
await Promise.all(Object.keys(changes).map((key) => {
const changesCopy = { ...changes[key] };
const documentChange = {
_id: key,
document: changesCopy
};
const eTag = changes[key].eTag;
if (!eTag || eTag === '*') {
col.updateOne({ _id: key }, { $set: { ...documentChange } }, { upsert: true });
} else if (eTag.length > 0) {
col.replaceOne({ _id: eTag }, documentChange);
} else {
throw new Error('eTag empty');
}
}));
} finally {
client.close();
}
}
async delete(keys) {
const client = await this.getClient();
try {
var col = await this.getCollection(client);
await Promise.all(Object.keys(keys).map((key) => {
col.deleteOne({ _id: key });
}));
} finally {
client.close();
}
}
async getClient() {
const client = await MongoClient.connect(this.url, this.mongoOptions)
.catch(err => { throw err; });
if (!client) throw new Error('Unable to create MongoDB client');
return client;
}
async getCollection(client) {
return client.db(this.db).collection(this.collection);
}
};
Note: I've only done a little testing on this--enough to get it to work great with the Multi-Turn-Prompt Sample. Use at your own risk and modify as necessary.
I based this off of a combination of these three storage implementations:
memoryStorage
blobStorage
cosmosDbStorage
3. Use it in your bot
index.js
const MongoDbStorage = require('./MongoDbStorage');
const mongoDbStorage = new MongoDbStorage('mongodb://localhost:27017/', 'testDatabase', 'testCollection');
const conversationState = new ConversationState(mongoDbStorage);
const userState = new UserState(mongoDbStorage);

Resources