UNPKG

6.94 kBJavaScriptView Raw
1Object.defineProperty(exports, '__esModule', { value: true });
2exports.gsheet =
3 exports.Gsheet =
4 exports.noFilter =
5 exports.noChange =
6 exports.trim =
7 exports.gSheetDateTime =
8 exports.gSheetDate =
9 void 0;
10/* eslint-disable camelcase */
11/* eslint-disable no-use-before-define */
12const sheets_1 = require('@googleapis/sheets');
13const promise_1 = require('@resolute/std/promise');
14
15const gSheetDate = (date = new Date()) =>
16 `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}`;
17exports.gSheetDate = gSheetDate;
18const gSheetDateTime = (date = new Date()) =>
19 `${(0, exports.gSheetDate)(date)} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
20exports.gSheetDateTime = gSheetDateTime;
21const trim = (arg) => {
22 if (typeof arg === 'string') {
23 return arg.replace(/\s+/g, ' ').trim();
24 }
25 return arg;
26};
27exports.trim = trim;
28const noChange = (arg) => arg;
29exports.noChange = noChange;
30const noFilter = () => true;
31exports.noFilter = noFilter;
32// From Google’s API (TypeScript/Go To Definition) documentation:
33//
34// For output, empty trailing rows and columns will not be included.
35//
36// For input, supported value types are: bool, string, and double. Null values
37// will be skipped. To set a cell to an empty value, set the string value to an
38// empty string.
39class Gsheet {
40 constructor(options) {
41 let _a; let _b; let _c; let
42 _d;
43 this.auth = (() => {
44 const googleAuth = new sheets_1.auth.GoogleAuth({
45 scopes: ['https://www.googleapis.com/auth/spreadsheets'],
46 });
47 if (options.jwt) {
48 const client_email =
49 'client_email' in options.jwt ? options.jwt.client_email : options.jwt.email;
50 const private_key =
51 'private_key' in options.jwt ? options.jwt.private_key : options.jwt.key;
52 return googleAuth.fromJSON({ client_email, private_key });
53 }
54 return googleAuth.getClient();
55 })();
56 this.client = Promise.resolve(this.auth).then((auth) => {
57 let _a;
58 return (0, sheets_1.sheets)({
59 version: 'v4',
60 auth,
61 http2: (_a = options.http2) !== null && _a !== void 0 ? _a : false,
62 });
63 });
64 this.spreadsheetId = options.spreadsheetId;
65 this.range = options.range;
66 this.headerRows =
67 (_a = options === null || options === void 0 ? void 0 : options.headerRows) !== null &&
68 _a !== void 0
69 ? _a
70 : 1;
71 this.keyTransform =
72 (_b = options === null || options === void 0 ? void 0 : options.keyTransform) !== null &&
73 _b !== void 0
74 ? _b
75 : exports.noChange;
76 this.sanitize =
77 (_c = options === null || options === void 0 ? void 0 : options.sanitize) !== null &&
78 _c !== void 0
79 ? _c
80 : exports.trim;
81 this.filter =
82 (_d = options === null || options === void 0 ? void 0 : options.filter) !== null &&
83 _d !== void 0
84 ? _d
85 : exports.noFilter;
86 this.keptSheet = (0, promise_1.keeper)(this.getSheet.bind(this));
87 this.keptColumns = (0, promise_1.keeper)(this.getColumns.bind(this));
88 if (options.preload) {
89 this.refresh();
90 }
91 if (options.interval) {
92 this.keepFresh(options.interval);
93 }
94 }
95 async getSheet(range = this.range) {
96 // const auth = new google.auth.GoogleAuth();
97 const client = await this.client;
98 const response = await client.spreadsheets.values.get({
99 spreadsheetId: this.spreadsheetId,
100 range,
101 });
102 if (!response.data.values) {
103 throw new Error('No data found.');
104 }
105 return response.data.values;
106 }
107 getHeaderRowsOnly() {
108 return this.getSheet(`${this.range}!A${this.headerRows}:ZZZ${this.headerRows}`);
109 }
110 getColumnsFromSheetCache() {
111 try {
112 return this.keptSheet.stale();
113 } catch (_a) {
114 return this.getHeaderRowsOnly();
115 }
116 }
117 async getColumns() {
118 const rows = await this.getColumnsFromSheetCache();
119 const columns = rows[this.headerRows - 1];
120 if (!columns || !columns.length) {
121 throw new Error(`Unable to turn rows into objects. Row at ${this.headerRows} is empty.`);
122 }
123 return columns.map(this.sanitize).map(this.keyTransform);
124 }
125 async columns() {
126 return this.keptColumns.get();
127 }
128 async rows() {
129 const rows = await this.keptSheet.get();
130 return rows
131 .slice(this.headerRows)
132 .map((row) => row.map(this.sanitize))
133 .filter(this.filter);
134 }
135 async data() {
136 // IMPORTANT: Get the rows first--otherwise, if the cache is empty,
137 // this.columns() will do its own expensive call to get only the header
138 // rows, even though we’re going to need all of the rows immediately after.
139 const rows = await this.rows();
140 const keys = await this.columns();
141 return rows
142 .map((row) =>
143 row.reduce((obj, value, index) => {
144 if (keys[index]) {
145 // eslint-disable-next-line no-param-reassign
146 obj[keys[index]] = value;
147 }
148 return obj;
149 }, {}))
150 .filter(this.filter);
151 }
152 async normalizeInputData(arg) {
153 if (Array.isArray(arg)) {
154 return arg;
155 }
156 // match against lowercase
157 const entries = Object.entries(arg).map(([key, val]) => [key.toLowerCase(), val]);
158 const columns = await this.columns();
159 return columns
160 .map((key) => (entries.find(([entryKey]) => entryKey === key.toLowerCase()) || [])[1])
161 .map(this.sanitize);
162 }
163 async append(arg) {
164 let _a; let
165 _b;
166 const row = await this.normalizeInputData(arg);
167 if (!row) {
168 throw new Error(`Unable to add ${arg}.`);
169 }
170 const client = await this.client;
171 const response = await client.spreadsheets.values.append({
172 spreadsheetId: this.spreadsheetId,
173 range: this.range,
174 insertDataOption: 'INSERT_ROWS',
175 valueInputOption: 'USER_ENTERED',
176 requestBody: { range: this.range, values: [row] },
177 });
178 // TODO: should we do this? Or allow other readers to still get stale content?
179 this.keptSheet.fresh();
180 if (
181 !(
182 ((_b =
183 (_a = response === null || response === void 0 ? void 0 : response.data) === null ||
184 _a === void 0
185 ? void 0
186 : _a.updates) === null || _b === void 0
187 ? void 0
188 : _b.updatedRows) > 0
189 )
190 ) {
191 throw new Error('Failed to save your information. Please try again.');
192 }
193 return response;
194 }
195 refresh() {
196 this.keptSheet.fresh();
197 }
198 keepFresh(interval) {
199 this.keptSheet.start(interval);
200 }
201}
202exports.Gsheet = Gsheet;
203const gsheet = (options) => {
204 const instance = new Gsheet(options);
205 instance.rows = instance.rows.bind(instance);
206 instance.data = instance.data.bind(instance);
207 instance.keepFresh = instance.keepFresh.bind(instance);
208 instance.append = instance.append.bind(instance);
209 instance.refresh = instance.refresh.bind(instance);
210 return instance;
211};
212exports.gsheet = gsheet;
213exports.default = exports.gsheet;