Skip to content

Commit ee794ac

Browse files
committed
Export core API typings in a namespace & use ES modules
Using ES modules (import/export everywhere) prevents from polluting the global namespace with values and types, which is a best practice for a GAS library. Using a namespace for the core API typings allows for a better DX when using the library in other GAS projects. Use it like this: 1. Install the library just for grabbing its types locally: npm -i -D firestore_google-apps-script # once/if published to npmjs.com # or npm -i -D grahamearley/FirestoreGoogleAppsScript # in the meantime 2. In a .d.ts file of your making that’s included in tsconfig.json, add: import { FirestoreGoogleAppsScript } from "firestore_google-apps-script/typings"; declare const FirestoreDataSource: FirestoreGoogleAppsScript.FirestoreApp; // globally available // or declare global { const FirestoreDataSource: FirestoreGoogleAppsScript.FirestoreApp; } // globally available // or export const FirestoreDataSource: FirestoreGoogleAppsScript.FirestoreApp; // you’ll have to import it in files Note: although you don’t need to, it may clarify your intent adding: /// <reference types="../node_modules/firestore_google-apps-script/typings/index.d.ts" /> Note: you may rename `FirestoreDataSource`, it’s just a _Clean Architecture_ naming convention I’m following, which maps the `userSymbol` I dediced to use in appsscript.json. If you are manually enabling the lib through the Google Apps Script UI, it will actually be be called `FirestoreApp` by default, which does not conflict with the one that’s namespaced.
1 parent ad24ec3 commit ee794ac

15 files changed

+930
-405
lines changed

Auth.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import Request from './Request';
2+
import Util_ from './Util';
3+
import type * as AuthTypes from './typings/Auth';
4+
15
/**
26
* Auth token is formatted to {@link https://developers.google.com/identity/protocols/oauth2/service-account#authorizingrequests}
37
*
@@ -7,7 +11,7 @@
711
* @param authUrl the authorization url
812
* @returns {string} the access token needed for making future requests
913
*/
10-
class Auth {
14+
export default class Auth {
1115
email: string;
1216
key: string;
1317
authUrl: string;
@@ -31,7 +35,7 @@ class Auth {
3135
* @returns {string} The generated access token string
3236
*/
3337
get accessToken(): string {
34-
const request = new Request(this.authUrl, '', this.options_).post<TokenResponse>();
38+
const request = new Request(this.authUrl, '', this.options_).post<AuthTypes.TokenResponse>();
3539
return request.access_token;
3640
}
3741

@@ -47,7 +51,7 @@ class Auth {
4751
return `${signatureInput}.${Utilities.base64EncodeWebSafe(signature)}`;
4852
}
4953

50-
get jwtPayload_(): JwtClaim {
54+
get jwtPayload_(): AuthTypes.JwtClaim {
5155
const seconds = ~~(new Date().getTime() / 1000);
5256
return {
5357
iss: this.email,
@@ -58,7 +62,7 @@ class Auth {
5862
};
5963
}
6064

61-
get jwtHeader_(): JwtHeader {
65+
get jwtHeader_(): AuthTypes.JwtHeader {
6266
return {
6367
alg: 'RS256',
6468
typ: 'JWT',

Document.ts

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
import FirestoreAPI = gapi.client.firestore;
2+
import { FirestoreGoogleAppsScript as T } from './typings';
3+
import Util_ from './Util';
4+
15
/**
26
* Firestore Document
37
*/
4-
class Document implements FirestoreAPI.Document, FirestoreAPI.MapValue {
8+
export default class Document implements FirestoreAPI.Document, FirestoreAPI.MapValue {
59
fields?: Record<string, FirestoreAPI.Value>;
610
createTime?: string;
711
updateTime?: string;
@@ -14,13 +18,13 @@ class Document implements FirestoreAPI.Document, FirestoreAPI.MapValue {
1418
* @param obj
1519
* @param name
1620
*/
17-
constructor(obj: Value | FirestoreAPI.Document, name?: string | Document | FirestoreAPI.ReadOnly) {
21+
constructor(obj: T.Value | FirestoreAPI.Document, name?: string | Document | FirestoreAPI.ReadOnly) {
1822
//Treat parameters as existing Document with extra parameters to merge in
1923
if (typeof name === 'object') {
2024
Object.assign(this, obj);
2125
Object.assign(this, name);
2226
} else {
23-
this.fields = Document.wrapMap(obj as ValueObject).fields;
27+
this.fields = Document.wrapMap(obj as T.ValueObject).fields;
2428
if (name) {
2529
this.name = name;
2630
}
@@ -49,15 +53,15 @@ class Document implements FirestoreAPI.Document, FirestoreAPI.MapValue {
4953
* @param {object} firestoreDoc the Firestore document whose fields will be extracted
5054
* @return {object} an object with the given document's fields and values
5155
*/
52-
get obj(): Record<string, Value> {
56+
get obj(): Record<string, T.Value> {
5357
return Document.unwrapObject(this);
5458
}
5559

5660
toString(): string {
5761
return `Document (${Util_.getDocumentFromPath(this.name as string)[1]})`;
5862
}
5963

60-
static unwrapValue(obj: FirestoreAPI.Value): Value {
64+
static unwrapValue(obj: FirestoreAPI.Value): T.Value {
6165
// eslint-disable-next-line prefer-const
6266
let [type, val]: [string, any] = Object.entries(obj)[0];
6367
switch (type) {
@@ -83,17 +87,17 @@ class Document implements FirestoreAPI.Document, FirestoreAPI.MapValue {
8387
}
8488
}
8589

86-
static unwrapObject(obj: FirestoreAPI.MapValue): ValueObject {
90+
static unwrapObject(obj: FirestoreAPI.MapValue): T.ValueObject {
8791
return Object.entries(obj.fields || {}).reduce(
88-
(o: Record<string, Value>, [key, val]: [string, FirestoreAPI.Value]) => {
92+
(o: Record<string, T.Value>, [key, val]: [string, FirestoreAPI.Value]) => {
8993
o[key] = Document.unwrapValue(val);
9094
return o;
9195
},
9296
{}
9397
);
9498
}
9599

96-
static unwrapArray(wrappedArray: FirestoreAPI.Value[] = []): Value[] {
100+
static unwrapArray(wrappedArray: FirestoreAPI.Value[] = []): T.Value[] {
97101
return wrappedArray.map(this.unwrapValue, this);
98102
}
99103

@@ -102,13 +106,13 @@ class Document implements FirestoreAPI.Document, FirestoreAPI.MapValue {
102106
return new Date(wrappedDate.replace(Util_.regexDatePrecision, '$1'));
103107
}
104108

105-
static wrapValue(val: Value): FirestoreAPI.Value {
109+
static wrapValue(val: T.Value): FirestoreAPI.Value {
106110
const type = typeof val;
107111
switch (type) {
108112
case 'string':
109113
return this.wrapString(val as string);
110114
case 'object':
111-
return this.wrapObject(val as ValueObject);
115+
return this.wrapObject(val as T.ValueObject);
112116
case 'number':
113117
return this.wrapNumber(val as number);
114118
case 'boolean':
@@ -132,7 +136,7 @@ class Document implements FirestoreAPI.Document, FirestoreAPI.MapValue {
132136
return { stringValue: string };
133137
}
134138

135-
static wrapObject(obj: ValueObject): FirestoreAPI.Value {
139+
static wrapObject(obj: T.ValueObject): FirestoreAPI.Value {
136140
if (!obj) {
137141
return this.wrapNull();
138142
}
@@ -154,9 +158,9 @@ class Document implements FirestoreAPI.Document, FirestoreAPI.MapValue {
154158
return { mapValue: this.wrapMap(obj) };
155159
}
156160

157-
static wrapMap(obj: ValueObject): FirestoreAPI.MapValue {
161+
static wrapMap(obj: T.ValueObject): FirestoreAPI.MapValue {
158162
return {
159-
fields: Object.entries(obj).reduce((o: Record<string, FirestoreAPI.Value>, [key, val]: [string, Value]) => {
163+
fields: Object.entries(obj).reduce((o: Record<string, FirestoreAPI.Value>, [key, val]: [string, T.Value]) => {
160164
o[key] = Document.wrapValue(val);
161165
return o;
162166
}, {}),

Firestore.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,20 @@
1+
import FirestoreAPI = gapi.client.firestore;
2+
import FirestoreRead from './FirestoreRead';
3+
import FirestoreWrite from './FirestoreWrite';
4+
import FirestoreDelete from './FirestoreDelete';
5+
import Document from './Document';
6+
import Query from './Query';
7+
import Auth from './Auth';
8+
import Request from './Request';
9+
10+
export { Auth, Document, Firestore, FirestoreRead, FirestoreWrite, FirestoreDelete, Query, Request };
11+
112
/* eslint @typescript-eslint/no-unused-vars: ["error", { "varsIgnorePattern": "^getFirestore$" }] */
213

314
/**
415
* An authenticated interface to a Firestore project.
516
*/
6-
class Firestore implements FirestoreRead, FirestoreWrite, FirestoreDelete {
17+
export default class Firestore implements FirestoreRead, FirestoreWrite, FirestoreDelete {
718
auth: Auth;
819
basePath: string;
920
baseUrl: string;
@@ -133,7 +144,7 @@ class Firestore implements FirestoreRead, FirestoreWrite, FirestoreDelete {
133144
query_ = FirestoreRead.prototype.query_;
134145
}
135146

136-
type Version = 'v1' | 'v1beta1' | 'v1beta2';
147+
export type Version = 'v1' | 'v1beta1' | 'v1beta2';
137148

138149
/**
139150
* Get an object that acts as an authenticated interface with a Firestore project.
@@ -144,6 +155,6 @@ type Version = 'v1' | 'v1beta1' | 'v1beta2';
144155
* @param {string} apiVersion [Optional] The Firestore API Version ("v1beta1", "v1beta2", or "v1")
145156
* @return {Firestore} an authenticated interface with a Firestore project (function)
146157
*/
147-
function getFirestore(email: string, key: string, projectId: string, apiVersion: Version = 'v1'): Firestore {
158+
export function getFirestore(email: string, key: string, projectId: string, apiVersion: Version = 'v1'): Firestore {
148159
return new Firestore(email, key, projectId, apiVersion);
149160
}

FirestoreDelete.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import FirestoreAPI = gapi.client.firestore;
2+
import Request from './Request';
3+
14
/**
25
* Extends Firestore class with private method
36
*/
4-
class FirestoreDelete {
7+
export default class FirestoreDelete {
58
/**
69
* Delete the Firestore document at the given path.
710
* Note: this deletes ONLY this document, and not any subcollections.

FirestoreRead.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1+
import FirestoreAPI = gapi.client.firestore;
2+
import Document from './Document';
3+
import Request from './Request';
4+
import Query from './Query';
5+
import Util_ from './Util';
6+
17
/**
28
* Extends Firestore class with private method
39
*/
4-
class FirestoreRead {
10+
export default class FirestoreRead {
511
/**
612
* Get the Firestore document or collection at a given path.
713
* If the collection contains enough IDs to return a paginated result, this method only returns the first page.

FirestoreWrite.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
import FirestoreAPI = gapi.client.firestore;
2+
import Document from './Document';
3+
import Request from './Request';
4+
import Util_ from './Util';
5+
16
/**
27
* Extends Firestore class with private method
38
*/
4-
class FirestoreWrite {
9+
export default class FirestoreWrite {
510
/**
611
* Create a document with the given ID and fields.
712
*

Query.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1-
interface QueryCallback {
1+
import FirestoreAPI = gapi.client.firestore;
2+
import Document from './Document';
3+
import Util_ from './Util';
4+
5+
import type { FilterOp } from './typings/Query';
6+
7+
export interface QueryCallback {
28
(query: Query): Document[];
39
}
410
/**
511
* @see {@link https://firebase.google.com/docs/firestore/reference/rest/v1/StructuredQuery#Operator_1 FieldFilter Operator}
612
*/
7-
enum FieldFilterOps_ {
13+
export enum FieldFilterOps_ {
814
'==' = 'EQUAL',
915
'===' = 'EQUAL',
1016
'<' = 'LESS_THAN',
@@ -18,7 +24,7 @@ enum FieldFilterOps_ {
1824
/**
1925
* @see {@link https://firebase.google.com/docs/firestore/reference/rest/v1/StructuredQuery#Operator_2 UnaryFilter Operator}
2026
*/
21-
enum UnaryFilterOps_ {
27+
export enum UnaryFilterOps_ {
2228
'nan' = 'IS_NAN',
2329
'null' = 'IS_NULL',
2430
}
@@ -29,7 +35,7 @@ enum UnaryFilterOps_ {
2935
*
3036
* @see {@link https://firebase.google.com/docs/firestore/reference/rest/v1/StructuredQuery Firestore Structured Query}
3137
*/
32-
class Query implements FirestoreAPI.StructuredQuery {
38+
export default class Query implements FirestoreAPI.StructuredQuery {
3339
select?: FirestoreAPI.Projection;
3440
from?: FirestoreAPI.CollectionSelector[];
3541
where?: FirestoreAPI.Filter;
@@ -104,7 +110,7 @@ class Query implements FirestoreAPI.StructuredQuery {
104110

105111
filter_(field: string, operator: string | number | null, value: any): FirestoreAPI.Filter {
106112
if (typeof operator === 'string') {
107-
operator = operator.toLowerCase().replace('_', '') as FilterOp;
113+
operator = (operator.toLowerCase().replace('_', '') as FilterOp) as string;
108114
} else if (value == null) {
109115
// Covers null and undefined values
110116
operator = 'null';

Request.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
import Util_ from './Util';
2+
13
/**
24
* Manages the requests to send. Chain methods to update options.
35
* Must call .get/.post/.patch/.remove to send the request with given options.
46
*/
5-
class Request {
7+
export default class Request {
68
url: string;
79
authToken: string;
810
queryString: string;

Tests.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
import { getFirestore } from './Firestore';
2+
import { FirestoreGoogleAppsScript } from './typings/index';
3+
import Util_ from './Util';
4+
5+
import type { Version } from './Firestore';
6+
type Firestore = FirestoreGoogleAppsScript.Firestore;
7+
type Value = FirestoreGoogleAppsScript.Value;
8+
19
function StoreCredentials_(): void {
210
/** DO NOT SAVE CREDENTIALS HERE */
311
const email = '[email protected]';
@@ -30,7 +38,7 @@ class Tests implements TestManager {
3038
this.pass.push('Test_Get_Firestore');
3139
} catch (e) {
3240
// On failure, fail the remaining tests without execution
33-
this.fail.set('Test_Get_Firestore', e);
41+
this.fail.set('Test_Get_Firestore', e as Error);
3442
const err = new Error('Test Initialization Error');
3543
err.stack = 'See Test_Get_Firestore Error';
3644
for (const func of funcs) {
@@ -73,7 +81,7 @@ class Tests implements TestManager {
7381
// eslint-disable-next-line no-ex-assign
7482
e = err;
7583
}
76-
this.fail.set(func, e);
84+
this.fail.set(func, e as Error);
7785
}
7886
}
7987
}
@@ -112,7 +120,7 @@ class Tests implements TestManager {
112120
this.db.createDocument(path);
113121
GSUnit.fail('Duplicate document without error');
114122
} catch (e) {
115-
if (e.message !== `Document already exists: ${this.db.basePath}${path}`) {
123+
if ((e as Error).message !== `Document already exists: ${this.db.basePath}${path}`) {
116124
throw e;
117125
}
118126
}
@@ -219,7 +227,7 @@ class Tests implements TestManager {
219227
this.db.getDocument(path);
220228
GSUnit.fail('Missing document without error');
221229
} catch (e) {
222-
if (e.message !== `Document "${this.db.basePath}${path}" not found.`) {
230+
if ((e as Error).message !== `Document "${this.db.basePath}${path}" not found.`) {
223231
throw e;
224232
}
225233
}

Util.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
class Util_ {
1+
import FirestoreAPI = gapi.client.firestore;
2+
export default class Util_ {
23
/**
34
* RegEx test for root path references. Groups relative path for extraction.
45
*/

0 commit comments

Comments
 (0)