This question references the method examples in AWS SDK for Javascript documentation.
The examples on the page (ie. Describing a table) splits up the code into modules:
ddbClient.js (has configuration settings).
ddb_describetable.js (has code that actually runs the method).
In ddb_describetable.js, why is there an export in front of the "const params..." and the "const run = async"? Can you explain the purpose with a high level example/best practices in relation to AWS or nodejs (if examples/best practices apply)?
ddbClient.js
// Create service client module using ES6 syntax.
import { DynamoDBClient } from "#aws-sdk/client-dynamodb";
// Set the AWS Region.
const REGION = "REGION"; //e.g. "us-east-1"
// Create an Amazon DynamoDB service client object.
const ddbClient = new DynamoDBClient({ region: REGION });
export { ddbClient };
ddb_describetable.js
// Import required AWS SDK clients and commands for Node.js
import { DescribeTableCommand } from "#aws-sdk/client-dynamodb";
import { ddbClient } from "./libs/ddbClient.js";
// Set the parameters
export const params = { TableName: "TABLE_NAME" }; //TABLE_NAME
export const run = async () => {
try {
const data = await ddbClient.send(new DescribeTableCommand(params));
console.log("Success", data);
// console.log("Success", data.Table.KeySchema);
return data;
} catch (err) {
console.log("Error", err);
}
};
run();
The actual DescribeTableCommand code documentation (which doesn't show an example) doesn't imply that this structure should be used.
Related
I'm looking at this page https://docs.aws.amazon.com/sdk-for-javascript... and it seems to imply that the SDK will look at ~/.aws/credentials and take the [default] profile if there is no AWS_PROFILE environment var.
I'm running a NextJS app on my local machine trying to list S3 buckets, getting Error: Credential is missing.
I would really love not to have to specify the creds in env vars as I'll be deploying the app to ECS later where it will use an IAM Role for access.
Here's my code:
import { ListBucketsCommand } from '#aws-sdk/client-s3';
import React, { useEffect } from 'react';
import { s3Client } from '../lib/s3Client';
const S3Buckets = () => {
useEffect(() => {
async function getS3Buckets() {
const input = {};
const command = new ListBucketsCommand(input);
const res = await s3Client.send(command);
console.log(res);
}
getS3Buckets();
}, []);
return <div>{/* S3 Buckets eventually listed here */}</div>;
};
export default S3Buckets;
with the s3Client helper as below:
import { S3Client } from '#aws-sdk/client-s3';
export const s3Client = new S3Client({ region: process.env.AWS_REGION });
My scenario is to use pouch db data in ionic and I successfully added pouch db package to ionic and created a sample and it worked fine. Now I have a scenario I have the below file
000003.log in which I have all the data, but in ionic it is storing in the indexdb so how can I use this 000003.log data and copy it to indexeddb or is there any way copy the contents ?
Below is my app code
import { Injectable } from '#angular/core';
import PouchDB from 'pouchdb';
#Injectable({
providedIn: 'root'
})
export class DataService {
private database: any;
private myNotes: any;
constructor() {
this.database = new PouchDB('my-notes');
}
public addNote(theNote: string): Promise<string> {
const promise = this.database
.put({
_id: ('note:' + (new Date()).getTime()),
note: theNote
})
.then((result): string => (result.id));
return (promise);
}
getMyNotes() {
return new Promise(resolve => {
let _self = this;
this.database.allDocs({
include_docs: true,
attachments: true
}).then(function (result) {
// handle result
_self.myNotes = result.rows;
console.log("Results: " + JSON.stringify(_self.myNotes));
resolve(_self.myNotes);
}).catch(function (err) {
console.log(err);
});
});
}
How to export/import the existing database in ionic app? Do I have to store in file system or indexeddb?
By default PouchDb will use IndexDb, so its doing it correctly. If you want to change storage you need to setup a different adapter.
I don't see where you set up the options for the local adapter, so I think you are missing the local & adapter setup options to support it
Now use the correct adapter you want PouchDB here
I've created an Ionic 5/Angular repo that demonstrates how to take a local pouchdb as described in the OP and load it as a default canned database in the app.
https://github.com/ramblin-rose/canned-pouch-db
The hurdles were not huge, but I encountered some problems along the way, mainly some wrangling with respect to pouchdb's es modules and module default exports.
Specifically, the documentation for pouchdb-replication-stream is not helpful for incorporation for Ionic5/Angular. I assumed the import
import ReplicationStream from 'pouchdb-replication-stream';
Would just work, but unfortunately at runtime this dread error would popup
Type Error: Promise is not a constructor
Ouch! That's a show stopper. However I came across the pouchdb-replication-stream issue es modules
Which prompted the solution:
import ReplicationStream from 'pouchdb-replication-stream/dist/pouchdb.replication-stream.min.js';
Anyway the highlights of the repo are 'can-a-pouchdb.js' and 'data.service.ts'.
can-a-pouchdb.js
This script will create a local node pouchdb and then serialize that db to app/assets/db, which is later loaded by the ionic app.
The important bits of code:
// create some trivial docs
const docs = [];
const dt = new Date(2021, 6, 4, 12, 0, 0);
for (let i = 0; i < 10; i++, dt.setMinutes(dt.getMinutes() + i)) {
docs[i] = {
_id: "note:" + dt.getTime(),
note: `Note number ${i}`,
};
}
// always start clean - remove database dump file
fs.rmdirSync(dbPath, { recursive: true });
PouchDB.plugin(replicationStream.plugin);
PouchDB.adapter(
"writableStream",
replicationStream.adapters.writableStream
);
const db = new PouchDB(dbName);
console.log(JSON.stringify(docs));
await db.bulkDocs(docs);
//
// dump db to file.
//
fs.mkdirSync(dumpFileFolder, { recursive: true });
const ws = fs.createWriteStream(dumpFilePath);
await db.dump(ws);
To recreate the canned database run the following from the CL:
$ node can-a-pouchdb.js
data.service.ts
Here's how the app's pouchdb is hydrated from the canned database. Take note the db is using the memory adapter, because as a demo app not persisting the db is desirable.
public async init(): Promise<void> {
if (this.db === undefined) {
PouchDB.plugin(PouchdbAdapterMemory);
PouchDB.plugin(ReplicationStream.plugin);
this.db = new PouchDB(DataService.dbName, { adapter: 'memory' });
// if the db is empty, hydrate it with the canned db assets/db
const info = await this.db.info();
if (info.doc_count === 0) {
//load the asset into a string
const cannedDbText = await this.http
.get('/assets/db/mydb.dump.txt', {
responseType: 'text',
})
.toPromise();
// hydrate the db
return (this.db as any).load(
MemoryStream.createReadStream(cannedDbText)
);
}
}
Before top-level await becomes a thing, loading secrets asynchronously from AWS Secrets Manager upon startup is a bit of a pain. I'm wondering if anyone has a better solution than what I currently have.
Upon starting up my Node.JS server I'm loading all secrets from AWS Secrets manager and setting them in config files where I have a mix of hardcoded variables and secrets. Here's an example:
In aws.js
import AWS from 'aws-sdk';
const region = "eu-north-1";
AWS.config.setPromisesDependency();
const client = new AWS.SecretsManager({
region
});
export const getSecret = async(secretName) => {
const data = await client.getSecretValue({SecretId: secretName}).promise();
return data.SecretString;
}
Then in sendgridConfig.js
import { getSecret } from "./aws";
export default async() => {
const secret = JSON.parse(await getSecret("sendgridSecret"));
return {
APIKey: secret.sendgridKey,
fromEmail: "some#email.com",
toEmail: "some#email.com"
}
}
Then in some file where the config is used:
import { sendgridConfig } from "./sendgridConfig";
const myFunc = () => {
const sendgridConf = await sendgridConfig();
... do stuff with config ...
}
This works okay in async functions, but what if I'd like to use the same setup in non-async functions where I use my hardcoded variables? Then the secrets haven't been fetched yet, and I can't use them. Also I have to always await the secrets. IMO a good solution in the future could be top level await, where upon booting the server, the server will await the secrets from AWS before proceeding. I guess I could find a way to block the main thread and set the secrets, but that feels kind of hacky.
Does anyone have a better solution?
So I ended up doing the following. First I'm setting the non-async config variables in an exported object literal. Then I'm assigning values to the object literal in the sendgridConfigAsync IIFE (doesn't have to be an IFEE). That way I don't have to await the config promise. As long as the app awaits the IIFE on startup, the keys will be assigned before being accessed.
In sendgridConfig.js
import { getSecret } from "./aws";
export const sendgridConfig = {
emailFrom: process.env.sendgridFromEmail,
emailTo: process.env.sendgridToEmail
}
export const sendgridConfigAsync = (async() => {
const secret = JSON.parse(await getSecret("Sendgrid-dev"));
sendgridConfig.sendgridKey = secret.key;
})()
Then in the main config file _index.js where I import all the config files.
import { sendgridConfigAsync } from "./sendgrid";
import { twilioConfigAsync } from "./twilio";
import { appConfigAsync } from "./app";
export const setAsyncConfig = async() => {
await Promise.all([
appConfigAsync,
sendgridConfigAsync,
twilioConfigAsync
]);
}
Then in the main index.js file I'm awaiting the setAsyncConfig function first. I did also rebuild the app somewhat in order to control all function invocations and promise resolving in the desired order.
import { servicesConnect } from "../src/service/_index.js";
import { setAsyncConfig } from '$config';
import { asyncHandler } from "./middleware/async";
import { middleware } from "./middleware/_index";
import { initJobs } from "./jobs/_index"
import http from 'http';
async function startServer() {
await setAsyncConfig();
await servicesConnect();
await initJobs();
app.use(middleware);
app.server = http.createServer(app);
app.server.listen(appConfig.port);
console.log(`Started on port ${app.server.address().port}`);
}
asyncHandler(startServer());
Yup I have the same problem. Once you start with a promise, all dependencies down the line require await.
One solution is to do your awaits and only after that have all your downstream code run. Requires a slightly different software architecture.
E.g.
export const getSecretAndThenDoStuff = async(secretName) => {
const data = await client.getSecretValue({SecretId: secretName}).promise();
// instead of return data.SecretString;
runYourCodeThatNeedsSecret(data);
}
A more generic solution to the top-level await that I tend to use:
async function main() {
// Do whatever you want with await here
}
main();
Clean and simple.
I have updated the question as found the root cause of the issue.
As I have hosted my React SSR app which uses firebase database in the client serving by one of the cloud function named app throwing an error of Error: FIREBASE FATAL ERROR: Database initialized multiple times. Please make sure the format of the database URL matches with each database() call.. When I comment out one by one and deploy, works perfectly. But when I deploy together doesn't work. How do I separate these two keeping both at the same repo?
ORIGINAL Question: Why firebase cloud function throwing an error of 'The default Firebase app does not exist.'?
So I am trying out firebase function for the first time. admin.messaging() throwing me the following error. Help me figure out why?
If I look at the console I get results till console.log('deviceToken', deviceToken);
so whats wrong in const messageDone = await admin.messaging().sendToDevice(deviceToken, payload);?
const functions = require('firebase-functions');
const admin = require('firebase-admin');
exports.updateUnreadCount = functions.database.ref('/chats/{chatId}/{messageId}')
.onCreate(async(snap, context) => {
const appOptions = JSON.parse(process.env.FIREBASE_CONFIG);
appOptions.databaseAuthVariableOverride = context.auth;
const adminApp = admin.initializeApp(appOptions, 'app');
const { message, senderId, receiverUid } = snap.val();
console.log(message, senderId, receiverUid);
console.log('------------------------');
const deleteApp = () => adminApp.delete().catch(() => null);
try {
const db = adminApp.database();
const reciverUserRef = await db.ref(`users/${receiverUid}/contacts/${senderId}/`);
console.log('reciverUserRef', reciverUserRef);
const deviceTokenSnapshot = await reciverUserRef.child('deviceToken').once('value');
const deviceToken = await deviceTokenSnapshot.val();
console.log('deviceToken', deviceToken);
const payload = {
notification: {
title: 'Test Notification Title',
body: message,
sound: 'default',
badge: '1'
}
};
const messageDone = await admin.messaging().sendToDevice(deviceToken, payload);
console.log('Successfully sent message: ', JSON.stringify(messageDone));
return deleteApp().then(() => res);
} catch (err) {
console.log('error', err);
return deleteApp().then(() => Promise.reject(err));
}
});
Update1: According to this https://firebase.google.com/docs/cloud-messaging/send-message#send_to_a_topic, admin.messaging().sendToDevice(deviceToken, payload) APIs are only available in the Admin Node.js SDK?
So switched to
const payload = {
data: {
title: 'Test Notification Title',
body: message,
sound: 'default',
badge: '1'
},
token: deviceToken
};
const messageDone = await admin.messaging().send(payload);
Which is not working either. Getting an error Error: The default Firebase app does not exist. Make sure you call initializeApp() before using any of the Firebase services. Any lead will be helpful.
EDIT: Finally got the function working.
My index.js is exporting to functions, follwoing
exports.app = functions.https.onRequest(app); //React SSR
exports.updateChat = functions.database.ref('/chats/{chatId}/{messageId}').onCreate(updateChat);
exports.app is a react ssr function, which I am using to host my site. This uses database too. and throwing error of multiple database instance.
When I comment out one by one and deploy, works perfectly. But when I deploy together doesn't work. How do I separate these two keeping both at the same repo? Any suggestions, please?
You can initialise db outside export function.
const admin = require('firebase-admin');
const adminApp = admin.initializeApp(appOptions, 'app')
//continue code
Update:
const admin = require('firebase-admin');
const adminApp = admin.initializeApp(options);
async function initialize(options, apps = 'app') {
try {
const defaultApp = adminApp.name
if(defaultApp) {
const adminApp1 = admin.initializeApp(apps);
}else {
const adminApp1 = admin.initializeApp(options, apps);
}
}catch(err) {
console.error(err);
}
}
Modify this snippet as per your need and try it out
It abstracts initialize of app in another function. Just call this function at appropriate place in your code.
I'm in the process of setting a graphql endpoint with servlerless/ lambda and am receiving an error when trying to connect to the graphql playground that comes with graphql-yoga. When I go to my route that has the playground (/playground) it launches the playground interface however it just says:
Server cannot be reached
In the top right of the playground. It's worth noting i'm using the makeRemoteExecutableSchema utility to proxy to another graphql endpoint (which is my CMS called Prismic). I don't believe this is the issue as I have successfully connected to it with the playground when testing on a normal express server.
Here is the code in my handler.js
'use strict';
const { makeRemoteExecutableSchema } = require('graphql-tools');
const { PrismicLink } = require("apollo-link-prismic");
const { introspectSchema } = require('graphql-tools');
const { ACCESS_TOKEN, CMS_URL } = process.env;
const { GraphQLServerLambda } = require('graphql-yoga')
const lambda = async () => {
const link = PrismicLink({
uri: CMS_URL,
accessToken: ACCESS_TOKEN
});
const schema = await introspectSchema(link);
const executableSchema = makeRemoteExecutableSchema({
schema,
link,
});
return new GraphQLServerLambda({
schema: executableSchema,
context: req => ({ ...req })
});
}
exports.playground = async (event, context, callback) => {
context.callbackWaitsForEmptyEventLoop = false;
const graphQl = await lambda();
return graphQl.playgroundHandler(event, context, callback);
};
I have followed this guide for getting it running up till here and am fairly sure i've followed similar steps for what applies to what i'm trying to do but can't seem to figure out where i've gone wrong.
Thanks,
Could you take a look at what version of the graphql-yoga package you are using?
I had a similar problem using the Apollo server in combination with Kentico Cloud Headless CMS and I found this issue:
https://github.com/prisma/graphql-yoga/issues/267