Skip to content

Commit f4b4baa

Browse files
committed
Create/Update Document returns create/update timestamp as Date.
Fix JS date creation to omit microseconds since Apps Script JavaScript Engine doesn't support it. Added lots of missing documentation.
1 parent 581a818 commit f4b4baa

File tree

6 files changed

+93
-22
lines changed

6 files changed

+93
-22
lines changed

Delete.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* Delete the Firestore document at the given path.
55
* Note: this deletes ONLY this document, and not any subcollections.
66
*
7+
* @private
78
* @param {string} path the path to the document to delete
89
* @param {string} request the Firestore Request object to manipulate
910
* @return {object} the JSON response from the DELETE request

FirestoreDocument.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
/**
55
* Create a Firestore documents with the corresponding fields.
66
*
7+
* @private
78
* @param {object} fields the document's fields
89
* @return {object} a Firestore document with the given fields
910
*/
@@ -20,6 +21,7 @@ function createFirestoreDocument_ (fields) {
2021
/**
2122
* Extract fields from a Firestore document.
2223
*
24+
* @private
2325
* @param {object} firestoreDoc the Firestore document whose fields will be extracted
2426
* @return {object} an object with the given document's fields and values
2527
*/
@@ -49,6 +51,12 @@ function unwrapDocumentFields_ (docResponse) {
4951
if (docResponse.fields) {
5052
docResponse.fields = getFieldsFromFirestoreDocument_(docResponse)
5153
}
54+
if (docResponse.createTime) {
55+
docResponse.createTime = unwrapDate_(docResponse.createTime)
56+
}
57+
if (docResponse.updateTime) {
58+
docResponse.updateTime = unwrapDate_(docResponse.updateTime)
59+
}
5260
return docResponse
5361
}
5462

@@ -89,7 +97,7 @@ function unwrapValue_ (value) {
8997
case 'arrayValue':
9098
return unwrapArray_(value.values)
9199
case 'timestampValue':
92-
return new Date(value)
100+
return unwrapDate_(value)
93101
case 'nullValue':
94102
default: // error
95103
return null
@@ -179,3 +187,8 @@ function unwrapArray_ (wrappedArray) {
179187
const array = (wrappedArray || []).map(unwrapValue_)
180188
return array
181189
}
190+
191+
function unwrapDate_ (wrappedDate) {
192+
// Trim out extra microsecond precision
193+
return new Date(wrappedDate.replace(regexDatePrecision_, '$1'))
194+
}

Query.js

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

4-
/**
5-
* This callback type is called `queryCallback`.
6-
* Callback should utilize the Query parameter to send a request and return the response.
7-
*
8-
* @callback queryCallback
9-
* @param {object} query the Structured Query to utilize in the query request {@link FirestoreQuery_}
10-
* @returns [object] response of the sent query
11-
*/
12-
134
/**
145
* An object that acts as a Query to be a structured query.
156
* Chain methods to update query. Must call .execute to send request.
@@ -101,8 +92,8 @@ var FirestoreQuery_ = function (from, callback) {
10192

10293
/**
10394
* Filter Query by a given field and operator (or additionally a value).
104-
* Can be repeated if multiple filters required.
105-
* Results must satisfy all filters.
95+
* Can be repeated if multiple filters required.
96+
* Results must satisfy all filters.
10697
*
10798
* @param {string} field The field to reference for filtering
10899
* @param {string} operator The operator to filter by. {@link fieldOps} {@link unaryOps}
@@ -130,7 +121,7 @@ var FirestoreQuery_ = function (from, callback) {
130121

131122
/**
132123
* Orders the Query results based on a field and specific direction.
133-
* Can be repeated if additional ordering is needed.
124+
* Can be repeated if additional ordering is needed.
134125
*
135126
* @see {@link https://firebase.google.com/docs/firestore/reference/rest/v1beta1/StructuredQuery#Projection Select}
136127
* @param {string} field The field to order by.
@@ -209,7 +200,7 @@ var FirestoreQuery_ = function (from, callback) {
209200

210201
/**
211202
* Executes the query with the given callback method and the generated query.
212-
* Must be used at the end of any query for execution.
203+
* Must be used at the end of any query for execution.
213204
*
214205
* @returns {object} The query results from the execution
215206
*/

Util.js

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,39 +5,79 @@ var regexPath_ = /^projects\/.+?\/databases\/\(default\)\/documents\/(.+\/.+)$/
55
// RegEx test for testing for binary data by checking for non-printable characters.
66
// Parsing strings for binary data is completely dependent on the data being sent over.
77
var regexBinary_ = /[\x00-\x08\x0E-\x1F]/ // eslint-disable-line no-control-regex
8+
// RegEx test for finding and capturing milliseconds.
9+
// Apps Scripts doesn't support RFC3339 Date Formats, nanosecond precision must be trimmed.
10+
var regexDatePrecision_ = /(\.\d{3})\d+/
811

9-
// Assumes n is a Number.
12+
/**
13+
* Checks if a number is an integer.
14+
*
15+
* @private
16+
* @param {value} n value to check
17+
* @returns {boolean} true if value can be coerced into an integer, false otherwise
18+
*/
1019
function isInt_ (n) {
1120
return n % 1 === 0
1221
}
1322

23+
24+
/**
25+
* Check if a value is a valid number.
26+
*
27+
* @private
28+
* @param {value} val value to check
29+
* @returns {boolean} true if a valid number, false otherwise
30+
*/
1431
function isNumeric_ (val) {
1532
return Number(parseFloat(val)) === val
1633
}
1734

1835
/**
1936
* Check if a value is of type Number but is NaN.
20-
* This check prevents seeing non-numeric values as NaN.
37+
* This check prevents seeing non-numeric values as NaN.
2138
*
22-
* @param {value} the value to check
23-
* @returns {boolean} whether the given value is of type number and equal to NaN
39+
* @private
40+
* @param {value} value value to check
41+
* @returns {boolean} true if NaN, false otherwise
2442
*/
2543
function isNumberNaN_ (value) {
2644
return typeof (value) === 'number' && isNaN(value)
2745
}
2846

47+
/**
48+
* Base64 Encodes a string without equals (=) symbol
49+
*
50+
* @private
51+
* @param {string} string string to encode
52+
* @returns {string} base64 encoded string (without =)
53+
*/
2954
function base64EncodeSafe_ (string) {
3055
const encoded = Utilities.base64EncodeWebSafe(string)
3156
return encoded.replace(/=/g, '')
3257
}
3358

59+
/**
60+
* Send HTTP request with provided options
61+
*
62+
* @private
63+
* @param {string} url Location path to send request to
64+
* @param {object} options HTTP related options to send
65+
* @returns {object} Response object from HTTP request
66+
*/
3467
function fetchObject_ (url, options) {
3568
const response = UrlFetchApp.fetch(url, options)
3669
const responseObj = JSON.parse(response.getContentText())
3770
checkForError_(responseObj)
3871
return responseObj
3972
}
4073

74+
/**
75+
* Validate response object for errors
76+
*
77+
* @private
78+
* @param {object} responseObj HTTP response object to validate
79+
* @throws Error if HTTP requests errors found
80+
*/
4181
function checkForError_ (responseObj) {
4282
if (responseObj.error) {
4383
throw new Error(responseObj.error.message)
@@ -46,13 +86,36 @@ function checkForError_ (responseObj) {
4686
throw new Error(responseObj[0].error.message)
4787
}
4888
}
89+
90+
/**
91+
* Gets collection of documents with the given path
92+
*
93+
* @private
94+
* @param {string} path Collection path
95+
* @returns {array} Collection of documents
96+
*/
4997
function getCollectionFromPath_ (path) {
5098
return getColDocFromPath_(path, false)
5199
}
100+
101+
/**
102+
* Gets document with the given path
103+
*
104+
* @private
105+
* @param {string} path Document path
106+
* @returns {object} Document object
107+
*/
52108
function getDocumentFromPath_ (path) {
53109
return getColDocFromPath_(path, true)
54110
}
55111

112+
/**
113+
* Gets collection or document with the given path
114+
*
115+
* @private
116+
* @param {string} path Document/Collection path
117+
* @returns {array|object} Collection of documents or a single document
118+
*/
56119
function getColDocFromPath_ (path, isDocument) {
57120
// Path defaults to empty string if it doesn't exist. Remove insignificant slashes.
58121
const splitPath = (path || '').split('/').filter(function (p) {

Write.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ function createDocument_ (path, fields, request) {
1818
if (documentId) {
1919
request.addParam('documentId', documentId)
2020
}
21-
return request.post(pathDoc[0], firestoreObject)
21+
22+
const newDoc = request.post(pathDoc[0], firestoreObject)
23+
return unwrapDocumentFields_(newDoc)
2224
}
2325

2426
/**
@@ -28,7 +30,7 @@ function createDocument_ (path, fields, request) {
2830
* @param {string} path the path of the document to update
2931
* @param {object} fields the document's new fields
3032
* @param {string} request the Firestore Request object to manipulate
31-
* @param {boolean} if true, the update will use a mask
33+
* @param {boolean} if true, the update will use a mask. i.e. true: updates only specific fields, false: overwrites document with specified fields
3234
* @return {object} the Document object written to Firestore
3335
*/
3436
function updateDocument_ (path, fields, request, mask) {
@@ -43,6 +45,6 @@ function updateDocument_ (path, fields, request, mask) {
4345
}
4446

4547
const firestoreObject = createFirestoreDocument_(fields)
46-
47-
return request.patch(path, firestoreObject)
48+
const updatedDoc = request.patch(path, firestoreObject)
49+
return unwrapDocumentFields_(updatedDoc)
4850
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"isNumeric_",
2727
"query_",
2828
"regexBinary_",
29+
"regexDatePrecision_",
2930
"regexPath_",
3031
"unwrapDocumentFields_",
3132
"updateDocument_",

0 commit comments

Comments
 (0)