UNPKG

@resolute/gsheet

Version:

Simple abstractions for Google Sheets

214 lines (212 loc) 6.94 kB
Object.defineProperty(exports, '__esModule', { value: true }); exports.gsheet = exports.Gsheet = exports.noFilter = exports.noChange = exports.trim = exports.gSheetDateTime = exports.gSheetDate = void 0; /* eslint-disable camelcase */ /* eslint-disable no-use-before-define */ const sheets_1 = require('@googleapis/sheets'); const promise_1 = require('@resolute/std/promise'); const gSheetDate = (date = new Date()) => `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}`; exports.gSheetDate = gSheetDate; const gSheetDateTime = (date = new Date()) => `${(0, exports.gSheetDate)(date)} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`; exports.gSheetDateTime = gSheetDateTime; const trim = (arg) => { if (typeof arg === 'string') { return arg.replace(/\s+/g, ' ').trim(); } return arg; }; exports.trim = trim; const noChange = (arg) => arg; exports.noChange = noChange; const noFilter = () => true; exports.noFilter = noFilter; // From Google’s API (TypeScript/Go To Definition) documentation: // // For output, empty trailing rows and columns will not be included. // // For input, supported value types are: bool, string, and double. Null values // will be skipped. To set a cell to an empty value, set the string value to an // empty string. class Gsheet { constructor(options) { let _a; let _b; let _c; let _d; this.auth = (() => { const googleAuth = new sheets_1.auth.GoogleAuth({ scopes: ['https://www.googleapis.com/auth/spreadsheets'], }); if (options.jwt) { const client_email = 'client_email' in options.jwt ? options.jwt.client_email : options.jwt.email; const private_key = 'private_key' in options.jwt ? options.jwt.private_key : options.jwt.key; return googleAuth.fromJSON({ client_email, private_key }); } return googleAuth.getClient(); })(); this.client = Promise.resolve(this.auth).then((auth) => { let _a; return (0, sheets_1.sheets)({ version: 'v4', auth, http2: (_a = options.http2) !== null && _a !== void 0 ? _a : false, }); }); this.spreadsheetId = options.spreadsheetId; this.range = options.range; this.headerRows = (_a = options === null || options === void 0 ? void 0 : options.headerRows) !== null && _a !== void 0 ? _a : 1; this.keyTransform = (_b = options === null || options === void 0 ? void 0 : options.keyTransform) !== null && _b !== void 0 ? _b : exports.noChange; this.sanitize = (_c = options === null || options === void 0 ? void 0 : options.sanitize) !== null && _c !== void 0 ? _c : exports.trim; this.filter = (_d = options === null || options === void 0 ? void 0 : options.filter) !== null && _d !== void 0 ? _d : exports.noFilter; this.keptSheet = (0, promise_1.keeper)(this.getSheet.bind(this)); this.keptColumns = (0, promise_1.keeper)(this.getColumns.bind(this)); if (options.preload) { this.refresh(); } if (options.interval) { this.keepFresh(options.interval); } } async getSheet(range = this.range) { // const auth = new google.auth.GoogleAuth(); const client = await this.client; const response = await client.spreadsheets.values.get({ spreadsheetId: this.spreadsheetId, range, }); if (!response.data.values) { throw new Error('No data found.'); } return response.data.values; } getHeaderRowsOnly() { return this.getSheet(`${this.range}!A${this.headerRows}:ZZZ${this.headerRows}`); } getColumnsFromSheetCache() { try { return this.keptSheet.stale(); } catch (_a) { return this.getHeaderRowsOnly(); } } async getColumns() { const rows = await this.getColumnsFromSheetCache(); const columns = rows[this.headerRows - 1]; if (!columns || !columns.length) { throw new Error(`Unable to turn rows into objects. Row at ${this.headerRows} is empty.`); } return columns.map(this.sanitize).map(this.keyTransform); } async columns() { return this.keptColumns.get(); } async rows() { const rows = await this.keptSheet.get(); return rows .slice(this.headerRows) .map((row) => row.map(this.sanitize)) .filter(this.filter); } async data() { // IMPORTANT: Get the rows first--otherwise, if the cache is empty, // this.columns() will do its own expensive call to get only the header // rows, even though we’re going to need all of the rows immediately after. const rows = await this.rows(); const keys = await this.columns(); return rows .map((row) => row.reduce((obj, value, index) => { if (keys[index]) { // eslint-disable-next-line no-param-reassign obj[keys[index]] = value; } return obj; }, {})) .filter(this.filter); } async normalizeInputData(arg) { if (Array.isArray(arg)) { return arg; } // match against lowercase const entries = Object.entries(arg).map(([key, val]) => [key.toLowerCase(), val]); const columns = await this.columns(); return columns .map((key) => (entries.find(([entryKey]) => entryKey === key.toLowerCase()) || [])[1]) .map(this.sanitize); } async append(arg) { let _a; let _b; const row = await this.normalizeInputData(arg); if (!row) { throw new Error(`Unable to add ${arg}.`); } const client = await this.client; const response = await client.spreadsheets.values.append({ spreadsheetId: this.spreadsheetId, range: this.range, insertDataOption: 'INSERT_ROWS', valueInputOption: 'USER_ENTERED', requestBody: { range: this.range, values: [row] }, }); // TODO: should we do this? Or allow other readers to still get stale content? this.keptSheet.fresh(); if ( !( ((_b = (_a = response === null || response === void 0 ? void 0 : response.data) === null || _a === void 0 ? void 0 : _a.updates) === null || _b === void 0 ? void 0 : _b.updatedRows) > 0 ) ) { throw new Error('Failed to save your information. Please try again.'); } return response; } refresh() { this.keptSheet.fresh(); } keepFresh(interval) { this.keptSheet.start(interval); } } exports.Gsheet = Gsheet; const gsheet = (options) => { const instance = new Gsheet(options); instance.rows = instance.rows.bind(instance); instance.data = instance.data.bind(instance); instance.keepFresh = instance.keepFresh.bind(instance); instance.append = instance.append.bind(instance); instance.refresh = instance.refresh.bind(instance); return instance; }; exports.gsheet = gsheet; exports.default = exports.gsheet;