Skip to content

Commit b35c224

Browse files
committed
Allow API version selection (defaults to "v1beta1").
Add "IN" and "Contains_Any" Query field operators (usable only with "v1" API). Expand Firestore.getDocuments to get specific named documents. Update README link and minor documentation.
1 parent 09738e4 commit b35c224

File tree

7 files changed

+94
-37
lines changed

7 files changed

+94
-37
lines changed

.github/README.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ To make a service account,
2727
5. When you press "Create," your browser will download a `.json` file with your private key (`private_key`), service account email (`client_email`), and project ID (`project_id`). Copy these values into your Google Apps Script — you'll need them to authenticate with Firestore.
2828

2929
#### Create a test document in Firestore from your script
30-
Now, with your service account client email address `email`, private key `key`, and project ID `projectId`, we will authenticate with Firestore to get our `Firestore` object. To do this, get the `Firestore` object from the library:
30+
Now, with your service account client email address `email`, private key `key`, project ID `projectId`, and Firestore API version, we will authenticate with Firestore to get our `Firestore` object. To do this, get the `Firestore` object from the library:
3131

3232
```javascript
33-
var firestore = FirestoreApp.getFirestore(email, key, projectId);
33+
var firestore = FirestoreApp.getFirestore(email, key, projectId, "v1");
3434
```
3535

3636
Using this Firestore instance, we will create a Firestore document with a field `name` with value `test!`. Let's encode this as a JSON object:
@@ -71,20 +71,28 @@ You can also retrieve all documents within a collection by using the `getDocumen
7171
const allDocuments = firestore.getDocuments("FirstCollection")
7272
```
7373

74-
If more specific queries need to be performed, you can use the `query` function followed by an `execute` invocation to get that data:
74+
You can also get specific documents by providing an array of document names
75+
76+
```javascript
77+
const someDocuments = firestore.getDocuments("FirstCollection", ["Doc1", "Doc2", "Doc3"])
78+
```
79+
7580

81+
If more specific queries need to be performed, you can use the `query` function followed by an `execute` invocation to get that data:
82+
7683
```javascript
7784
const allDocumentsWithTest = firestore.query("FirstCollection").where("name", "==", "Test!").execute()
7885
```
7986

80-
See other library methods and details [in the wiki](https://github.com/grahamearley/FirestoreGoogleAppsScript/wiki/Firestore-Method-Documentation).
87+
See other library methods and details [in the wiki](https://github.com/grahamearley/FirestoreGoogleAppsScript/wiki/).
8188

8289
### Breaking Changes
90+
* v23: When retrieving documents the createTime and updateTime document properties are JS Date objects and not Timestamp Strings.
8391
* v16: **Removed:** `createDocumentWithId(documentId, path, fields)`
8492
> Utilize `createDocument(path + '/' + documentId, fields)` instead to create a document with a specific ID.
8593
8694
## Contributions
87-
Contributions are welcome — send a pull request! This library is a work in progress. See [here](https://github.com/grahamearley/FirestoreGoogleAppsScript/blob/master/CONTRIBUTING.md) for more information on contributing.
95+
Contributions are welcome — send a pull request! This library is a work in progress. See [here](https://github.com/grahamearley/FirestoreGoogleAppsScript/blob/master/.github/CONTRIBUTING.md) for more information on contributing.
8896

8997
After cloning this repository, you can push it to your own private copy of this Google Apps Script project to test it yourself. See [here](https://github.com/google/clasp) for directions on using `clasp` to develop App Scripts locally.
9098

Firestore.js

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66
* @param {string} email the user email address (for authentication)
77
* @param {string} key the user private key (for authentication)
88
* @param {string} projectId the Firestore project ID
9+
* @param {string} apiVersion [Optional] The Firestore API Version ("v1beta1", "v1beta2", or "v1")
910
* @return {object} an authenticated interface with a Firestore project
1011
*/
11-
function getFirestore (email, key, projectId) {
12-
return new Firestore(email, key, projectId)
12+
function getFirestore (email, key, projectId, apiVersion) {
13+
return new Firestore(email, key, projectId, apiVersion)
1314
}
1415

1516
/**
@@ -19,14 +20,18 @@ function getFirestore (email, key, projectId) {
1920
* @param {string} email the user email address (for authentication)
2021
* @param {string} key the user private key (for authentication)
2122
* @param {string} projectId the Firestore project ID
23+
* @param {string} apiVersion [Optional] The Firestore API Version ("v1beta1", "v1beta2", or "v1"). Defaults to "v1beta1"
2224
* @return {object} an authenticated interface with a Firestore project
2325
*/
24-
var Firestore = function (email, key, projectId) {
26+
var Firestore = function (email, key, projectId, apiVersion) {
27+
if (!apiVersion) { apiVersion = 'v1beta1' }
28+
2529
/**
26-
* The authentication token used for accessing Firestore.
27-
*/
30+
* The authentication token used for accessing Firestore.
31+
*/
2832
const authToken = getAuthToken_(email, key, 'https://oauth2.googleapis.com/token')
29-
const baseUrl = 'https://firestore.googleapis.com/v1beta1/projects/' + projectId + '/databases/(default)/documents/'
33+
const basePath = 'projects/' + projectId + '/databases/(default)/documents/'
34+
const baseUrl = 'https://firestore.googleapis.com/' + apiVersion + '/' + basePath
3035

3136
/**
3237
* Get a document.
@@ -43,10 +48,18 @@ var Firestore = function (email, key, projectId) {
4348
* Get a list of all documents in a collection.
4449
*
4550
* @param {string} path the path to the collection
51+
* @param {array} ids [Optional] String array of document names to filter. Missing documents will not be included.
4652
* @return {object} an array of the documents in the collection
4753
*/
48-
this.getDocuments = function (path) {
49-
return this.query(path).execute()
54+
this.getDocuments = function (path, ids) {
55+
var docs
56+
if (!ids) {
57+
docs = this.query(path).execute()
58+
} else {
59+
const request = new FirestoreRequest_(baseUrl.replace('/documents/', '/documents:batchGet/'), authToken)
60+
docs = getDocuments_(basePath + path, request, ids)
61+
}
62+
return docs
5063
}
5164

5265
/**
@@ -75,8 +88,7 @@ var Firestore = function (email, key, projectId) {
7588
/**
7689
* Update/patch a document at the given path with new fields.
7790
*
78-
* @param {string} path the path of the document to update.
79-
* If document name not provided, a random ID will be generated.
91+
* @param {string} path the path of the document to update. If document name not provided, a random ID will be generated.
8092
* @param {object} fields the document's new fields
8193
* @param {boolean} mask if true, the update will use a mask
8294
* @return {object} the Document object written to Firestore
@@ -87,8 +99,7 @@ var Firestore = function (email, key, projectId) {
8799
}
88100

89101
/**
90-
* Run a query against the Firestore Database and
91-
* return an all the documents that match the query.
102+
* Run a query against the Firestore Database and return an all the documents that match the query.
92103
* Must call .execute() to send the request.
93104
*
94105
* @param {string} path to query

FirestoreDocument.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,22 @@ function unwrapDocumentFields_ (docResponse) {
6060
return docResponse
6161
}
6262

63+
/**
64+
* Unwrap the given array of batch documents.
65+
*
66+
* @private
67+
* @param docsResponse the document response
68+
* @return the array of documents, with unwrapped fields
69+
*/
70+
function unwrapBatchDocuments_ (docsResponse) {
71+
docsResponse = docsResponse.filter(function (docItem) { return docItem.found }) // Remove missing entries
72+
return docsResponse.map(function (docItem) {
73+
const doc = unwrapDocumentFields_(docItem.found)
74+
doc.readTime = unwrapDate_(docItem.readTime)
75+
return doc
76+
})
77+
}
78+
6379
function wrapValue_ (value) {
6480
const type = typeof (value)
6581
switch (type) {

Query.js

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,36 +7,40 @@
77
*
88
* @constructor
99
* @private
10-
* @see {@link https://firebase.google.com/docs/firestore/reference/rest/v1beta1/StructuredQuery Firestore Structured Query}
10+
* @see {@link https://firebase.google.com/docs/firestore/reference/rest/v1/StructuredQuery Firestore Structured Query}
1111
* @param {string} from the base collection to query
1212
* @param {queryCallback} callback the function that is executed with the internally compiled query
1313
*/
1414
var FirestoreQuery_ = function (from, callback) {
1515
const this_ = this
1616

17-
// @see {@link https://firebase.google.com/docs/firestore/reference/rest/v1beta1/StructuredQuery#Operator_1 FieldFilter Operator}
17+
// @see {@link https://firebase.google.com/docs/firestore/reference/rest/v1/StructuredQuery#Operator_1 FieldFilter Operator}
1818
const fieldOps = {
1919
'==': 'EQUAL',
2020
'===': 'EQUAL',
2121
'<': 'LESS_THAN',
2222
'<=': 'LESS_THAN_OR_EQUAL',
2323
'>': 'GREATER_THAN',
2424
'>=': 'GREATER_THAN_OR_EQUAL',
25-
'contains': 'ARRAY_CONTAINS'
25+
'contains': 'ARRAY_CONTAINS',
26+
'containsany': 'ARRAY_CONTAINS_ANY',
27+
'in': 'IN'
2628
}
2729

28-
// @see {@link https://firebase.google.com/docs/firestore/reference/rest/v1beta1/StructuredQuery#Operator_2 FieldFilter Operator}
30+
// @see {@link https://firebase.google.com/docs/firestore/reference/rest/v1/StructuredQuery#Operator_2 FieldFilter Operator}
2931
const unaryOps = {
3032
'nan': 'IS_NAN',
3133
'null': 'IS_NULL'
3234
}
3335

34-
// @see {@link https://firebase.google.com/docs/firestore/reference/rest/v1beta1/StructuredQuery#FieldReference Field Reference}
36+
// @see {@link https://firebase.google.com/docs/firestore/reference/rest/v1/StructuredQuery#FieldReference Field Reference}
3537
const fieldRef = function (field) {
3638
return { 'fieldPath': field }
3739
}
3840
const filter = function (field, operator, value) {
39-
// @see {@link https://firebase.google.com/docs/firestore/reference/rest/v1beta1/StructuredQuery#FieldFilter Field Filter}
41+
operator = operator.toLowerCase().replace('_', '')
42+
43+
// @see {@link https://firebase.google.com/docs/firestore/reference/rest/v1/StructuredQuery#FieldFilter Field Filter}
4044
if (operator in fieldOps) {
4145
if (value == null) { // Covers null and undefined values
4246
operator = 'null'
@@ -53,7 +57,7 @@ var FirestoreQuery_ = function (from, callback) {
5357
}
5458
}
5559

56-
// @see {@link https://firebase.google.com/docs/firestore/reference/rest/v1beta1/StructuredQuery#UnaryFilter Unary Filter}
60+
// @see {@link https://firebase.google.com/docs/firestore/reference/rest/v1/StructuredQuery#UnaryFilter Unary Filter}
5761
if (operator.toLowerCase() in unaryOps) {
5862
return {
5963
'unaryFilter': {
@@ -72,9 +76,9 @@ var FirestoreQuery_ = function (from, callback) {
7276

7377
/**
7478
* Select Query which can narrow which fields to return.
75-
* Can be repeated if multiple fields are needed in the response.
79+
* Can be repeated if multiple fields are needed in the response.
7680
*
77-
* @see {@link https://firebase.google.com/docs/firestore/reference/rest/v1beta1/StructuredQuery#Projection Select}
81+
* @see {@link https://firebase.google.com/docs/firestore/reference/rest/v1/StructuredQuery#Projection Select}
7882
* @param {string} field The field to narrow down (if empty, returns name of document)
7983
* @returns {object} this query object for chaining
8084
*/
@@ -123,7 +127,7 @@ var FirestoreQuery_ = function (from, callback) {
123127
* Orders the Query results based on a field and specific direction.
124128
* Can be repeated if additional ordering is needed.
125129
*
126-
* @see {@link https://firebase.google.com/docs/firestore/reference/rest/v1beta1/StructuredQuery#Projection Select}
130+
* @see {@link https://firebase.google.com/docs/firestore/reference/rest/v1/StructuredQuery#Projection Select}
127131
* @param {string} field The field to order by.
128132
* @param {string} dir The direction to order the field by. Should be one of "asc" or "desc". Defaults to Ascending.
129133
* @returns {object} this query object for chaining

Read.js

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "_" }] */
2+
/* eslint quote-props: ["error", "always"] */
23

34
/**
45
* Get the Firestore document or collection at a given path.
5-
* If the collection contains enough IDs to return a paginated result,
6-
* this method only returns the first page.
6+
* If the collection contains enough IDs to return a paginated result, this method only returns the first page.
77
*
88
* @private
99
* @param {string} path the path to the document or collection to get
@@ -16,7 +16,7 @@ function get_ (path, request) {
1616

1717
/**
1818
* Get a page of results from the given path.
19-
* If null pageToken is supplied, returns first page.
19+
* If null pageToken is supplied, returns first page.
2020
*
2121
* @private
2222
* @param {string} path the path to the document or collection to get
@@ -33,7 +33,7 @@ function getPage_ (path, pageToken, request) {
3333

3434
/**
3535
* Get a list of the JSON responses received for getting documents from a collection.
36-
* The items returned by this function are formatted as Firestore documents (with types).
36+
* The items returned by this function are formatted as Firestore documents (with types).
3737
*
3838
* @private
3939
* @param {string} path the path to the collection
@@ -57,7 +57,7 @@ function getDocumentResponsesFromCollection_ (path, request) {
5757

5858
/**
5959
* Get a list of all IDs of the documents in a collection.
60-
* Works with nested collections.
60+
* Works with nested collections.
6161
*
6262
* @private
6363
* @param {string} path the path to the collection
@@ -88,6 +88,23 @@ function getDocument_ (path, request) {
8888
}
8989
return unwrapDocumentFields_(doc)
9090
}
91+
92+
/**
93+
* Get documents with given IDs.
94+
*
95+
* @private
96+
* @see {@link https://firebase.google.com/docs/firestore/reference/rest/v1beta1/projects.databases.documents/batchGet Firestore Documents BatchGet}
97+
* @param {string} path the path to the document
98+
* @param {string} request the Firestore Request object to manipulate
99+
* @param {array} ids String array of document names
100+
* @return {object} an object mapping the document's fields to their values
101+
*/
102+
function getDocuments_ (path, request, ids) {
103+
const idPaths = ids.map(function (doc) { return path + '/' + doc }) // Format to absolute paths (relative to API endpoint)
104+
const documents = request.post(null, { 'documents': idPaths })
105+
return unwrapBatchDocuments_(documents)
106+
}
107+
91108
/**
92109
* Set up a Query to receive data from a collection
93110
*
@@ -101,7 +118,7 @@ function query_ (path, request) {
101118
const callback = function (query) {
102119
// Send request to innermost document with given query
103120
const responseObj = request.post(grouped[0] + ':runQuery', {
104-
structuredQuery: query
121+
'structuredQuery': query
105122
})
106123

107124
// Filter out results without documents and unwrap document fields

Util.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,14 @@ var regexDatePrecision_ = /(\.\d{3})\d+/
1212
/**
1313
* Checks if a number is an integer.
1414
*
15-
* @private
15+
* @private
1616
* @param {value} n value to check
1717
* @returns {boolean} true if value can be coerced into an integer, false otherwise
1818
*/
1919
function isInt_ (n) {
2020
return n % 1 === 0
2121
}
2222

23-
2423
/**
2524
* Check if a value is a valid number.
2625
*
@@ -103,7 +102,7 @@ function getCollectionFromPath_ (path) {
103102
*
104103
* @private
105104
* @param {string} path Document path
106-
* @returns {object} Document object
105+
* @returns {object} Document object
107106
*/
108107
function getDocumentFromPath_ (path) {
109108
return getColDocFromPath_(path, true)

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "firestore_google-apps-script",
3-
"version": "23",
3+
"version": "24",
44
"description": "A Google Apps Script library for accessing Google Cloud Firestore",
55
"homepage": "https://github.com/grahamearley/FirestoreGoogleAppsScript",
66
"bugs": "https://github.com/grahamearley/FirestoreGoogleAppsScript/issues",
@@ -21,13 +21,15 @@
2121
"getDocumentFromPath_",
2222
"getDocumentIds_",
2323
"getDocument_",
24+
"getDocuments_",
2425
"isInt_",
2526
"isNumberNaN_",
2627
"isNumeric_",
2728
"query_",
2829
"regexBinary_",
2930
"regexDatePrecision_",
3031
"regexPath_",
32+
"unwrapBatchDocuments_",
3133
"unwrapDocumentFields_",
3234
"updateDocument_",
3335
"wrapValue_"

0 commit comments

Comments
 (0)