Use Firestore collection get() return type in a function - node.js

I'm currently trying to write some code that retrieves a collection from my Firestore instance.
My codebase uses the service repository pattern to keep business logic seperate from the code that retrieves data. For this reason I've made the following code:
import { injectable, inject } from "inversify";
import { IOfficeRepository, TYPES } from "../common/types";
import { Firestore } from "#google-cloud/firestore";
#injectable()
export default class OfficeRepository implements IOfficeRepository {
private fireStoreClient: Firestore;
constructor(#inject(TYPES.FireStoreFactory) firestoreFactory: () => Firestore) {
this.fireStoreClient = firestoreFactory();
};
public async getOffice(officeId: string): Promise<FirebaseFirestore.QueryDocumentSnapshot<FirebaseFirestore.DocumentData>> {
const officeCollection = "offices";
const document = await this.fireStoreClient.collection(officeCollection).get();
return document;
};
}
What I'd like to do is return the value from the get() call to my service, in the service I will be performing checks and executing the business logic that I need.
The get() returns a Promise<FirebaseFirestore.QuerySnapshot<FirebaseFirestore.DocumentData>>, but I am unable to use this as a return type for the function in my repository. I just get the following error:
Type 'QuerySnapshot' is missing the following properties from type 'QueryDocumentSnapshot': createTime, updateTime, data, exists, and 3 more.
I've already looked-up the error, but I wasn't able to find any solution or a post where someone was trying to return the result from the get() function before performing any logic on the result.
So my question is: How would I be able to make this setup work? Or is there something I am doing wrong with this setup? If so, what would be another approach to work this out while using the service repository pattern?

Your declared return type of QueryDocumentSnapshot doesn't match the actual return type of QuerySnapshot.
This line of code:
const document = await this.fireStoreClient.collection(officeCollection).get();
performs a query for all of the documents in the officeCollection collection. As you can see from the API documentation, CollectionReference.get() yields a QuerySnapshot object. The entire set of documents will be available in the returned docs property.
It seems that you expect getOffice to return a single document instead. I'm noticing that you never used the argument officeId to narrow down your query to just the one document you want. Perhaps you meant to do something like this instead to get a single document using its ID?
const document = await this.fireStoreClient
.collection(officeCollection)
.doc(officeId)
.get();
In this case, document will be a DocumentSnapshot object.

Related

How to get gql return type of resolver from within resolver?

EDIT: Maybe a simpler question - Is there any way of accessing __typename in a resolver? like this php question https://github.com/webonyx/graphql-php/discussions/1087
I am trying to prevent manually defining the type arg of fieldWasRequested below. Can I reliably get this string from within the resolver? This would be the same as the resolvers __typename value. I found a way that kinda gets close but is ultimately too brittle.
I am using parseResolveInfo from graphql-parse-resolve-info to check if a field was requested in a resolver. In order to do this, I need to access the type name of the resolver. Currently I am manually setting this and getting the name by looking at the graphql schema file.
import { parseResolveInfo } from 'graphql-parse-resolve-info'
export const fieldWasRequested = (field: string, type: string, info: GraphQLResolveInfo) => {
const parsedInfo = parseResolveInfo(info)
const fields = parsedInfo?.fieldsByTypeName?.[type] ?? {}
return field in fields
}
export const thing1: Resolvers['thing1'] = async (parent, args, ctx, info): Promise<Thing1[]> => {
const fieldWasRequested = fieldWasRequested('field1', 'Thing1', info)
// continued ...
}
I see there is info.returnType, which is this GraphQLOutputType. This is a unioin of many types, all of which seem to have a toString method.
Logging info.returnType.toString() returns [Thing1]. This is an array of Thing1. But for other resolvers it could be Thing2, [Thing3!]!, etc. It's just not feasible to parse from this. Is there a better way?

How to send params of current request to the constructor of the service?

Update: I have fixed it by only supplying the pattern of the path of the collection, and creating a function that can parse the provided IDs and now the functions themselves create the collections when they are called upon and it also works with Typescript:)
Updated in the repository:
https://github.com/Darkbound/nestjs-firebase/tree/main/src/firebase
In the user service:
https://github.com/Darkbound/nestjs-firebase/blob/main/src/user/user.service.ts
In the purchase transactions service: https://github.com/Darkbound/nestjs-firebase/blob/main/src/user/modules/purchase-transaction/purchase-transaction.service.ts
In the purchase transactions controller: https://github.com/Darkbound/nestjs-firebase/blob/main/src/user/modules/purchase-transaction/purchase-transaction.controller.ts#L14
Now the functionality works out of the box, the service class simply needs to extend the FirebaseCollectionService and give it the pattern of the path to the collection and thats it!
https://github.com/Darkbound/nestjs-firebase I have uploaded it into a repository, you only need to add .env with the keys for firebase admin.
And the specific example: https://github.com/Darkbound/nestjs-firebase/blob/main/src/user/modules/purchase-transaction/purchase-transaction.service.ts
I have created a class that gives me the functionality to perform CRUD operations on firebase, so that I can just directly inherit from it for any of my CRUD resources, as the logic is again usually mostly the same. Just like Nestjs generator gives me all of the routes for it.
#Injectable()
export class UserService extends NestjsFirebase<User> {
constructor(#InjectFirebaseAdmin() firebase: FirebaseAdmin) {
super(firebase, "users");
// console.log(userId);
}
}
This works great, I can reuse that for any level 1 collection I have in firebase, however if I want to get into a nested collection on firebase, well thats a problem, because the path there needs to be dynamic and super(firebase, "this is no longer just users").
Say if I want to access the transactions of a user, so users/SomeUserIdXYZ/transactions, then the path is entirely dependent on the userId and is changing, therefor, I need to recreate the instance of the service (I simply need a new instance of the class), with a new path:
super(firebase, ["users", userId, "transactions"]
However with my still limited knowledge about Nestjs I know that everything in it basically is a Singleton and there is probably no way to do this? To get a new instance of the service, for every request that I have?
The solution that I can think of is, to handle that within my route functions, so if its a findTransactions:
#Get("users/:userId/transactions")
async findTransactions(#Param("userId") userId: string) {
return this.userService.findAll(`users/${userId}/transactions`);
}
And I am pretty sure that this will work, if I add a path argument to each of the functions, but this seems like coupling the Controller with what my Path in firebase should look like, instead I need to be able to give it just the params so that it can create its own path.
This is NestjsFirebase:
#Injectable()
class NestjsFirebase<T> {
constructor(#InjectFirebaseAdmin() private readonly firebase: FirebaseAdmin, private readonly collectionPath: string) {}
async findAll(userId: string): Promise<T> {
const db = new FirebaseCollectionService<T>(this.firebase, this.collectionPath);
return await db.findAll(userId);
}
}
export class FirebaseCollectionService<T> {
protected db: CollectionReference<T>;
constructor(firebase: FirebaseAdmin, collectionPath: string) {
super(firebase.db);
this.db = this.createCollectionPath(collectionPath);
}
public async findAll(id: string) {
... some logic to find all transactions ...
}
}

Automatically parse query parameter to object when defined in NestJS

I am writing a NestJS application. Some of the endpoints support sorting e.g. http://127.0.0.1:3000/api/v1/members?sort=-id&take=100 Which means sort by id descending.
This parameter arrives as a #Query parameter and is passed to my service. This service transforms it into an object which is used by TypeORM:
{
id: 'DESC'
}
I don't want to call this conversion method manually every time I need sorting.
I've tried an intercepter but this one could not easily change the request parameters into the desired object.
A pipe worked but then I still need to add #Query(new SortPipe()) for every endpoint definition.
Another option is in the repository itself. The NestJS documentation is very well written, but misses guidance in where to put what.
Is there someone who had a similar issue with converting Query parameters before they are used in NestJS, and can explain what approach is the best within NestJS?
This question might look like an opinion based question, however I am looking for the way it is supposed to be done with the NestJS philosophy in mind.
Pipes are probably the easiest way to accomplish this. Instead of adding your pipe for every endpoint definition you can add a global pipe that will be called on every endpoint. In your main.ts:
async function bootstrap() {
...
app.useGlobalPipes(new SortPipe());
...
}
You can then create a pipe like this:
import { PipeTransform, Injectable, ArgumentMetadata } from '#nestjs/common';
#Injectable()
export class SortPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
const { type } = metadata;
// Make sure to only run your logic on queries
if (type === 'query') return this.transformQuery(value);
return value;
}
transformQuery(query: any) {
if (typeof query !== 'object' || !value) return query;
const { sort } = query;
if (sort) query.sort = convertForTypeOrm(sort);
return query;
}
}
If you do not want sort value on ALL endpoints to be automatically converted, you can pass custom parameter to #Query(), for example #Query('sort'). And then:
transform(value: any, metadata: ArgumentMetadata) {
const { type, data } = metadata;
// Make sure to only run your logic on queries when 'sort' is supplied
if (type === 'query' && data === 'sort') return this.transformQuery(value);
return value;
}

How does pluck hook work in feathersjs

In the feathersjs docs the explanation provided is as follows:
pluck discards all fields except for the specified ones, either from
the data submitted or from the result. If the data is an array or a
paginated find result the hook will remove the field(s) for every
item.
import _pluck from '../common/_pluck';
import checkContextIf from './check-context-if';
import getItems from './get-items';
import replaceItems from './replace-items';
export default function (...fieldNames) {
return context => {
checkContextIf(context, 'before', ['create', 'update', 'patch'], 'pluck');
if(context.params.provider) {
replaceItems(context, _pluck(getItems(context), fieldNames));
}
return context;
};
}
The getItems utility returns the items in either hook.data or
hook.result depending on whether the hook is being used as a before or
after hook. hook.result.data or hook.result is returned for a find
method.
The returned items are always an array to simplify further processing.
The replaceItems utility is the reverse of getItems , returning the
items where they came from.
My question relates to the checkContextIf function. This function prevents the pluck hook from being called except before the create,update and patch methods. How then does the pluck hook work on the results of the query. Are not the results produced after the service call and handled in an after hook?
As the documentation states:
The getItems utility returns the items in either hook.data or hook.result depending on whether the hook is being used as a before or after hook.
hook.data is the data (body) sent with a create, patch or update request so it can be used to omit fields that you do not want to be saved to the database. This is also documented in the hooks API:
data - The request data (for create, update and patch)

Generics and Anonymous types

I have a function,
public static IPagedResponse<T> GetPagedResponse<T, TAnon>(
this IQueryable<TAnon> query,
QueryableRequestMessage request)
where T : class
{
//...
}
I'm trying to pass query as an IQueryable of an anonymous type.
var query = _repository.All.Select(
i => new //anon type
{
i.Id,
i.Name,
}
);
var result = query.GetPagedResponse<EftInterfaceDto, ??????>(request);
The issue is I don't know what to put in place of ??????? It can't seem to infer it. And any combinations using .GetType() or typeof() I have tried, failed.
I tried changing the function to be IQueryable<dynamic> but that resulted in other errors, about dynamic not being allowed in Expression trees.
HACK:
I can make it work if I change my function to this:
public static IPagedResponse<T> GetPagedResponse<T, TAnon>(
this IQueryable<TAnon> query,
QueryableRequestMessage request,
T typeSample)
where T : class
{
//...
}
And then pass in an instance of T
var result = query.GetPagedResponse(request, new SomeClassOfT());
This way, I can use type inference to determine the anonymous type TAnon, and don't need to be explicit in the call to the generic (no <types> required).
However, I don't want to do this, as it's clearly not clear what I'm doing.

Resources