Skip to content

Commit

Permalink
v.1.1.0: Add getDocumentsInCollectionGroup method. Small bug-fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
olegkorol committed Dec 2, 2024
1 parent 76051a2 commit 6511646
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 6 deletions.
35 changes: 31 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,17 +93,35 @@ Returns an array of document IDs.
### Fetch all documents in a collection

Note: The fetched documents will have an additional `_id` field, which is the
document ID.
document ID. `getDocumentsInCollectionGroup` additionally returns a `_path`
property.

To simply fetch all documents in a collection, use `getDocumentsInCollection`,
e.g.:

```typescript
const collection = await firestore.getDocumentsInCollection("my-collection");
```

If you want to fetch documents from a sub collection (aka. collection group),
you can do so with `getDocumentsInCollectionGroup`, e.g.:

```typescript
// e.g. `my-sub-collection` is a sub collection of `my-collection/{someId}`
const collection = await firestore.getDocumentsInCollectionGroup(
"my-sub-collection",
);
```

### Fetch all documents in a collection with a filter

Note: The fetched documents will have an additional `_id` field, which is the
document ID.

> You can use the same syntax for both `getDocumentsInCollection` and
> `getDocumentsInCollectionGroup` methods. The latter also returns a `_path`
> property.
```typescript
// Import the FirestoreOperator enum
import { FirestoreOperator } from "@koiztech/firestore-admin";
Expand Down Expand Up @@ -133,7 +151,11 @@ const documents = await firestore.getDocumentsInCollection("my-collection", {
["name", FirestoreOperator.EQUAL, "Ivan Petrov"],
["height", FirestoreOperator.LESS_THAN, 200],
["address.city", FirestoreOperator.EQUAL, "Moscow"], // example of a nested field
["bornAt", FirestoreOperator.GREATER_THAN, new Date("1990-01-01T12:50:00.000Z")], // example of a timestamp filter
[
"bornAt",
FirestoreOperator.GREATER_THAN,
new Date("1990-01-01T12:50:00.000Z"),
], // example of a timestamp filter
],
},
orderBy: [{ field: "createdAt", direction: "DESCENDING" }], // you can sort the results
Expand Down Expand Up @@ -184,12 +206,17 @@ await firestore.createDocument("my-collection", {

The above will be converted to a Firestore timestamp automatically.

When filtering results by timestamp, make sure to use `Date` objects as well, e.g.:
When filtering results by timestamp, make sure to use `Date` objects as well,
e.g.:

```typescript
const documents = await firestore.getDocumentsInCollection("my-collection", {
where: {
filters: [["createdAt", FirestoreOperator.GREATER_THAN, new Date("2024-12-02")]],
filters: [[
"createdAt",
FirestoreOperator.GREATER_THAN,
new Date("2024-12-02"),
]],
},
});
```
2 changes: 1 addition & 1 deletion deno.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@koiztech/firestore-admin",
"version": "1.0.4",
"version": "1.1.0",
"exports": "./mod.ts",
"tasks": {
"dev": "deno run --watch --env mod.ts",
Expand Down
99 changes: 98 additions & 1 deletion mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,12 +302,109 @@ export class FirestoreAdminClient {
this.errorHandler(data.error, "listDocumentsInCollection");
}

return data.documents.map((doc: any) => {
return data.documents?.map((doc: any) => {
const docId = doc.name.split(`/`).pop() ?? "unknown";
const documentFields = doc.fields || {};
return { ...this.documentToJson(documentFields), _id: docId };
}) || [];
}
}

/**
* Gets documents from a collection group (i.e. a sub collection)
* @param collectionId - The ID of the collection group to query
* @param options - Query options including filters, ordering, and limits
* @returns Array of documents matching the query
* @example
* ```ts
* // e.g. `orders` is a sub collection of `customers/{customerId}`
* const documents = await firestore.getDocumentsInCollectionGroup('orders', {
* where: {
* filters: [
* ['createdAt', FirestoreOperator.GREATER_THAN, new Date('2024-01-01')]
* ]
* }
* })
* ```
*/
async getDocumentsInCollectionGroup(
collectionId: string,
options: {
where?: {
filters: [string, FirestoreOperator, any][];
};
orderBy?: { field: string; direction: "ASCENDING" | "DESCENDING" }[];
limit?: number;
} = {},
): Promise<any[]> {
const structuredQuery: any = {
from: [{
collectionId,
allDescendants: true,
}],
};

if (options.where?.filters) {
structuredQuery.where = {
compositeFilter: {
op: "AND",
filters: options.where.filters.map(([field, operator, value]) => ({
fieldFilter: {
field: { fieldPath: field },
op: operator,
value: this.jsonToDocument({ value })[`fields`][`value`],
},
})),
},
};
}

if (options.orderBy) {
structuredQuery.orderBy = options.orderBy.map(({ field, direction }) => ({
field: { fieldPath: field },
direction,
}));
}

if (options.limit) {
structuredQuery.limit = options.limit;
}

const headers = await this.getHeaders();
const response = await fetch(
`${this.firestoreBaseUrl}:runQuery`,
{
headers,
method: "POST",
body: JSON.stringify({ structuredQuery }),
},
);
const data: any = await response.json();

if (data?.error || data?.[0]?.error) {
this.errorHandler(
data.error ?? data?.[0]?.error,
`${this.firestoreBaseUrl}:runQuery`,
);
console.log({
extendedDetails: data.error?.details ?? data?.[0]?.error?.details,
});
return [];
}

if (data.length == 1 && !data[0].document) {
return [];
}

return data.map((doc: any) => {
const docId = doc.document?.name.split("/").pop() ?? "unknown";
const documentFields = doc.document?.fields || {};
return {
...this.documentToJson(documentFields),
_id: docId,
_path: doc.document?.name?.split("documents/")[1],
};
});
}

/**
Expand Down

0 comments on commit 6511646

Please sign in to comment.