Skip to content

Commit 0edb5f7

Browse files
committed
CRUD operations are more stable and allow virtually any Collection or Document name.
Removed use of documents.createDocument API Endpoint, keys with # or ? characters cannot be created.
1 parent b35c224 commit 0edb5f7

File tree

6 files changed

+56
-21
lines changed

6 files changed

+56
-21
lines changed

Query.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22
/* eslint quote-props: ["error", "always"] */
33

44
/**
5-
* An object that acts as a Query to be a structured query.
5+
* An internal object that acts as a Structured Query to be prepared before execution.
66
* Chain methods to update query. Must call .execute to send request.
77
*
88
* @constructor
9-
* @private
109
* @see {@link https://firebase.google.com/docs/firestore/reference/rest/v1/StructuredQuery Firestore Structured Query}
1110
* @param {string} from the base collection to query
1211
* @param {queryCallback} callback the function that is executed with the internally compiled query

Read.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ function get_ (path, request) {
2020
*
2121
* @private
2222
* @param {string} path the path to the document or collection to get
23-
* @param {string} request the Firestore Request object to manipulate
2423
* @param {string} pageToken if defined, is utilized for retrieving subsequent pages
24+
* @param {string} request the Firestore Request object to manipulate
2525
* @return {object} the JSON response from the GET request
2626
*/
2727
function getPage_ (path, pageToken, request) {
@@ -115,9 +115,10 @@ function getDocuments_ (path, request, ids) {
115115
*/
116116
function query_ (path, request) {
117117
const grouped = getCollectionFromPath_(path)
118+
request.route('runQuery')
118119
const callback = function (query) {
119120
// Send request to innermost document with given query
120-
const responseObj = request.post(grouped[0] + ':runQuery', {
121+
const responseObj = request.post(grouped[0], {
121122
'structuredQuery': query
122123
})
123124

Request.js

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,15 @@ var FirestoreRequest_ = function (url, authToken, options) {
4343
*/
4444
const method = function (type, path) {
4545
options.method = type
46-
return fetchObject_(url + (path || '') + queryString, options)
46+
return fetchObject_(url + cleanPath_(path || '') + queryString, options)
4747
}
4848

4949
/**
5050
* Adds a parameter to the URL query string.
5151
* Can be repeated for additional key-value mappings
5252
*
53-
* @param key the key to add
54-
* @param value the value to set
53+
* @param {string} key the key to add
54+
* @param {string} value the value to set
5555
* @returns {FirestoreRequest_} this request to be chained
5656
*/
5757
this.addParam = function (key, value) {
@@ -60,10 +60,22 @@ var FirestoreRequest_ = function (url, authToken, options) {
6060
queryString += (queryString.indexOf('?') === -1 ? '?' : '&') + key + '=' + value
6161
return this_
6262
}
63+
64+
/**
65+
* Alters the route by prepending the query string.
66+
*
67+
* @param {string} route to set
68+
* @returns {FirestoreRequest_} this request to be chained
69+
*/
70+
this.route = function (route) {
71+
queryString = ':' + route + queryString
72+
return this_
73+
}
74+
6375
/**
6476
* Set request as a GET method
6577
*
66-
* @param path the path to send the request to
78+
* @param {string} path the path to send the request to
6779
* @returns {FirestoreRequest_} this request to be chained
6880
*/
6981
this.get = function (path) {

Util.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,41 @@ function getColDocFromPath_ (path, isDocument) {
122122
})
123123
const len = splitPath.length
124124

125+
cleanParts_(splitPath)
126+
125127
// Set item path to document if isDocument, otherwise set to collection if exists.
126128
// This works because path is always in the format of "collection/document/collection/document/etc.."
127129
const item = len && len & 1 ^ isDocument ? splitPath.splice(len - 1, 1)[0] : ''
128130

129131
// Remainder of path is in splitPath. Put back together and return.
130132
return [splitPath.join('/'), item]
131133
}
134+
135+
/**
136+
* Validates Collection and Document names
137+
*
138+
* @private
139+
* @see {@link https://firebase.google.com/docs/firestore/quotas#collections_documents_and_fields Firestore Limits}
140+
* @param {array} parts Array of strings representing document path
141+
* @returns {array} of URI Encoded path names
142+
* @throws {Error} Validation errors if it doesn't meet API guidelines
143+
*/
144+
function cleanParts_ (parts) {
145+
return parts.map(function (part, i) {
146+
var type = i & 1 ? 'Collection' : 'Document'
147+
if (part === '.' || part === '..') { throw new TypeError(type + ' name cannot solely consist of a single period (.) or double periods (..)') }
148+
if (part.indexOf('__') === 0 && part.endsWith('__')) { throw new TypeError(type + ' name cannot be a dunder name (begin and end with double underscores)') }
149+
return encodeURIComponent(part)
150+
})
151+
}
152+
153+
/**
154+
* Splits up path to be cleaned
155+
*
156+
* @private
157+
* @param {string} path to be cleaned
158+
* @returns {string} path that is URL-safe
159+
*/
160+
function cleanPath_ (path) {
161+
return cleanParts_(path.split('/')).join('/')
162+
}

Write.js

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,13 @@
55
*
66
* @private
77
* @param {string} path the path where the document will be written
8-
* @param {string} documentId the document's ID in Firestore
98
* @param {object} fields the document's fields
109
* @param {string} request the Firestore Request object to manipulate
1110
* @return {object} the Document object written to Firestore
1211
*/
1312
function createDocument_ (path, fields, request) {
14-
const pathDoc = getDocumentFromPath_(path)
15-
const firestoreObject = createFirestoreDocument_(fields)
16-
const documentId = pathDoc[1]
17-
18-
if (documentId) {
19-
request.addParam('documentId', documentId)
20-
}
21-
22-
const newDoc = request.post(pathDoc[0], firestoreObject)
23-
return unwrapDocumentFields_(newDoc)
13+
request.addParam('currentDocument.exists', false)
14+
return updateDocument_(path, fields, request)
2415
}
2516

2617
/**
@@ -30,7 +21,7 @@ function createDocument_ (path, fields, request) {
3021
* @param {string} path the path of the document to update
3122
* @param {object} fields the document's new fields
3223
* @param {string} request the Firestore Request object to manipulate
33-
* @param {boolean} if true, the update will use a mask. i.e. true: updates only specific fields, false: overwrites document with specified fields
24+
* @param {boolean} mask if true, the update will use a mask. i.e. true: updates only specific fields, false: overwrites document with specified fields
3425
* @return {object} the Document object written to Firestore
3526
*/
3627
function updateDocument_ (path, fields, request, mask) {

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "firestore_google-apps-script",
3-
"version": "24",
3+
"version": "25",
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",
@@ -12,6 +12,7 @@
1212
"UrlFetchApp",
1313
"Utilities",
1414
"base64EncodeSafe_",
15+
"cleanPath_",
1516
"createDocument_",
1617
"createFirestoreDocument_",
1718
"deleteDocument_",

0 commit comments

Comments
 (0)