In attempting to minimize cold start time for Firebase Cloud functions, how to import a class for use inside one function? [duplicate] - node.js

This question already has an answer here:
What is the correct way to dynamically import a class inside of a Firebase Cloud function using typescript?
(1 answer)
Closed 3 years ago.
If I have a module that is only required for one of my Firebase Cloud functions, this Firebase Tutorial suggests importing that module inside just the function that needs it, in order to minimize cold start time for all other functions in a project.
This makes sense, but is it also possible to import a class which contains its own set of dependencies inside of a function?
I have a need to use Bcrypt but only in two of my functions. So I would rather not have to load it for all of my other cloud functions where it is not needed.
In my application, I have the following import:
import BcryptTool from './classes/bcrypt'; // <--- only needed in 2 functions
Here is the contents of bcrypt.ts:
import * as bcrypt from 'bcryptjs';
export default class BcryptTool {
public static hashValue(value: string, rounds: number, callback: (error: Error, hash: string) => void) : void {
bcrypt.hash(value, rounds, (error:any, hash:any) => {
callback(error, hash);
});
}
public static compare(value: string, dbHash: string, callback: (error: string | null, match: boolean | null) => void) {
bcrypt.compare(value, dbHash, (err: Error, match: boolean) => {
if(match) {
callback(null, true);
} else {
callback('Invalid value match', null);
}
});
}
}
And finally, in my Firebase Cloud functions index.ts:
const express = require('express');
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const cors = require('cors')({ origin: true });
admin.initializeApp();
const util = express();
const api = express();
...
import BcryptTool from './classes/bcrypt'; // <-- when i import here, calls to its methods within my functions work as expected
...
util.use(cors);
util.post('/verify', async (request: any, response: any) => {
// according to Doug's answer below i should be able to attempt to import here as a solution using a dynamic import expression like so:
const BcryptTool = await import('./classes/bcrypt');
// but the following subsequent call to .compare() fails
BcryptTool.compare(...)
// VS Code hinting shows an error: Property 'compare' does not exist on type 'typeof import('FULL/PATH/TO/CLASS/classes/bcrypt')'
});
api.use(cors);
api.post('/endpoint/foo', async (request: any, response: any) => {
// I do not need Bcrypt here
});
api.post('/endpoint/bar', async (request: any, response: any) => {
// I do not need Bcrypt here
});
Is this not possible? Am I just doing it all wrong?*

Sure, you can async (dynamic) import anywhere you want in TypeScript code. The imported symbols will be visible in the scope where you imported it, and nowhere else. It doesn't matter what the module contains.

(Posted answer on behalf of the question author, to move it from the question post).
I was not importing the class correctly. The reason along with a solution is outlined here.

Related

how to setup morgan-boddy in nestjs

I want to setup morgan-boddy as a midldleware to log requests and responses.
So I've created a function like that:
export function RequestLogging(app) {
const logger = new Logger('Request');
app.use(
morganBody(app, {
stream: { // <--- error is here "Void function return value is used "
write: (message) => logger.log(message.replace('\n', '')),
},
}),
);
}
That I call on main.ts
// main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
useRequestLogging(app);
// ...
}
However it seems does not work. I've got an error 'Void function return value is used' on line stream: {
Any idea how to fix?
UPDATE:
I tried to go different path and actually just stick morganBody in to main.ts as per docs:
import bodyParser from 'body-parser';
import morganBody from 'morgan-body';
app.use(bodyParser.json());
// hook morganBody to express app
morganBody(app); <-- getting error here "TS2345: Argument of type 'INestApplication' is not assignable to parameter of type 'Application'."
I wish there was a proper documentation how to approach in nestjs.
This is a pretty interesting middleware. It ends up needing the express instance itself because it calls app.use(morgan) and app.response.send() under the hood. I would really look into some other solution instead of something that accesses the response in this way.
Either way: this set up works
import { Logger } from '#nestjs/common';
import { NestFactory } from '#nestjs/core';
import * as morgan from 'morgan-body';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const logger = app.get(Logger);
(morgan as any)(app.getHttpAdapter().getInstance(), {
stream: {
write: (message: string) => {
logger.log(message.replace('\n', ''));
return true;
},
},
});
await app.listen(3033);
}
bootstrap();
The types for the package are wrong as well, it's not a default export, but a named one, so import morgan from 'morgan-body' doesn't work as advertised (at least it didn't for me).
The return true is necessary because write expects a stream.writable() method, which has returns a boolean. You can just default this to true. Then you have to use app.getHttpAdapter().getInstance() so that you ensure you pass the express instance to the middleware. Again, wonky setup, but it works.

What is the proper type for a request handler of a firebase HttpsFunction?

I need to split my functions into multiple files.
This is my index.ts
export const helloWorld = functions.https.onRequest((request, response) => {
functions.logger.info("Hello logs!", {structuredData: true});
response.send("Hello from Firebase!");
});
I need it to be something like:
import helloWorldHandler from "./handlers/helloWorldHandler"
export const helloWorld = functions.https.onRequest(helloWorldHandler);
So what should I type the helloWorldHandler request handler?
const helloWorldHandler : ??? = async (req,res) => {
const result = await someApi();
functions.logger.info("Hello logs!", {structuredData: true});
res.send("Hello from Firebase!");
};
export default helloWorldHandler;
I tried:
import * as functions from "firebase-functions";
const helloWorldHandler : functions.HttpsFunction = async (req,res) => { ... };
But I'm getting this error:
Type '(req: Request, res: Response) => Promise<Response>' is not assignable to type 'HttpsFunction'.
Property '__trigger' is missing in type '(req: Request, res: Response) => Promise<Response>' but required in type 'TriggerAnnotated'.
The onRequest() method, the one that should take the handler as a parameter, does not seem to give it a proper type name, rather than a function signature. Do I need to create an alias for that?
The type functions.HttpsFunction is the return type of functions.https.onRequest(), and not the argument to it. A function of this type is exported by your code and defines what needs to be deployed by the Firebase CLI (the region, memory size and so on are stored in the __trigger property).
As you want the type of the first argument to functions.https.onRequest(), you are instead looking for the type:
type HttpsOnRequestHandler = (req: functions.https.Request, resp: functions.Response<any>) => void | Promise<void>
But rather than hard-code this, you can extract it from the Firebase Functions library using either:
import * as functions from "firebase-functions";
type HttpsOnRequestHandler = Parameters<typeof functions.https.onRequest>[0];
or
import { https } from "firebase-functions";
type HttpsOnRequestHandler = Parameters<typeof https.onRequest>[0]
Note: If your code doesn't use the firebase-functions library itself, you can tell TypeScript that you only want its types using import type * as functions from "firebase-functions"; and import type { https } from "firebase-functions"; as appropriate; this removes the import from the compiled JavaScript as its not needed for running the code.
Here is what I've come up with. Still interested in knowing if is there an out-of-the-box type alias for this.
index.ts
import * as functions from "firebase-functions";
import helloWorldHandler from "./handlers/helloWorldHandler"
type Req = functions.https.Request
type Res = functions.Response
// WILL USE THIS EXPORTED TYPE
export type RequestHandler = (req: Req, res: Res) => void | Promise<void>
export const helloWorld = functions.https.onRequest(helloWorldHandler);
helloWorldHandler .ts
import { RequestHandler } from "../index";
const helloWorldHandler : RequestHandler = async (req,res) => { ... };
I'd suggest simply using the provided HttpsFunction type:
import * as functions from "firebase-functions";
import type { HttpsFunction } from "firebase-functions";
export const foo: HttpsFunction = functions.https.onRequest((req, res) => {
// the types of req and res are known here...
res.send("HELO");
});
I managed to run the new Firebase Functions v2 in TypeScript with
// import from a specific subpackage
import {onRequest} from "firebase-functions/v2/https";
export default onRequest((request, response) => {
response.send("Hello from Firebase!");
});

How to inject dependencies into Firebase/Google Cloud Functions? (unit & integration testing)

I don't know whether my question is really related to Firebase Cloud Functions, but I came across this problem trying to test my Firebase Cloud Functions.
Let's say I have a Firebase Cloud function written in NodeJS:
function.ts
import * as functions from "firebase-functions"
const admin = require("firebase-admin")
import * as authVerifier from "../../auth/authVerifier"
export default functions.https.onRequest(async (req, res) => {
let authId
try {
authId = await authVerifier.identifyClientRequest(req)
} catch (err) {
console.error(`Unauthorized request error: ${err}`)
return res.status(401).send({
error: "Unauthorized request"
})
}
}
Usually I have an interface and can easily mock any class I want to test it.
And, for example, authVerifier looks like:
authVerifier.ts
import * as express from "express"
export async function identifyClientRequest(req: express.Request) {
return true // whatever, it doesn't really matter, should be real logic here
}
I'm trying to test function.ts and I only can pass res and req into it, e.g:
function.test.ts
it("should verify client identity", async () => {
const req = {
method: "PUT"
}
const res = { }
await myFunctions(req as express.Request, res as express.Response)
// assert that authVerifier.identifyClientRequest(req) called with passed req
})
So the question is: how can I mock authVerifier.identifyClientRequest(req) to use different implementations in function.ts and in function.test.ts?
I don't really know NodeJS/TypeScript, so I wonder if I can import another mock class of authVerifier for test or something like that.
Ok, seems like I found the answer. I'll post it here just in case.
Using sinonjs, chai we can stub our class (authVerifier in that case) to return necessary results:
const chai = require("chai")
const assert = chai.assert
const sinon = require("sinon")
import * as authVerifier from "../../../src/auth/authVerifier"
it("should verify client identity", async () => {
const req = {
method: "PUT"
}
const res = mocks.createMockResponse()
const identifyClientRequestStub = sinon.stub(authVerifier, "identifyClientRequest");
const authVerifierStub = identifyClientRequestStub.resolves("UID")
await updateUser(req as express.Request, res as express.Response)
assert.isTrue(authVerifierStub.calledWith(req))
})
And the result is:

Firestore where 'IN' query on google-cloud firestore

(Using typescript for better readability. Vanilla js is always welcome)
Nodejs App, using these imports:
import { FieldPath, DocumentReference } from '#google-cloud/firestore';
and this function
async getByIds(ids: DocumentReference[]) {
const collection = this.db.client.collection('authors');
const query = await collection.where(FieldPath.documentId(), 'in', ids).get();
return query.docs.map(d => ({ id: d.id, ...d.data() }));
}
returns this very specific error:
The corresponding value for FieldPath.documentId() must be a string or a DocumentReference.
The debugger confirms that ids is in fact a DocumentReference array.
Maybe the #google-cloud/firestore package isn't aligned to the firebase one?
EDIT:
as noted by Doug in it's comment, I forgot to put the code for this.db.client. Here you are:
export class DatabaseProvider {
private _db: Firestore;
get client(): Firestore {
return this._db;
}
constructor() {
this._db = new Firestore({
projectId: ...,
keyFilename: ...
});
}
}
And used as
const db = new DatabaseProvider();
It seems like what you're trying to do is a batch get, which is available via a different method: getAll(). I think you want this:
async getByIds(ids: DocumentReference[]) {
return this.db.client.getAll(...ids);
}

What is the correct way to dynamically import a class inside of a Firebase Cloud function using typescript?

In a Firebase Cloud Function project...
I have the following typescript file at the root of my src directory right along side of my main index.ts file which imports one dependency and exports a class that includes 2 methods. This file is titled bcrypt.class.ts:
import * as bcrypt from 'bcryptjs';
export default class BcryptTool {
public static hashValue(value: string, rounds: number, callback: (error: Error, hash: string) => void) : void {
bcrypt.hash(value, rounds, (error:any, hash:any) => {
callback(error, hash);
});
}
public static compare(value: string, dbHash: string, callback: (error: string | null, match: boolean | null) => void) {
bcrypt.compare(value, dbHash, (err: Error, match: boolean) => {
if(match) {
callback(null, true);
} else {
callback('Invalid value match', null);
}
});
}
}
In my Firebase Cloud functions index.ts file I import this class and make a call to it's 'compare' method within one of my functions without issue, this works as expected:
'use strict';
const express = require('express');
const functions = require('firebase-functions');
const cors = require('cors')({ origin: true });
const admin = require('firebase-admin');
admin.initializeApp();
const api = express();
import BcryptTool from './bcrypt.class'; // <-- i import the class here
// and use it in a function
api.use(cors);
api.post('/credentials', async (request: any, response: any) => {
BcryptTool.compare(...) // <--- calls to this method succeed without issue
});
The problem
My application includes many functions, but I only need the class noted above in one of them, so in an attempt to optimize cold start time for all my other functions, I attempt to dynamically import this class inside of the function that needs it instead of importing it into the global scope as outlined above. This does not work and I cannot figure out why:
'use strict';
const express = require('express');
const functions = require('firebase-functions');
const cors = require('cors')({ origin: true });
const admin = require('firebase-admin');
admin.initializeApp();
const api = express();
api.use(cors);
api.post('/credentials', async (request: any, response: any) => {
const BcryptTool = await import('./bcrypt.class'); // <-- when i attempt to import here instead
BcryptTool.compare(...) // <--- subsequent calls to this method fail
// Additionally, VS Code hinting displays a warning: Property 'compare' does not exist on type 'typeof import('FULL/PATH/TO/MY/bcrypt.class')'
});
Is my class not written or exported correctly?
Am I not importing the class correctly inside of my cloud function?
The top-level import (import BcryptTool from './bcrypt.class';) will automatically import the default export from the bcrypt.class module. However, when using the import statement as a function (so called "dynamic import"), it will import the module itself, not the default export.
You can see the difference when you would console.log(BcryptTool) both imports:
import BcryptTool from './bcrypt.class' will show { default: { [Function: BcryptTool] hashValue: [Function], compare: [Function] } }
const BcryptTool = await require('bcrypt.class') will show { [Function: BcryptTool] hashValue: [Function], compare: [Function] }
Did you notice the default in the first console.log? That shows you imported the module, not the default.
Now actually the import BcryptTool from './bcrypt.class' syntax is syntactic sugar for doing import { default as BcryptTool } from './bcrypt.class'. If you apply this knowledge on the dynamic import, you could do this:
const BcryptToolModule = await import('./bcrypt.class');
BcryptToolModule.default.compare(...);
Or in a cleaner syntax:
const { default: BcryptTool } = await import('./bcrypt.class');
BcryptTool.compare(...);

Resources