UNPKG

google-spreadsheet

Version:

Google Sheets API -- simple interface to read/write data and manage sheets

1 lines 140 kB
{"version":3,"file":"index.cjs","names":["HTTPError"],"sources":["../src/lib/utils.ts","../src/lib/GoogleSpreadsheetRow.ts","../src/lib/GoogleSpreadsheetCellErrorValue.ts","../src/lib/GoogleSpreadsheetCell.ts","../src/lib/GoogleSpreadsheetWorksheet.ts","../src/lib/types/auth-types.ts","../src/lib/GoogleSpreadsheet.ts"],"sourcesContent":["import * as _ from './toolkit';\n\nexport function getFieldMask(obj: Record<string, unknown>) {\n  let fromGrid = '';\n  const fromRoot = Object.keys(obj).filter((key) => key !== 'gridProperties').join(',');\n\n  if (obj.gridProperties) {\n    fromGrid = Object.keys(obj.gridProperties).map((key) => `gridProperties.${key}`).join(',');\n    if (fromGrid.length && fromRoot.length) {\n      fromGrid = `${fromGrid},`;\n    }\n  }\n  return fromGrid + fromRoot;\n}\n\nexport function columnToLetter(column: number) {\n  let temp;\n  let letter = '';\n  let col = column;\n  while (col > 0) {\n    temp = (col - 1) % 26;\n    letter = String.fromCharCode(temp + 65) + letter;\n    col = (col - temp - 1) / 26;\n  }\n  return letter;\n}\n\nexport function letterToColumn(letter: string) {\n  let column = 0;\n  const { length } = letter;\n  for (let i = 0; i < length; i++) {\n    column += (letter.charCodeAt(i) - 64) * 26 ** (length - i - 1);\n  }\n  return column;\n}\n\nexport function checkForDuplicateHeaders(headers: string[]) {\n  // check for duplicate headers\n  const checkForDupes = _.groupBy(headers); // { c1: ['c1'], c2: ['c2', 'c2' ]}\n  _.each(checkForDupes, (grouped, header) => {\n    if (!header) return; // empty columns are skipped, so multiple is ok\n    if (grouped.length > 1) {\n      throw new Error(`Duplicate header detected: \"${header}\". Please make sure all non-empty headers are unique`);\n    }\n  });\n}\n\n","import { GoogleSpreadsheetWorksheet } from './GoogleSpreadsheetWorksheet';\nimport { columnToLetter } from './utils';\n\n\n// TODO: add type for possible row values (currently any)\n\nexport class GoogleSpreadsheetRow<T extends Record<string, any> = Record<string, any>> {\n  constructor(\n    /** parent GoogleSpreadsheetWorksheet instance */\n    readonly _worksheet: GoogleSpreadsheetWorksheet,\n    /** the A1 row (1-indexed) */\n    private _rowNumber: number,\n    /** raw underlying data for row */\n    private _rawData: any[]\n  ) {\n    this._padRawData();\n  }\n\n  /** pad _rawData with empty strings so it always matches header length */\n  private _padRawData() {\n    const headerLength = this._worksheet.headerValues.length;\n    while (this._rawData.length < headerLength) {\n      this._rawData.push('');\n    }\n  }\n\n  private _deleted = false;\n  get deleted() { return this._deleted; }\n\n  /** row number (matches A1 notation, ie first row is 1) */\n  get rowNumber() { return this._rowNumber; }\n  /**\n   * @internal\n   * Used internally to update row numbers after deleting rows.\n   * Should not be called directly.\n  */\n  _updateRowNumber(newRowNumber: number) {\n    this._rowNumber = newRowNumber;\n  }\n\n  /**\n   * @internal\n   * Used internally to mark row as deleted.\n   * Should not be called directly.\n   */\n  _markDeleted() {\n    this._deleted = true;\n  }\n\n  get a1Range() {\n    return [\n      this._worksheet.a1SheetName,\n      '!',\n      `A${this._rowNumber}`,\n      ':',\n      `${columnToLetter(this._worksheet.headerValues.length)}${this._rowNumber}`,\n    ].join('');\n  }\n\n  /** get row's value of specific cell (by header key) */\n  get(key: keyof T) {\n    const index = this._worksheet.headerValues.indexOf(key as string);\n    return this._rawData[index];\n  }\n  /** set row's value of specific cell (by header key) */\n  set<K extends keyof T>(key: K, val: T[K]) {\n    const index = this._worksheet.headerValues.indexOf(key as string);\n    this._rawData[index] = val;\n  }\n  /** set multiple values in the row at once from an object */\n  assign(obj: Partial<T>) {\n    // eslint-disable-next-line no-restricted-syntax, guard-for-in\n    for (const key in obj) this.set(key, obj[key] as any);\n  }\n\n  /** return raw object of row data */\n  toObject() {\n    const o: Partial<T> = {};\n    for (let i = 0; i < this._worksheet.headerValues.length; i++) {\n      const key: keyof T = this._worksheet.headerValues[i];\n      if (!key) continue;\n      o[key] = this._rawData[i];\n    }\n    return o;\n  }\n\n  /** save row values */\n  async save(options?: { raw?: boolean }) {\n    if (this._deleted) throw new Error('This row has been deleted - call getRows again before making updates.');\n\n    const response = await this._worksheet._spreadsheet.sheetsApi.put(`values/${encodeURIComponent(this.a1Range)}`, {\n      searchParams: {\n        valueInputOption: options?.raw ? 'RAW' : 'USER_ENTERED',\n        includeValuesInResponse: true,\n      },\n      json: {\n        range: this.a1Range,\n        majorDimension: 'ROWS',\n        values: [this._rawData],\n      },\n    });\n    const data = await response.json<any>();\n    this._rawData = data.updatedData.values?.[0] || [];\n    this._padRawData();\n  }\n\n  /** delete this row */\n  async delete() {\n    if (this._deleted) throw new Error('This row has been deleted - call getRows again before making updates.');\n\n    const result = await this._worksheet._makeSingleUpdateRequest('deleteRange', {\n      range: {\n        sheetId: this._worksheet.sheetId,\n        startRowIndex: this._rowNumber - 1, // this format is zero indexed, because of course...\n        endRowIndex: this._rowNumber,\n      },\n      shiftDimension: 'ROWS',\n    });\n    this._deleted = true;\n    this._worksheet._shiftRowCache(this.rowNumber);\n\n    return result;\n  }\n\n  /**\n   * @internal\n   * Used internally to clear row data after calling sheet.clearRows\n   * Should not be called directly.\n  */\n  _clearRowData() {\n    for (let i = 0; i < this._rawData.length; i++) {\n      this._rawData[i] = '';\n    }\n  }\n}\n","import { CellValueErrorType, ErrorValue } from './types/sheets-types';\n\n/**\n * Cell error\n *\n * not a js \"error\" that gets thrown, but a value that holds an error code and message for a cell\n * it's useful to use a class so we can check `instanceof`\n\n * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/other#ErrorType\n */\nexport class GoogleSpreadsheetCellErrorValue {\n  /**\n   * type of the error\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/other#ErrorType\n   * */\n  readonly type: CellValueErrorType;\n\n  /** A message with more information about the error (in the spreadsheet's locale) */\n  readonly message: string;\n\n  constructor(rawError: ErrorValue) {\n    this.type = rawError.type;\n    this.message = rawError.message;\n  }\n}\n","/* eslint-disable max-classes-per-file */\nimport * as _ from './toolkit';\n\nimport { columnToLetter } from './utils';\n\nimport { GoogleSpreadsheetWorksheet } from './GoogleSpreadsheetWorksheet';\nimport { GoogleSpreadsheetCellErrorValue } from './GoogleSpreadsheetCellErrorValue';\n\nimport {\n  CellData,\n  CellFormat, CellValueType, ColumnIndex, RowIndex,\n} from './types/sheets-types';\n\nexport class GoogleSpreadsheetCell {\n  private _rawData?: CellData;\n  private _draftData: any = {};\n  private _error?: GoogleSpreadsheetCellErrorValue;\n  private _deleted = false;\n\n  constructor(\n    readonly _sheet: GoogleSpreadsheetWorksheet,\n    private _rowIndex: RowIndex,\n    private _columnIndex: ColumnIndex,\n    rawCellData: CellData\n  ) {\n    this._updateRawData(rawCellData);\n    this._rawData = rawCellData; // so TS does not complain\n  }\n\n  get deleted() { return this._deleted; }\n\n  // TODO: figure out how to deal with empty rawData\n  // newData can be undefined/null if the cell is totally empty and unformatted\n  /**\n   * update cell using raw CellData coming back from sheets API\n   * @internal\n   */\n  _updateRawData(newData: CellData) {\n    this._rawData = newData;\n    this._draftData = {};\n    if (this._rawData?.effectiveValue && 'errorValue' in this._rawData.effectiveValue) {\n      this._error = new GoogleSpreadsheetCellErrorValue(this._rawData.effectiveValue.errorValue);\n    } else {\n      this._error = undefined;\n    }\n  }\n\n  // CELL LOCATION/ADDRESS /////////////////////////////////////////////////////////////////////////\n  get rowIndex() { return this._rowIndex; }\n  get columnIndex() { return this._columnIndex; }\n  get a1Column() { return columnToLetter(this._columnIndex + 1); }\n  get a1Row() { return this._rowIndex + 1; } // a1 row numbers start at 1 instead of 0\n  get a1Address() { return `${this.a1Column}${this.a1Row}`; }\n\n  /**\n   * @internal\n   * Used internally to update cell indices after deleting rows/columns.\n   * Should not be called directly.\n   */\n  _updateIndices(rowIndex: RowIndex, columnIndex: ColumnIndex) {\n    this._rowIndex = rowIndex;\n    this._columnIndex = columnIndex;\n  }\n\n  /**\n   * @internal\n   * Used internally to mark cell as deleted.\n   * Should not be called directly.\n   */\n  _markDeleted() {\n    this._deleted = true;\n  }\n\n  // CELL CONTENTS - VALUE/FORMULA/NOTES ///////////////////////////////////////////////////////////\n  get value(): number | boolean | string | null | GoogleSpreadsheetCellErrorValue {\n    // const typeKey = _.keys(this._rawData.effectiveValue)[0];\n    if (this._draftData.value !== undefined) throw new Error('Value has been changed');\n    if (this._error) return this._error;\n    if (!this._rawData?.effectiveValue) return null;\n    return _.values(this._rawData.effectiveValue)[0];\n  }\n\n\n  set value(newValue: number | boolean | Date | string | null | undefined | GoogleSpreadsheetCellErrorValue) {\n    if (this._deleted) throw new Error('This cell has been deleted - reload cells before making updates.');\n\n    // had to include the GoogleSpreadsheetCellErrorValue in the type to make TS happy\n    if (newValue instanceof GoogleSpreadsheetCellErrorValue) {\n      throw new Error(\"You can't manually set a value to an error\");\n    }\n\n    if (_.isBoolean(newValue)) {\n      this._draftData.valueType = 'boolValue';\n    } else if (_.isString(newValue)) {\n      if (newValue.substring(0, 1) === '=') this._draftData.valueType = 'formulaValue';\n      else this._draftData.valueType = 'stringValue';\n    } else if (_.isFinite(newValue)) {\n      this._draftData.valueType = 'numberValue';\n    } else if (_.isNil(newValue)) {\n      // null or undefined\n      this._draftData.valueType = 'stringValue';\n      newValue = '';\n    } else {\n      throw new Error('Set value to boolean, string, or number');\n    }\n    this._draftData.value = newValue;\n  }\n\n  get valueType(): CellValueType | null {\n    // an error only happens with a formula (as far as I know)\n    if (this._error) return 'errorValue';\n    if (!this._rawData?.effectiveValue) return null;\n    return _.keys(this._rawData.effectiveValue)[0] as CellValueType;\n  }\n\n  /** The formatted value of the cell - this is the value as it's shown to the user */\n  get formattedValue(): string | null { return this._rawData?.formattedValue || null; }\n\n  get formula() { return _.get(this._rawData, 'userEnteredValue.formulaValue', null); }\n  set formula(newValue: string | null) {\n    if (!newValue) throw new Error('To clear a formula, set `cell.value = null`');\n    if (newValue.substring(0, 1) !== '=') throw new Error('formula must begin with \"=\"');\n    this.value = newValue; // use existing value setter\n  }\n  /**\n   * @deprecated use `cell.errorValue` instead\n   */\n  get formulaError() { return this._error; }\n  /**\n   * error contained in the cell, which can happen with a bad formula (maybe some other weird cases?)\n   */\n  get errorValue() { return this._error; }\n\n  get numberValue(): number | undefined {\n    if (this.valueType !== 'numberValue') return undefined;\n    return this.value as number;\n  }\n  set numberValue(val: number | undefined) {\n    this.value = val;\n  }\n\n  get boolValue(): boolean | undefined {\n    if (this.valueType !== 'boolValue') return undefined;\n    return this.value as boolean;\n  }\n  set boolValue(val: boolean | undefined) {\n    this.value = val;\n  }\n\n  get stringValue(): string | undefined {\n    if (this.valueType !== 'stringValue') return undefined;\n    return this.value as string;\n  }\n  set stringValue(val: string | undefined) {\n    this._draftData.valueType = 'stringValue';\n    this._draftData.value = val || '';\n  }\n\n  /**\n   * Hyperlink contained within the cell.\n   *\n   * To modify, do not set directly. Instead set cell.formula, for example `cell.formula = \\'=HYPERLINK(\"http://google.com\", \"Google\")\\'`\n   */\n  get hyperlink() {\n    if (this._draftData.value) throw new Error('Save cell to be able to read hyperlink');\n    return this._rawData?.hyperlink;\n  }\n\n  /** a note attached to the cell */\n  get note(): string {\n    return this._draftData.note !== undefined ? this._draftData.note : this._rawData?.note || '';\n  }\n  set note(newVal: string | null | undefined | false) {\n    if (newVal === null || newVal === undefined || newVal === false) newVal = '';\n    if (!_.isString(newVal)) throw new Error('Note must be a string');\n    if (newVal === this._rawData?.note) delete this._draftData.note;\n    else this._draftData.note = newVal;\n  }\n\n  // CELL FORMATTING ///////////////////////////////////////////////////////////////////////////////\n  get userEnteredFormat() { return Object.freeze(this._rawData?.userEnteredFormat); }\n  get effectiveFormat() { return Object.freeze(this._rawData?.effectiveFormat); }\n\n  private _getFormatParam<T extends keyof CellFormat>(param: T): Readonly<CellFormat[T]> {\n    // we freeze the object so users don't change nested props accidentally\n    // TODO: figure out something that would throw an error if you try to update it?\n    if (_.get(this._draftData, `userEnteredFormat.${param}`)) {\n      throw new Error('User format is unsaved - save the cell to be able to read it again');\n    }\n    // TODO: figure out how to deal with possible empty rawData\n    // if (!this._rawData?.userEnteredFormat?.[param]) {\n    //   return undefined;\n    // }\n    return Object.freeze(this._rawData!.userEnteredFormat[param]);\n  }\n\n  private _setFormatParam<T extends keyof CellFormat>(param: T, newVal: CellFormat[T]) {\n    if (_.isEqual(newVal, _.get(this._rawData, `userEnteredFormat.${param}`))) {\n      _.unset(this._draftData, `userEnteredFormat.${param}`);\n    } else {\n      _.set(this._draftData, `userEnteredFormat.${param}`, newVal);\n      this._draftData.clearFormat = false;\n    }\n  }\n\n  // format getters\n  get numberFormat() { return this._getFormatParam('numberFormat'); }\n  get backgroundColor() { return this._getFormatParam('backgroundColor'); }\n  get backgroundColorStyle() { return this._getFormatParam('backgroundColorStyle'); }\n  get borders() { return this._getFormatParam('borders'); }\n  get padding() { return this._getFormatParam('padding'); }\n  get horizontalAlignment() { return this._getFormatParam('horizontalAlignment'); }\n  get verticalAlignment() { return this._getFormatParam('verticalAlignment'); }\n  get wrapStrategy() { return this._getFormatParam('wrapStrategy'); }\n  get textDirection() { return this._getFormatParam('textDirection'); }\n  get textFormat() { return this._getFormatParam('textFormat'); }\n  get hyperlinkDisplayType() { return this._getFormatParam('hyperlinkDisplayType'); }\n  get textRotation() { return this._getFormatParam('textRotation'); }\n\n  // format setters\n  set numberFormat(newVal: CellFormat['numberFormat']) { this._setFormatParam('numberFormat', newVal); }\n  set backgroundColor(newVal: CellFormat['backgroundColor']) { this._setFormatParam('backgroundColor', newVal); }\n  set backgroundColorStyle(newVal: CellFormat['backgroundColorStyle']) { this._setFormatParam('backgroundColorStyle', newVal); }\n  set borders(newVal: CellFormat['borders']) { this._setFormatParam('borders', newVal); }\n  set padding(newVal: CellFormat['padding']) { this._setFormatParam('padding', newVal); }\n  set horizontalAlignment(newVal: CellFormat['horizontalAlignment']) { this._setFormatParam('horizontalAlignment', newVal); }\n  set verticalAlignment(newVal: CellFormat['verticalAlignment']) { this._setFormatParam('verticalAlignment', newVal); }\n  set wrapStrategy(newVal: CellFormat['wrapStrategy']) { this._setFormatParam('wrapStrategy', newVal); }\n  set textDirection(newVal: CellFormat['textDirection']) { this._setFormatParam('textDirection', newVal); }\n  set textFormat(newVal: CellFormat['textFormat']) { this._setFormatParam('textFormat', newVal); }\n  set hyperlinkDisplayType(newVal: CellFormat['hyperlinkDisplayType']) { this._setFormatParam('hyperlinkDisplayType', newVal); }\n  set textRotation(newVal: CellFormat['textRotation']) { this._setFormatParam('textRotation', newVal); }\n\n  clearAllFormatting() {\n    // need to track this separately since by setting/unsetting things, we may end up with\n    // this._draftData.userEnteredFormat as an empty object, but not an intent to clear it\n    this._draftData.clearFormat = true;\n    delete this._draftData.userEnteredFormat;\n  }\n\n  // SAVING + UTILS ////////////////////////////////////////////////////////////////////////////////\n\n  // returns true if there are any updates that have not been saved yet\n  get _isDirty() {\n    // have to be careful about checking undefined rather than falsy\n    // in case a new value is empty string or 0 or false\n    if (this._draftData.note !== undefined) return true;\n    if (_.keys(this._draftData.userEnteredFormat).length) return true;\n    if (this._draftData.clearFormat) return true;\n    if (this._draftData.value !== undefined) return true;\n    return false;\n  }\n\n  discardUnsavedChanges() {\n    this._draftData = {};\n  }\n\n  /**\n   * saves updates for single cell\n   * usually it's better to make changes and call sheet.saveUpdatedCells\n   * */\n  async save() {\n    await this._sheet.saveCells([this]);\n  }\n\n  /**\n   * used by worksheet when saving cells\n   * returns an individual batchUpdate request to update the cell\n   * @internal\n   */\n  _getUpdateRequest() {\n    // this logic should match the _isDirty logic above\n    // but we need it broken up to build the request below\n    const isValueUpdated = this._draftData.value !== undefined;\n    const isNoteUpdated = this._draftData.note !== undefined;\n    const isFormatUpdated = !!_.keys(this._draftData.userEnteredFormat || {}).length;\n    const isFormatCleared = this._draftData.clearFormat;\n\n    // if no updates, we return null, which we can filter out later before sending requests\n    if (!_.some([isValueUpdated, isNoteUpdated, isFormatUpdated, isFormatCleared])) {\n      return null;\n    }\n\n    // build up the formatting object, which has some quirks...\n    const format = {\n      // have to pass the whole object or it will clear existing properties\n      ...this._rawData?.userEnteredFormat,\n      ...this._draftData.userEnteredFormat,\n    };\n    // if background color already set, cell has backgroundColor and backgroundColorStyle\n    // but backgroundColorStyle takes precendence so we must remove to set the color\n    // see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/cells#CellFormat\n    if (_.get(this._draftData, 'userEnteredFormat.backgroundColor')) {\n      delete (format.backgroundColorStyle);\n    }\n\n    return {\n      updateCells: {\n        rows: [{\n          values: [{\n            ...isValueUpdated && {\n              userEnteredValue: { [this._draftData.valueType]: this._draftData.value },\n            },\n            ...isNoteUpdated && {\n              note: this._draftData.note,\n            },\n            ...isFormatUpdated && {\n              userEnteredFormat: format,\n            },\n            ...isFormatCleared && {\n              userEnteredFormat: {},\n            },\n          }],\n        }],\n        // turns into a string of which fields to update ex \"note,userEnteredFormat\"\n        fields: _.keys(_.pickBy({\n          userEnteredValue: isValueUpdated,\n          note: isNoteUpdated,\n          userEnteredFormat: isFormatUpdated || isFormatCleared,\n        })).join(','),\n        start: {\n          sheetId: this._sheet.sheetId,\n          rowIndex: this.rowIndex,\n          columnIndex: this.columnIndex,\n        },\n      },\n    };\n  }\n}\n","import { type ReadableStream } from 'stream/web';\nimport * as _ from './toolkit';\n\nimport { GoogleSpreadsheetRow } from './GoogleSpreadsheetRow';\nimport { GoogleSpreadsheetCell } from './GoogleSpreadsheetCell';\n\nimport {\n  getFieldMask, columnToLetter, letterToColumn, checkForDuplicateHeaders,\n} from './utils';\nimport { GoogleSpreadsheet } from './GoogleSpreadsheet';\nimport {\n  A1Range, SpreadsheetId, DimensionRangeIndexes, WorksheetDimension, WorksheetId, WorksheetProperties, A1Address,\n  RowIndex, ColumnIndex, DataFilterWithoutWorksheetId, DataFilter, GetValuesRequestOptions, WorksheetGridProperties,\n  WorksheetDimensionProperties, CellDataRange, AddRowOptions, GridRangeWithOptionalWorksheetId,\n  DataValidationRule,\n  ProtectedRange, Integer, GridCoordinateWithOptionalWorksheetId, PasteType, DelimiterType, PasteOrientation,\n  SortSpec, SourceAndDestination, DimensionRange,\n  FilterView, ConditionalFormatRule, BandedRange, DeveloperMetadata, DataFilterObject,\n} from './types/sheets-types';\n\n\n// types of cell data accepted when using row based api\ntype RowCellData = string | number | boolean | Date;\n// raw row data can be passed in as an array or an object using header values as keys\ntype RawRowData = RowCellData[] | Record<string, RowCellData>;\n\nexport class GoogleSpreadsheetWorksheet {\n  // assume \"header row\" (for row-based calls) is in first row, can be adjusted later\n  private _headerRowIndex = 1;\n\n  private _rawProperties: WorksheetProperties | null = null;\n  private _cells: GoogleSpreadsheetCell[][] = [];\n  private _rowMetadata: any[] = [];\n  private _columnMetadata: any[] = [];\n  private _protectedRanges: ProtectedRange[] | null = null;\n\n  private _headerValues: string[] | undefined;\n  get headerValues() {\n    if (!this._headerValues) {\n      throw new Error('Header values are not yet loaded');\n    }\n    return this._headerValues!;\n  }\n\n  constructor(\n    /** parent GoogleSpreadsheet instance */\n    readonly _spreadsheet: GoogleSpreadsheet,\n    rawProperties: WorksheetProperties,\n    rawCellData?: CellDataRange[],\n    protectedRanges?: ProtectedRange[]\n  ) {\n    this._headerRowIndex = 1;\n\n    // basic properties\n    this._rawProperties = rawProperties;\n\n    this._cells = []; // we will use a 2d sparse array to store cells;\n\n    this._rowMetadata = []; // 1d sparse array\n    this._columnMetadata = [];\n    if (protectedRanges) this._protectedRanges = protectedRanges;\n\n    if (rawCellData) this._fillCellData(rawCellData);\n  }\n\n  // INTERNAL UTILITY FUNCTIONS ////////////////////////////////////////////////////////////////////\n\n  updateRawData(properties: WorksheetProperties, rawCellData: CellDataRange[], protectedRanges?: ProtectedRange[]) {\n    this._rawProperties = properties;\n    this._fillCellData(rawCellData);\n    if (protectedRanges) this._protectedRanges = protectedRanges;\n  }\n\n  async _makeSingleUpdateRequest(requestType: string, requestParams: any) {\n    // pass the call up to the parent\n    return this._spreadsheet._makeSingleUpdateRequest(requestType, {\n      ...requestParams,\n    });\n  }\n\n  private _ensureInfoLoaded() {\n    if (!this._rawProperties) {\n      throw new Error('You must call `doc.loadInfo()` again before accessing this property');\n    }\n  }\n\n  /**\n   * clear local cache of sheet data/properties\n   */\n  resetLocalCache(\n    /** set to true to clear data only, leaving sheet metadata/propeties intact */\n    dataOnly?: boolean\n  ) {\n    if (!dataOnly) this._rawProperties = null;\n    this._headerValues = undefined;\n    this._headerRowIndex = 1;\n    this._cells = [];\n  }\n\n  private _fillCellData(\n    dataRanges: CellDataRange[]\n  ) {\n    _.each(dataRanges, (range) => {\n      const startRow = range.startRow || 0;\n      const startColumn = range.startColumn || 0;\n      const numRows = range.rowMetadata.length;\n      const numColumns = range.columnMetadata.length;\n\n      // update cell data for entire range\n      for (let i = 0; i < numRows; i++) {\n        const actualRow = startRow + i;\n        for (let j = 0; j < numColumns; j++) {\n          const actualColumn = startColumn + j;\n\n          // if the row has not been initialized yet, do it\n          if (!this._cells[actualRow]) this._cells[actualRow] = [];\n\n          // see if the response includes some info for the cell\n          const cellData = _.get(range, `rowData[${i}].values[${j}]`);\n\n          // update the cell object or create it\n          if (this._cells[actualRow][actualColumn]) {\n            this._cells[actualRow][actualColumn]._updateRawData(cellData);\n          } else {\n            this._cells[actualRow][actualColumn] = new GoogleSpreadsheetCell(\n              this,\n              actualRow,\n              actualColumn,\n              cellData\n            );\n          }\n        }\n      }\n\n      // update row metadata\n      for (let i = 0; i < range.rowMetadata.length; i++) {\n        this._rowMetadata[startRow + i] = range.rowMetadata[i];\n      }\n      // update column metadata\n      for (let i = 0; i < range.columnMetadata.length; i++) {\n        this._columnMetadata[startColumn + i] = range.columnMetadata[i];\n      }\n    });\n  }\n\n  // TODO: make this handle A1 ranges as well?\n  private _addSheetIdToRange(range: GridRangeWithOptionalWorksheetId) {\n    if (range.sheetId && range.sheetId !== this.sheetId) {\n      throw new Error('Leave sheet ID blank or set to matching ID of this sheet');\n    }\n    return {\n      ...range,\n      sheetId: this.sheetId,\n    };\n  }\n\n\n  // PROPERTY GETTERS //////////////////////////////////////////////////////////////////////////////\n\n  private _getProp<T extends keyof WorksheetProperties>(param: T): WorksheetProperties[T] {\n    this._ensureInfoLoaded();\n    // see note about asserting info loaded on GoogleSpreasheet\n    return this._rawProperties![param];\n  }\n  // eslint-disable-line no-unused-vars\n  private _setProp<T extends keyof WorksheetProperties>(_param: T, _newVal: WorksheetProperties[T]) {\n    throw new Error('Do not update directly - use `updateProperties()`');\n  }\n\n  get sheetId() { return this._getProp('sheetId'); }\n  get title() { return this._getProp('title'); }\n  get index() { return this._getProp('index'); }\n  get sheetType() { return this._getProp('sheetType'); }\n  get gridProperties() { return this._getProp('gridProperties'); }\n  get hidden() { return this._getProp('hidden'); }\n  get tabColor() { return this._getProp('tabColor'); }\n  get rightToLeft() { return this._getProp('rightToLeft'); }\n  get protectedRanges() { return this._protectedRanges; }\n  private get _headerRange() {\n    return `A${this._headerRowIndex}:${this.lastColumnLetter}${this._headerRowIndex}`;\n  }\n\n  set sheetId(newVal: WorksheetProperties['sheetId']) { this._setProp('sheetId', newVal); }\n  set title(newVal: WorksheetProperties['title']) { this._setProp('title', newVal); }\n  set index(newVal: WorksheetProperties['index']) { this._setProp('index', newVal); }\n  set sheetType(newVal: WorksheetProperties['sheetType']) { this._setProp('sheetType', newVal); }\n  set gridProperties(newVal: WorksheetProperties['gridProperties']) { this._setProp('gridProperties', newVal); }\n  set hidden(newVal: WorksheetProperties['hidden']) { this._setProp('hidden', newVal); }\n  set tabColor(newVal: WorksheetProperties['tabColor']) { this._setProp('tabColor', newVal); }\n  set rightToLeft(newVal: WorksheetProperties['rightToLeft']) { this._setProp('rightToLeft', newVal); }\n\n  get rowCount() {\n    this._ensureInfoLoaded();\n    return this.gridProperties.rowCount;\n  }\n  get columnCount() {\n    this._ensureInfoLoaded();\n    return this.gridProperties.columnCount;\n  }\n\n  get a1SheetName() { return `'${this.title.replace(/'/g, \"''\")}'`; }\n  get encodedA1SheetName() { return encodeURIComponent(this.a1SheetName); }\n  get lastColumnLetter() {\n    // TODO: double check behaviour if data not loaded\n    return this.columnCount ? columnToLetter(this.columnCount) : '';\n  }\n\n\n  // CELLS-BASED INTERACTIONS //////////////////////////////////////////////////////////////////////\n\n  get cellStats() {\n    let allCells = _.flatten(this._cells);\n    allCells = _.compact(allCells);\n    return {\n      nonEmpty: _.filter(allCells, (c) => c.value).length,\n      loaded: allCells.length,\n      total: this.rowCount * this.columnCount,\n    };\n  }\n\n  getCellByA1(a1Address: A1Address) {\n    const split = a1Address.match(/([A-Z]+)([0-9]+)/);\n    if (!split) throw new Error(`Cell address \"${a1Address}\" not valid`);\n    const columnIndex = letterToColumn(split[1]);\n    const rowIndex = parseInt(split[2]);\n    return this.getCell(rowIndex - 1, columnIndex - 1);\n  }\n\n  getCell(rowIndex: RowIndex, columnIndex: ColumnIndex) {\n    if (rowIndex < 0 || columnIndex < 0) throw new Error('Min coordinate is 0, 0');\n    if (rowIndex >= this.rowCount || columnIndex >= this.columnCount) {\n      throw new Error(`Out of bounds, sheet is ${this.rowCount} by ${this.columnCount}`);\n    }\n\n    if (!_.get(this._cells, `[${rowIndex}][${columnIndex}]`)) {\n      throw new Error('This cell has not been loaded yet');\n    }\n    return this._cells[rowIndex][columnIndex];\n  }\n\n  async loadCells(sheetFilters?: DataFilterWithoutWorksheetId | DataFilterWithoutWorksheetId[]) {\n    // load the whole sheet\n    if (!sheetFilters) return this._spreadsheet.loadCells(this.a1SheetName);\n\n    const filtersArray = _.isArray(sheetFilters) ? sheetFilters : [sheetFilters];\n    const filtersArrayWithSheetId: DataFilter[] = _.map(filtersArray, (filter) => {\n      // add sheet name to A1 ranges\n      if (_.isString(filter)) {\n        if (filter.startsWith(this.a1SheetName)) return filter;\n        return `${this.a1SheetName}!${filter}`;\n      }\n      if (_.isObject(filter)) {\n        // pass through developer metadata filters without adding sheetId\n        if ('developerMetadataLookup' in filter) {\n          return filter;\n        }\n\n        // check if the user passed in a sheet id\n        const filterAny = filter as any;\n        if (filterAny.sheetId && filterAny.sheetId !== this.sheetId) {\n          throw new Error('Leave sheet ID blank or set to matching ID of this sheet');\n        }\n\n        return { sheetId: this.sheetId, ...filter };\n      }\n      throw new Error('Each filter must be a A1 range string or gridrange object');\n    });\n    return this._spreadsheet.loadCells(filtersArrayWithSheetId);\n  }\n\n  async saveUpdatedCells() {\n    const cellsToSave = _.filter(_.flatten(this._cells), { _isDirty: true });\n    if (cellsToSave.length) {\n      await this.saveCells(cellsToSave);\n    }\n    // TODO: do we want to return stats? or the cells that got updated?\n  }\n\n  async saveCells(cellsToUpdate: GoogleSpreadsheetCell[]) {\n    // we send an individual \"updateCells\" request for each cell\n    // because the fields that are udpated for each group are the same\n    // and we dont want to accidentally overwrite something\n    const requests = _.map(cellsToUpdate, (cell) => cell._getUpdateRequest());\n    const responseRanges = _.map(cellsToUpdate, (c) => `${this.a1SheetName}!${c.a1Address}`);\n\n    // if nothing is being updated the request returned is just `null`\n    // so we make sure at least 1 request is valid - otherwise google throws a 400\n    if (!_.compact(requests).length) {\n      throw new Error('At least one cell must have something to update');\n    }\n\n    await this._spreadsheet._makeBatchUpdateRequest(requests, responseRanges);\n  }\n\n  // SAVING THIS FOR FUTURE USE\n  // puts the cells that need updating into batches\n  // async updateCellsByBatches() {\n  //   // saving this code, but it's problematic because each group must have the same update fields\n  //   const cellsByRow = _.groupBy(cellsToUpdate, 'rowIndex');\n  //   const groupsToSave = [];\n  //   _.each(cellsByRow, (cells, rowIndex) => {\n  //     let cellGroup = [];\n  //     _.each(cells, (c) => {\n  //       if (!cellGroup.length) {\n  //         cellGroup.push(c);\n  //       } else if (\n  //         cellGroup[cellGroup.length - 1].columnIndex ===\n  //         c.columnIndex - 1\n  //       ) {\n  //         cellGroup.push(c);\n  //       } else {\n  //         groupsToSave.push(cellGroup);\n  //         cellGroup = [];\n  //       }\n  //     });\n  //     groupsToSave.push(cellGroup);\n  //   });\n  //   const requests = _.map(groupsToSave, (cellGroup) => ({\n  //     updateCells: {\n  //       rows: [\n  //         {\n  //           values: _.map(cellGroup, (cell) => ({\n  //             ...cell._draftData.value && {\n  //               userEnteredValue: { [cell._draftData.valueType]: cell._draftData.value },\n  //             },\n  //             ...cell._draftData.note !== undefined && {\n  //               note: cell._draftData.note ,\n  //             },\n  //             ...cell._draftData.userEnteredFormat && {\n  //               userEnteredValue: cell._draftData.userEnteredFormat,\n  //             },\n  //           })),\n  //         },\n  //       ],\n  //       fields: 'userEnteredValue,note,userEnteredFormat',\n  //       start: {\n  //         sheetId: this.sheetId,\n  //         rowIndex: cellGroup[0].rowIndex,\n  //         columnIndex: cellGroup[0].columnIndex,\n  //       },\n  //     },\n  //   }));\n  //   const responseRanges = _.map(groupsToSave, (cellGroup) => {\n  //     let a1Range = cellGroup[0].a1Address;\n  //     if (cellGroup.length > 1)\n  //       a1Range += `:${cellGroup[cellGroup.length - 1].a1Address}`;\n  //     return `${cellGroup[0]._sheet.a1SheetName}!${a1Range}`;\n  //   });\n  // }\n\n  // ROW BASED FUNCTIONS ///////////////////////////////////////////////////////////////////////////\n\n  async _ensureHeaderRowLoaded() {\n    if (!this._headerValues) {\n      await this.loadHeaderRow();\n    }\n  }\n\n  async loadHeaderRow(headerRowIndex?: number) {\n    if (headerRowIndex !== undefined) this._headerRowIndex = headerRowIndex;\n    const rows = await this.getCellsInRange(this._headerRange);\n    this._processHeaderRow(rows);\n  }\n\n  private _processHeaderRow(rows: any[]) {\n    if (!rows) {\n      throw new Error('No values in the header row - fill the first row with header values before trying to interact with rows');\n    }\n    this._headerValues = _.map(rows[0], (header) => header?.trim());\n    if (!_.compact(this.headerValues).length) {\n      throw new Error('All your header cells are blank - fill the first row with header values before trying to interact with rows');\n    }\n    checkForDuplicateHeaders(this.headerValues);\n  }\n\n  async setHeaderRow(headerValues: string[], headerRowIndex?: number) {\n    if (!headerValues) return;\n    if (headerValues.length > this.columnCount) {\n      throw new Error(`Sheet is not large enough to fit ${headerValues.length} columns. Resize the sheet first.`);\n    }\n    const trimmedHeaderValues = _.map(headerValues, (h) => h?.trim());\n    checkForDuplicateHeaders(trimmedHeaderValues);\n\n    if (!_.compact(trimmedHeaderValues).length) {\n      throw new Error('All your header cells are blank -');\n    }\n\n    if (headerRowIndex) this._headerRowIndex = headerRowIndex;\n\n    const response = await this._spreadsheet.sheetsApi.put(\n      `values/${this.encodedA1SheetName}!${this._headerRowIndex}:${this._headerRowIndex}`,\n      {\n        searchParams: {\n          valueInputOption: 'USER_ENTERED', // other option is RAW\n          includeValuesInResponse: true,\n        },\n        json: {\n          range: `${this.a1SheetName}!${this._headerRowIndex}:${this._headerRowIndex}`,\n          majorDimension: 'ROWS',\n          values: [[\n            ...trimmedHeaderValues,\n            // pad the rest of the row with empty values to clear them all out\n            ..._.times(this.columnCount - trimmedHeaderValues.length, () => ''),\n          ]],\n        },\n      }\n    );\n    const data = await response.json<any>();\n    this._headerValues = data.updatedData.values[0];\n  }\n\n  // TODO: look at these types\n  async addRows(\n    rows: RawRowData[],\n    options: AddRowOptions = {}\n  ) {\n    // adds multiple rows in one API interaction using the append endpoint\n\n    // each row can be an array or object\n    // an array is just cells\n    // ex: ['column 1', 'column 2', 'column 3']\n    // an object must use the header row values as keys\n    // ex: { col1: 'column 1', col2: 'column 2', col3: 'column 3' }\n\n    // google bug that does not handle colons in sheet names\n    // see https://issuetracker.google.com/issues/150373119\n    if (this.title.includes(':')) {\n      throw new Error('Please remove the \":\" from your sheet title. There is a bug with the google API which breaks appending rows if any colons are in the sheet title.');\n    }\n\n    if (!_.isArray(rows)) throw new Error('You must pass in an array of row values to append');\n\n    await this._ensureHeaderRowLoaded();\n\n    // convert each row into an array of cell values rather than the key/value object\n    const rowsAsArrays: RawRowData[] = [];\n    _.each(rows, (row) => {\n      let rowAsArray;\n      if (_.isArray(row)) {\n        rowAsArray = row;\n      } else if (_.isObject(row)) {\n        rowAsArray = [];\n        for (let i = 0; i < this.headerValues.length; i++) {\n          const propName = this.headerValues[i];\n          rowAsArray[i] = row[propName];\n        }\n      } else {\n        throw new Error('Each row must be an object or an array');\n      }\n      rowsAsArrays.push(rowAsArray);\n    });\n\n    const response = await this._spreadsheet.sheetsApi.post(\n      `values/${this.encodedA1SheetName}!A${this._headerRowIndex}:append`,\n      {\n        searchParams: {\n          valueInputOption: options.raw ? 'RAW' : 'USER_ENTERED',\n          insertDataOption: options.insert ? 'INSERT_ROWS' : 'OVERWRITE',\n          includeValuesInResponse: true,\n        },\n        json: {\n          values: rowsAsArrays,\n        },\n      }\n    );\n\n    // extract the new row number from the A1-notation data range in the response\n    // ex: in \"'Sheet8!A2:C2\" -- we want the `2`\n    const data = await response.json<any>();\n    const { updatedRange } = data.updates;\n    let rowNumber = updatedRange.match(/![A-Z]+([0-9]+):?/)[1];\n    rowNumber = parseInt(rowNumber);\n\n\n    this._ensureInfoLoaded();\n    // if new rows were added, we need update sheet.rowRount\n    if (options.insert) {\n      this._rawProperties!.gridProperties.rowCount += rows.length;\n    } else if (rowNumber + rows.length > this.rowCount) {\n      // have to subtract 1 since one row was inserted at rowNumber\n      this._rawProperties!.gridProperties.rowCount = rowNumber + rows.length - 1;\n    }\n\n    return _.map(data.updates.updatedData.values, (rowValues) => {\n      const row = new GoogleSpreadsheetRow(this, rowNumber++, rowValues);\n      return row;\n    });\n  }\n\n  /**\n   * add a single row - see addRows for more info\n   */\n  async addRow(\n    rowValues: RawRowData,\n    options?: AddRowOptions\n  ) {\n    const rows = await this.addRows([rowValues], options);\n    return rows[0];\n  }\n\n\n  private _rowCache: GoogleSpreadsheetRow[] = [];\n  async getRows<T extends Record<string, any>>(\n    options?: {\n      /** skip first N rows */\n      offset?: number,\n      /** limit number of rows fetched */\n      limit?: number,\n    }\n  ) {\n    // https://developers.google.com/sheets/api/guides/migration\n    // v4 API does not have equivalents for the row-order query parameters provided\n    // Reverse-order is trivial; simply process the returned values array in reverse order.\n    // Order by column is not supported for reads, but it is possible to sort the data then read\n\n    // v4 API does not currently have a direct equivalent for the Sheets API v3 structured queries\n    // However, you can retrieve the relevant data and sort through it as needed in your application\n    const offset = options?.offset || 0;\n    const limit = options?.limit || this.rowCount - 1;\n\n    const firstRow = 1 + this._headerRowIndex + offset;\n    const lastRow = firstRow + limit - 1; // inclusive so we subtract 1\n\n    let rawRows;\n    if (this._headerValues) {\n      const lastColumn = columnToLetter(this.headerValues.length);\n      rawRows = await this.getCellsInRange(\n        `A${firstRow}:${lastColumn}${lastRow}`\n      );\n    } else {\n      const result = await this.batchGetCellsInRange([this._headerRange,\n        `A${firstRow}:${this.lastColumnLetter}${lastRow}`]);\n      this._processHeaderRow(result[0]);\n      rawRows = result[1];\n    }\n\n    if (!rawRows) return [];\n\n    const rows = [];\n    let rowNum = firstRow;\n    for (let i = 0; i < rawRows.length; i++) {\n      const row = new GoogleSpreadsheetRow<T>(this, rowNum++, rawRows[i]);\n      this._rowCache[row.rowNumber] = row;\n      rows.push(row);\n    }\n    return rows;\n  }\n\n  /**\n   * @internal\n   * Used internally to update row numbers after deleting rows.\n   * Should not be called directly.\n   * */\n  _shiftRowCache(deletedRowNumber: number) {\n    delete this._rowCache[deletedRowNumber];\n    this._rowCache.forEach((row) => {\n      if (row.rowNumber > deletedRowNumber) {\n        row._updateRowNumber(row.rowNumber - 1);\n      }\n    });\n  }\n\n  /**\n   * @internal\n   * Used internally to update row numbers after deleting multiple rows.\n   * Should not be called directly.\n   * */\n  _shiftRowCacheBulk(startIndex: number, endIndex: number) {\n    const numDeleted = endIndex - startIndex;\n    // Convert from 0-based indices to 1-based row numbers\n    const startRow = startIndex + 1;\n    const endRow = endIndex;\n\n    // Mark rows in the deleted range as deleted, then remove from cache\n    for (let rowNum = startRow; rowNum <= endRow; rowNum++) {\n      const row = this._rowCache[rowNum];\n      if (row) {\n        row._markDeleted(); // Mark as deleted\n      }\n      delete this._rowCache[rowNum];\n    }\n\n    // Shift rows after the deleted range\n    this._rowCache.forEach((row) => {\n      if (row.rowNumber > endRow) {\n        row._updateRowNumber(row.rowNumber - numDeleted);\n      }\n    });\n  }\n\n  /**\n   * @internal\n   * Used internally to shift cell cache after deleting rows.\n   * Should not be called directly.\n   * */\n  _shiftCellCacheRows(startIndex: number, endIndex: number) {\n    const numDeleted = endIndex - startIndex;\n\n    // Mark cells in the deleted row range as deleted, then remove from cache\n    for (let rowIndex = startIndex; rowIndex < endIndex; rowIndex++) {\n      const row = this._cells[rowIndex];\n      if (row) {\n        row.forEach((cell) => {\n          if (cell) cell._markDeleted();\n        });\n      }\n      delete this._cells[rowIndex];\n    }\n\n    // Collect rows that need to be shifted\n    const rowsToShift: Array<{ oldRowIndex: number, cells: any[] }> = [];\n    for (let rowIndex = endIndex; rowIndex < this._cells.length; rowIndex++) {\n      if (this._cells[rowIndex]) {\n        rowsToShift.push({ oldRowIndex: rowIndex, cells: this._cells[rowIndex] });\n      }\n    }\n\n    // Clear old positions and update to new positions\n    rowsToShift.forEach(({ oldRowIndex, cells }) => {\n      delete this._cells[oldRowIndex];\n      const newRowIndex = oldRowIndex - numDeleted;\n      this._cells[newRowIndex] = cells;\n      // Update each cell's internal row index\n      cells.forEach((cell, colIndex) => {\n        if (cell) cell._updateIndices(newRowIndex, colIndex);\n      });\n    });\n  }\n\n  /**\n   * @internal\n   * Used internally to shift cell cache after deleting columns.\n   * Should not be called directly.\n   * */\n  _shiftCellCacheColumns(startIndex: number, endIndex: number) {\n    const numDeleted = endIndex - startIndex;\n\n    // For each row, delete cells in the deleted column range and shift remaining\n    this._cells.forEach((row, rowIndex) => {\n      if (!row) return;\n\n      // Mark cells in the deleted column range as deleted, then remove from cache\n      for (let colIndex = startIndex; colIndex < endIndex; colIndex++) {\n        const cell = row[colIndex];\n        if (cell) cell._markDeleted();\n        delete row[colIndex];\n      }\n\n      // Collect cells that need to be shifted\n      const cellsToShift: Array<{ oldColIndex: number, cell: any }> = [];\n      for (let colIndex = endIndex; colIndex < row.length; colIndex++) {\n        if (row[colIndex]) {\n          cellsToShift.push({ oldColIndex: colIndex, cell: row[colIndex] });\n        }\n      }\n\n      // Clear old positions and update to new positions\n      cellsToShift.forEach(({ oldColIndex, cell }) => {\n        delete row[oldColIndex];\n        const newColIndex = oldColIndex - numDeleted;\n        row[newColIndex] = cell;\n        // Update cell's internal column index\n        cell._updateIndices(rowIndex, newColIndex);\n      });\n    });\n  }\n\n  async clearRows(\n    options?: {\n      start?: number,\n      end?: number,\n    }\n  ) {\n    // default to first row after header\n    const startRowIndex = options?.start || this._headerRowIndex + 1;\n    const endRowIndex = options?.end || this.rowCount;\n    await this._spreadsheet.sheetsApi.post(`values/${this.encodedA1SheetName}!${startRowIndex}:${endRowIndex}:clear`);\n    this._rowCache.forEach((row) => {\n      if (row.rowNumber >= startRowIndex && row.rowNumber <= endRowIndex) row._clearRowData();\n    });\n  }\n\n  // BASIC PROPS ///////////////////////////////////////////////////////////////////////////////////\n  /** @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#UpdateSheetPropertiesRequest */\n  async updateProperties(properties: Partial<Omit<WorksheetProperties, 'sheetId'>>) {\n    // Request type = `updateSheetProperties`\n\n    return this._makeSingleUpdateRequest('updateSheetProperties', {\n      properties: {\n        sheetId: this.sheetId,\n        ...properties,\n      },\n      fields: getFieldMask(properties),\n    });\n  }\n\n  /**\n   * passes through the call to updateProperties to update only the gridProperties object\n   */\n  async updateGridProperties(\n    gridProperties: Partial<WorksheetGridProperties>\n  ) {\n    return this.updateProperties({ gridProperties: gridProperties as WorksheetGridProperties });\n  }\n\n  /**\n   * resize, internally just calls updateGridProperties\n   */\n  async resize(\n    gridProperties: Pick<WorksheetGridProperties, 'rowCount' | 'columnCount'>\n  ) {\n    return this.updateGridProperties(gridProperties);\n  }\n\n  /**\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#updatedimensionpropertiesrequest\n   */\n  async updateDimensionProperties(\n    columnsOrRows: WorksheetDimension,\n    properties: Partial<WorksheetDimensionProperties>,\n    bounds: Partial<DimensionRangeIndexes>\n  ) {\n    // Request type = `updateDimensionProperties`\n\n    Object.keys(properties);\n\n    return this._makeSingleUpdateRequest('updateDimensionProperties', {\n      range: {\n        sheetId: this.sheetId,\n        dimension: columnsOrRows,\n        ...bounds,\n      },\n      properties,\n      fields: getFieldMask(properties as any),\n    });\n  }\n\n  // OTHER /////////////////////////////////////////////////////////////////////////////////////////\n\n  // this uses the \"values\" getter and does not give all the info about the cell contents\n  // it is used internally when loading header cells\n  async getCellsInRange(a1Range: A1Range, options?: GetValuesRequestOptions) {\n    const response = await this._spreadsheet.sheetsApi.get(`values/${this.encodedA1SheetName}!${a1Range}`, {\n      searchParams: options,\n    });\n    const data = await response.json<any>();\n    return data.values;\n  }\n\n  async batchGetCellsInRange(a1Ranges: A1Range[], options?: GetValuesRequestOptions) {\n    const ranges = a1Ranges.map((r) => `ranges=${this.encodedA1SheetName}!${r}`).join('&');\n    const response = await this._spreadsheet.sheetsApi.get(`values:batchGet?${ranges}`, {\n      searchParams: options,\n    });\n    const data = await response.json<any>();\n    return data.valueRanges.map((r: any) => r.values);\n  }\n\n  /**\n   * Updates an existing named range\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#UpdateNamedRangeRequest\n   */\n  async updateNamedRange(\n    /** ID of the named range to update */\n    namedRangeId: string,\n    /** The named range properties to update */\n    namedRange: Partial<{ name: string, range: GridRangeWithOptionalWorksheetId }>,\n    /** Field mask specifying which properties to update */\n    fields: string\n  ) {\n    return this._makeSingleUpdateRequest('updateNamedRange', {\n      namedRange: {\n        namedRangeId,\n        ...namedRange.name && { name: namedRange.name },\n        ...namedRange.range && { range: this._addSheetIdToRange(namedRange.range) },\n      },\n      fields,\n    });\n  }\n\n  /**\n   * Creates a new named range in this worksheet (convenience method that auto-fills sheetId)\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#AddNamedRangeRequest\n   */\n  async addNamedRange(\n    /** Name of the new named range */\n    name: string,\n    /** GridRange describing the range (sheetId optional, will be auto-filled) */\n    range: GridRangeWithOptionalWorksheetId,\n    /** Optional ID for the named range */\n    namedRangeId?: string\n  ) {\n    return this._spreadsheet.addNamedRange(\n      name,\n      this._addSheetIdToRange(range),\n      namedRangeId\n    );\n  }\n\n  /**\n   * Deletes a named range (convenience wrapper)\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#DeleteNamedRangeRequest\n   */\n  async deleteNamedRange(\n    /** ID of the named range to delete */\n    namedRangeId: string\n  ) {\n    return this._spreadsheet.deleteNamedRange(namedRangeId);\n  }\n\n  /**\n   * Updates all cells in a range with the same cell data\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#RepeatCellRequest\n   */\n  async repeatCell(\n    /** The range to update (sheetId optional) */\n    range: GridRangeWithOptionalWorksheetId,\n    /** The cell data to repeat across the range */\n    cell: any,\n    /** Which fields to update (use \"*\" for all fields) */\n    fields: string\n  ) {\n    await this._makeSingleUpdateRequest('repeatCell', {\n      range: this._addSheetIdToRange(range),\n      cell,\n      fields,\n    });\n  }\n\n  /**\n   * Auto-fills cells with data following a pattern (like dragging the fill handle)\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#AutoFillRequest\n   */\n  async autoFill(\n    /** The range to autofill (detects source location automatically, sheetId optional) or explicit source and destination specification */\n    rangeOrSource: GridRangeWithOptionalWorksheetId | SourceAndDestination,\n    /** Whether to generate data with the alternate series */\n    useAlternateSeries?: boolean\n  ) {\n    // Check if it's a SourceAndDestination by looking for the 'dimension' property\n    const isSourceAndDestination = 'dimension' in rangeOrSource;\n\n    await this._makeSingleUpdateRequest('autoFill', {\n      ...isSourceAndDestination\n        ? {\n          sourceAndDestination: {\n            ...rangeOrSource,\n            source: this._addSheetIdToRange((rangeOrSource as SourceAndDestination).source),\n          },\n        }\n        : { range: this._addSheetIdToRange(rangeOrSource as GridRangeWithOptionalWorksheetId) },\n      ...useAlternateSeries !== undefined && { useAlternateSeries },\n    });\n  }\n\n  /**\n   * Cuts data from a source range and pastes it to a destination coordinate\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#CutPasteRequest\n   */\n  async cutPaste(\n    /** The source range to cut from (sheetId optional) */\n    source: GridRangeWithOptionalWorksheetId,\n    /** The top-left coordinate where data should be pasted (sheetId optional) */\n    destination: GridCoordinateWithOptionalWorksheetId,\n    /** What kind of data to paste (defaults to PASTE_NORMAL) */\n    pasteType: PasteType = 'PASTE_NORMAL'\n  ) {\n    await this._makeSingleUpdateRequest('cutPaste', {\n      source: this._addSheetIdToRange(source),\n      destination: {\n        sheetId: this.sheetId,\n        rowIndex: destination.rowIndex,\n        columnIndex: destination.columnIndex,\n      },\n      pasteType,\n    });\n  }\n\n  /**\n   * Copies data from a source range and pastes it to a destination range\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#CopyPasteRequest\n   */\n  async copyPaste(\n    /** The source range to copy from (sheetId optional) */\n    source: GridRangeWithOptionalWorksheetId,\n    /** The destination range to paste to (sheetId optional) */\n    destination: GridRangeWithOptionalWorksheetId,\n    /** What kind of data to paste (defaults to PASTE_NORMAL) */\n    pasteType: PasteType = 'PASTE_NORMAL',\n    /** How data should be oriented (defaults to NORMAL) */\n    pasteOrientation: PasteOrientation = 'NORMAL'\n  ) {\n    await this._makeSingleUpdateRequest('copyPaste', {\n      source: this._addSheetIdToRange(source),\n      destination: this._addSheetIdToRange(destination),\n      pasteType,\n      pasteOrientation,\n    });\n  }\n\n  // TODO: check types on these ranges\n\n  /**\n   * Merges all cells in the range\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#MergeCellsRequest\n   */\n  async mergeCells(\n    range: GridRangeWithOptionalWorksheetId,\n    mergeType = 'MERGE_ALL'\n  ) {\n    await this._makeSingleUpdateRequest('mergeCells', {\n      mergeType,\n      range: this._addSheetIdToRange(range),\n    });\n  }\n\n  /**\n   * Unmerges cells in the given range\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#UnmergeCellsRequest\n   */\n  async unmergeCells(\n    range: GridRangeWithOptionalWorksheetId\n  ) {\n    await this._makeSingleUpdateRequest('unmergeCells', {\n      range: this._addSheetIdToRange(range),\n    });\n  }\n\n  /**\n   * Updates borders for a range\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#UpdateBordersRequest\n   */\n  async updateBorders(\n    /** The range whose borders should be updated (sheetId optional) */\n    range: GridRangeWithOptionalWorksheetId,\n    /** Border styles for top, bottom, left, right, innerHorizontal, innerVertical */\n    borders: {\n      top?: any,\n      bottom?: any,\n      left?: any,\n      right?: any,\n      innerHorizontal?: any,\n      innerVertical?: any\n    }\n  ) {\n    await this._makeSingleUpdateRequest('updateBorders', {\n      range: this._addSheetIdToRange(range),\n      ...borders,\n    });\n  }\n\n  /**\n   * Adds a filter view to the sheet\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#AddFilterViewRequest\n   */\n  async addFilterView(\n    /** The filter view to add (filterViewId is optional and will be auto-generated if not provided) */\n    filter: FilterView\n  ) {\n    return this._makeSingleUpdateRequest('addFilterView', {\n      filter,\n    });\n  }\n\n  /**\n   * Appends cells after the last row with data in a sheet\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#AppendCellsRequest\n   */\n  async appendCells(\n    /** The row data to append */\n    rows: any[],\n    /** Which fields to update (use \"*\" for all fields) */\n    fields: string\n  ) {\n    await this._makeSingleUpdateRequest('appendCells', {\n      sheetId: this.sheetId,\n      rows,\n      fields,\n    });\n  }\n\n  /**\n   * Clears the basic filter on this sheet\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#ClearBasicFilterRequest\n   */\n  async clearBasicFilter() {\n    await this._makeSingleUpdateRequest('clearBasicFilter', {\n      sheetId: this.sheetId,\n    });\n  }\n\n  /**\n   * Delete rows or columns in a given range\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#DeleteDimensionRequest\n   */\n  async deleteDimension(\n    columnsOrRows: WorksheetDimension,\n    rangeIndexes: DimensionRangeIndexes\n  ) {\n    if (!columnsOrRows) throw new Error('You need to specify a dimension. i.e. COLUMNS|ROWS');\n    if (!_.isObject(rangeIndexes)) throw new Error('`range` must be an object containing `startIndex` and `endIndex`');\n    if (!_.isInteger(rangeIndexes.startIndex) || rangeIndexes.startIndex < 0) throw new Error('range.startIndex must be an integer >=0');\n    if (!_.isInteger(rangeIndexes.endIndex) || rangeIndexes.endIndex < 0) throw new Error('range.endIndex must be an integer >=0');\n    if (rangeIndexes.endIndex <= rangeIndexes.startIndex) throw new Error('range.endIndex must be greater than range.startIndex');\n\n    const result = await this._makeSingleUpdateRequest('deleteDimension', {\n      range: {\n        sheetId: this.sheetId,\n        dimension: columnsOrRows,\n        startIndex: rangeIndexes.startIndex,\n        endIndex: rangeIndexes.endIndex,\n      },\n    });\n\n    // Update cached rows and cells\n    if (columnsOrRows === 'ROWS') {\n      this._shiftRowCacheBulk(rangeIndexes.startIndex, rangeIndexes.endIndex);\n      this._shiftCellCacheRows(rangeIndexes.startIndex, rangeIndexes.endIndex);\n    } else {\n      this._shiftCellCacheColumns(rangeIndexes.startIndex, rangeIndexes.endIndex);\n    }\n\n    return result;\n  }\n\n  /**\n   * Delete rows by index\n   */\n  async deleteRows(\n    /** the start row index (inclusive, 0-based) */\n    startIndex: number,\n    /** the end row index (exclusive) */\n    endIndex: number\n  ) {\n    return this.deleteDimension('ROWS', { startIndex, endIndex });\n  }\n\n  /**\n   * Delete columns by index\n   */\n  async deleteColumns(\n    /** the start column index (inclusive, 0-based) */\n    startIndex: number,\n    /** the end column index (exclusive) */\n    endIndex: number\n  ) {\n    return this.deleteDimension('COLUMNS', { startIndex, endIndex });\n  }\n\n  async deleteEmbeddedObject() {\n    // Request type = `deleteEmbeddedObject`\n    // https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#DeleteEmbeddedObjectRequest\n    throw new Error('Not implemented yet');\n  }\n\n  /**\n   * Deletes a filter view from the sheet\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#DeleteFilterViewRequest\n   */\n  async deleteFilterView(\n    /** The ID of the filter view to delete */\n    filterId: Integer\n  ) {\n    await this._makeSingleUpdateRequest('deleteFilterView', {\n      filterId,\n    });\n  }\n\n  /**\n   * Duplicates a filter view\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#DuplicateFilterViewRequest\n   */\n  async duplicateFilterView(\n    /** The ID of the filter view to duplicate */\n    filterId: Integer\n  ) {\n    await this._makeSingleUpdateRequest('duplicateFilterView', {\n      filterId,\n    });\n  }\n\n  /**\n   * Duplicate worksheet within the document\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#DuplicateSheetRequest\n   */\n  async duplicate(\n    options?: {\n      id?: WorksheetId,\n      title?: string,\n      index?: number,\n    }\n  ) {\n    const response = await this._makeSingleUpdateRequest('duplicateSheet', {\n      sourceSheetId: this.sheetId,\n      ...options?.index !== undefined && { insertSheetIndex: options.index },\n      ...options?.id && { newSheetId: options.id },\n      ...options?.title && { newSheetName: options.title },\n    });\n    const newSheetId = response.properties.sheetId;\n    return this._spreadsheet.sheetsById[newSheetId];\n  }\n\n  /**\n   * Finds and replaces text in cells\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#FindReplaceRequest\n   */\n  async findReplace(\n    /** The value to search for */\n    find: string,\n    /** The value to use as replacement */\n    replacement: string,\n    /** Search options (matchCase, matchEntireCell, searchByRegex, includeFormulas) */\n    options?: {\n      matchCase?: boolean,\n      matchEntireCell?: boolean,\n      searchByRegex?: boolean,\n      includeFormulas?: boolean\n    },\n    /** Optional range to search in (defaults to entire sheet, sheetId optional) */\n    range?: GridRangeWithOptionalWorksheetId\n  ) {\n    await this._makeSingleUpdateRequest('findReplace', {\n      find,\n      replacement,\n      ...options,\n      ...range\n        ? { range: this._addSheetIdToRange(range) }\n        : { sheetId: this.sheetId },\n    });\n  }\n\n  /**\n   * Inserts rows or columns at a particular index\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#InsertDimensionRequest\n   */\n  async insertDimension(\n    columnsOrRows: WorksheetDimension,\n    rangeIndexes: DimensionRangeIndexes,\n    inheritFromBefore?: boolean\n  ) {\n    if (!columnsOrRows) throw new Error('You need to specify a dimension. i.e. COLUMNS|ROWS');\n    if (!_.isObject(rangeIndexes)) throw new Error('`range` must be an object containing `startIndex` and `endIndex`');\n    if (!_.isInteger(rangeIndexes.startIndex) || rangeIndexes.startIndex < 0) throw new Error('range.startIndex must be an integer >=0');\n    if (!_.isInteger(rangeIndexes.endIndex) || rangeIndexes.endIndex < 0) throw new Error('range.endIndex must be an integer >=0');\n    if (rangeIndexes.endIndex <= rangeIndexes.startIndex) throw new Error('range.endIndex must be greater than range.startIndex');\n\n    // default inheritFromBefore to true - unless inserting in the first row/column\n    if (inheritFromBefore === undefined) {\n      inheritFromBefore = rangeIndexes.startIndex > 0;\n    }\n\n    // do not allow inheritFromBefore if inserting at first row/column\n    if (inheritFromBefore && rangeIndexes.startIndex === 0) {\n      throw new Error('Cannot set inheritFromBefore to true if inserting in first row/column');\n    }\n\n    return this._makeSingleUpdateRequest('insertDimension', {\n      range: {\n        sheetId: this.sheetId,\n        dimension: columnsOrRows,\n        startIndex: rangeIndexes.startIndex,\n        endIndex: rangeIndexes.endIndex,\n      },\n      inheritFromBefore,\n    });\n  }\n\n  /**\n   * insert empty cells in a range, shifting existing cells in the specified direction\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#InsertRangeRequest\n   */\n  async insertRange(\n    /** the range to insert new cells into */\n    range: GridRangeWithOptionalWorksheetId,\n    /** which direction to shift existing cells - ROWS (shift down) or COLUMNS (shift right) */\n    shiftDimension: WorksheetDimension\n  ) {\n    await this._makeSingleUpdateRequest('insertRange', {\n      range: this._addSheetIdToRange(range),\n      shiftDimension,\n    });\n  }\n\n  /**\n   * Moves rows or columns to a different position within the sheet\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#MoveDimensionRequest\n   */\n  async moveDimension(\n    /** Whether to move rows or columns */\n    dimension: WorksheetDimension,\n    /** The indexes of rows/columns to move */\n    source: DimensionRangeIndexes,\n    /** Where to move them (calculated before removal) */\n    destinationIndex: number\n  ) {\n    await this._makeSingleUpdateRequest('moveDimension', {\n      source: {\n        sheetId: this.sheetId,\n        dimension,\n        startIndex: source.startIndex,\n        endIndex: source.endIndex,\n      },\n      destinationIndex,\n    });\n  }\n\n  async updateEmbeddedObjectPosition() {\n    // Request type = `updateEmbeddedObjectPosition`\n    // https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#UpdateEmbeddedObjectPositionRequest\n    throw new Error('Not implemented yet');\n  }\n\n  /**\n   * Inserts data into the spreadsheet starting at the specified coordinate\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#PasteDataRequest\n   */\n  async pasteData(\n    /** The coordinate at which the data should start being inserted (sheetId optional) */\n    coordinate: GridCoordinateWithOptionalWorksheetId,\n    /** The data to insert */\n    data: string,\n    /** The delimiter in the data */\n    delimiter: string,\n    /** How the data should be pasted (defaults to PASTE_NORMAL) */\n    type: PasteType = 'PASTE_NORMAL'\n  ) {\n    await this._makeSingleUpdateRequest('pasteData', {\n      coordinate: {\n        sheetId: this.sheetId,\n        rowIndex: coordinate.rowIndex,\n        columnIndex: coordinate.columnIndex,\n      },\n      data,\n      delimiter,\n      type,\n    });\n  }\n\n  /**\n   * Splits a column of text into multiple columns based on a delimiter\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#TextToColumnsRequest\n   */\n  async textToColumns(\n    /** The column to split (must span exactly one column) */\n    source: GridRangeWithOptionalWorksheetId,\n    /** Type of delimiter to use */\n    delimiterType: DelimiterType,\n    /** Custom delimiter character (only used when delimiterType is CUSTOM) */\n    delimiter?: string\n  ) {\n    await this._makeSingleUpdateRequest('textToColumns', {\n      source: this._addSheetIdToRange(source),\n      delimiterType,\n      ...delimiter && { delimiter },\n    });\n  }\n\n  /**\n   * Updates properties of a filter view\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#UpdateFilterViewRequest\n   */\n  async updateFilterView(\n    /** The new properties of the filter view */\n    filter: FilterView,\n    /** The fields that should be updated (use \"*\" to update all fields) */\n    fields: string\n  ) {\n    await this._makeSingleUpdateRequest('updateFilterView', {\n      filter,\n      fields,\n    });\n  }\n\n  /**\n   * Deletes a range of cells and shifts remaining cells\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#DeleteRangeRequest\n   */\n  async deleteRange(\n    /** The range of cells to delete (sheetId optional) */\n    range: GridRangeWithOptionalWorksheetId,\n    /** How remaining cells should shift (ROWS = up, COLUMNS = left) */\n    shiftDimension: WorksheetDimension\n  ) {\n    await this._makeSingleUpdateRequest('deleteRange', {\n      range: this._addSheetIdToRange(range),\n      shiftDimension,\n    });\n  }\n\n  /**\n   * Appends rows or columns to the end of a sheet\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#AppendDimensionRequest\n   */\n  async appendDimension(\n    /** Whether rows or columns should be appended */\n    dimension: WorksheetDimension,\n    /** The number of rows or columns to append */\n    length: number\n  ) {\n    await this._makeSingleUpdateRequest('appendDimension', {\n      sheetId: this.sheetId,\n      dimension,\n      length,\n    });\n  }\n\n  /**\n   * Adds a new conditional formatting rule at the given index\n   * All subsequent rules' indexes are incremented\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#AddConditionalFormatRuleRequest\n   */\n  async addConditionalFormatRule(\n    /** The rule to add */\n    rule: ConditionalFormatRule,\n    /** The zero-based index where the rule should be inserted */\n    index: Integer\n  ) {\n    await this._makeSingleUpdateRequest('addConditionalFormatRule', {\n      rule,\n      index,\n    });\n  }\n\n  /**\n   * Updates a conditional format rule at the given index, or moves a conditional format rule to another index\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#UpdateConditionalFormatRuleRequest\n   */\n  async updateConditionalFormatRule(\n    /** Either provide `rule` to replace the rule, or `newIndex` and `sheetId` to move it */\n    options: {\n      /** The zero-based index of the rule */\n      index: Integer;\n      /** The rule that should replace the rule at the given index (mutually exclusive with newIndex) */\n      rule?: ConditionalFormatRule;\n      /** The zero-based new index the rule should end up at (mutually exclusive with rule, requires sheetId) */\n      newIndex?: Integer;\n      /** The sheet of the rule to move (required if newIndex is set) */\n      sheetId?: WorksheetId;\n    }\n  ) {\n    await this._makeSingleUpdateRequest('updateConditionalFormatRule', options);\n  }\n\n  /**\n   * Deletes a conditional format rule at the given index\n   * All subsequent rules' indexes are decremented\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#DeleteConditionalFormatRuleRequest\n   */\n  async deleteConditionalFormatRule(\n    /** The zero-based index of the rule to be deleted */\n    index: Integer,\n    /** The sheet the rule is being deleted from (defaults to this sheet) */\n    sheetId?: WorksheetId\n  ) {\n    await this._makeSingleUpdateRequest('deleteConditionalFormatRule', {\n      index,\n      sheetId: sheetId ?? this.sheetId,\n    });\n  }\n\n  /**\n   * Sorts data in rows based on sort order per column\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#SortRangeRequest\n   */\n  async sortRange(\n    /** The range to sort (sheetId optional) */\n    range: GridRangeWithOptionalWorksheetId,\n    /** Array of sort specifications (later specs used when values are equal) */\n    sortSpecs: SortSpec[]\n  ) {\n    await this._makeSingleUpdateRequest('sortRange', {\n      range: this._addSheetIdToRange(range),\n      sortSpecs,\n    });\n  }\n\n  /**\n   * Sets (or unsets) a data validation rule to every cell in the range\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#SetDataValidationRequest\n   */\n  async setDataValidation(\n    range: GridRangeWithOptionalWorksheetId,\n    /** data validation rule object, or set to false to clear an existing rule */\n    rule: DataValidationRule | false\n  ) {\n    return this._makeSingleUpdateRequest('setDataValidation', {\n      range: {\n        sheetId: this.sheetId,\n        ...range,\n      },\n      ...rule && { rule },\n    });\n  }\n\n  /**\n   * Sets the basic filter on this sheet\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#SetBasicFilterRequest\n   */\n  async setBasicFilter(\n    /** The basic filter configuration (range will auto-fill sheetId if not provided) */\n    filter: {\n      range?: GridRangeWithOptionalWorksheetId,\n      sortSpecs?: SortSpec[],\n      filterSpecs?: any[]\n    }\n  ) {\n    await this._makeSingleUpdateRequest('setBasicFilter', {\n      filter: {\n        ...filter,\n        ...filter.range && { range: this._addSheetIdToRange(filter.range) },\n      },\n    });\n  }\n\n  /**\n   * add a new protected range to the sheet\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#AddProtectedRangeRequest\n   */\n  async addProtectedRange(\n    protectedRange: ProtectedRange\n  ) {\n    if (!protectedRange.range && !protectedRange.namedRangeId) {\n      throw new Error('No range specified: either range or namedRangeId is required');\n    }\n    return this._makeSingleUpdateRequest('addProtectedRange', {\n      protectedRange,\n    });\n  }\n\n  /**\n   * update an existing protected range\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#UpdateProtectedRangeRequest\n   */\n  async updateProtectedRange(\n    protectedRangeId: Integer,\n    protectedRange: Partial<ProtectedRange>\n  ) {\n    return this._makeSingleUpdateRequest('updateProtectedRange', {\n      protectedRange: { protectedRangeId, ...protectedRange },\n      fields: getFieldMask(protectedRange as Record<string, unknown>),\n    });\n  }\n\n  /**\n   * delete a protected range by ID\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#DeleteProtectedRangeRequest\n   */\n  async deleteProtectedRange(\n    protectedRangeId: Integer\n  ) {\n    return this._makeSingleUpdateRequest('deleteProtectedRange', {\n      protectedRangeId,\n    });\n  }\n\n  /**\n   * auto-resize rows or columns to fit their contents\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#AutoResizeDimensionsRequest\n   */\n  async autoResizeDimensions(\n    /** which dimension to auto-resize */\n    columnsOrRows: WorksheetDimension,\n    /** start and end indexes (optional, defaults to all) */\n    rangeIndexes?: DimensionRangeIndexes\n  ) {\n    return this._makeSingleUpdateRequest('autoResizeDimensions', {\n      dimensions: {\n        sheetId: this.sheetId,\n        dimension: columnsOrRows,\n        ...rangeIndexes,\n      },\n    });\n  }\n\n  async addChart() {\n    // Request type = `addChart`\n    // https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#AddChartRequest\n    throw new Error('Not implemented yet');\n  }\n\n  async updateChartSpec() {\n    // Request type = `updateChartSpec`\n    // https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#UpdateChartSpecRequest\n    throw new Error('Not implemented yet');\n  }\n\n  /**\n   * Updates properties of a banded range\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#UpdateBandingRequest\n   */\n  async updateBanding(\n    /** The banded range to update with the new properties */\n    bandedRange: BandedRange,\n    /** The fields that should be updated (use \"*\" to update all fields) */\n    fields: string\n  ) {\n    await this._makeSingleUpdateRequest('updateBanding', {\n      bandedRange,\n      fields,\n    });\n  }\n\n  /**\n   * Adds a new banded range to the sheet\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#AddBandingRequest\n   */\n  async addBanding(\n    /** The banded range to add (bandedRangeId is optional and will be auto-generated if not provided) */\n    bandedRange: BandedRange\n  ) {\n    return this._makeSingleUpdateRequest('addBanding', {\n      bandedRange,\n    });\n  }\n\n  /**\n   * Deletes a banded range from the sheet\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#DeleteBandingRequest\n   */\n  async deleteBanding(\n    /** The ID of the banded range to delete */\n    bandedRangeId: Integer\n  ) {\n    await this._makeSingleUpdateRequest('deleteBanding', {\n      bandedRangeId,\n    });\n  }\n\n  /**\n   * Creates developer metadata\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#CreateDeveloperMetadataRequest\n   */\n  async createDeveloperMetadata(\n    /** The developer metadata to create */\n    developerMetadata: DeveloperMetadata\n  ) {\n    return this._makeSingleUpdateRequest('createDeveloperMetadata', {\n      developerMetadata,\n    });\n  }\n\n  /**\n   * Updates developer metadata that matches the specified filters\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#UpdateDeveloperMetadataRequest\n   */\n  async updateDeveloperMetadata(\n    /** The filters matching the developer metadata entries to update */\n    dataFilters: DataFilterObject[],\n    /** The value that all metadata matched by the filters will be updated to */\n    developerMetadata: DeveloperMetadata,\n    /** The fields that should be updated (use \"*\" to update all fields) */\n    fields: string\n  ) {\n    await this._makeSingleUpdateRequest('updateDeveloperMetadata', {\n      dataFilters,\n      developerMetadata,\n      fields,\n    });\n  }\n\n  /**\n   * Deletes developer metadata that matches the specified filter\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#DeleteDeveloperMetadataRequest\n   */\n  async deleteDeveloperMetadata(\n    /** The filter describing the criteria used to select which developer metadata to delete */\n    dataFilter: DataFilterObject\n  ) {\n    await this._makeSingleUpdateRequest('deleteDeveloperMetadata', {\n      dataFilter,\n    });\n  }\n\n  /**\n   * Randomizes the order of rows in a range\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#RandomizeRangeRequest\n   */\n  async randomizeRange(\n    /** The range to randomize (sheetId optional) */\n    range: GridRangeWithOptionalWorksheetId\n  ) {\n    await this._makeSingleUpdateRequest('randomizeRange', {\n      range: this._addSheetIdToRange(range),\n    });\n  }\n\n  async addDimensionGroup() {\n    // Request type = `addDimensionGroup`\n    // https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#AddDimensionGroupRequest\n    throw new Error('Not implemented yet');\n  }\n\n  async deleteDimensionGroup() {\n    // Request type = `deleteDimensionGroup`\n    // https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#DeleteDimensionGroupRequest\n    throw new Error('Not implemented yet');\n  }\n\n  async updateDimensionGroup() {\n    // Request type = `updateDimensionGroup`\n    // https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#UpdateDimensionGroupRequest\n    throw new Error('Not implemented yet');\n  }\n\n  /**\n   * Trims whitespace from the start and end of each cell's text\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#TrimWhitespaceRequest\n   */\n  async trimWhitespace(\n    /** The range whose cells to trim (sheetId optional) */\n    range: GridRangeWithOptionalWorksheetId\n  ) {\n    await this._makeSingleUpdateRequest('trimWhitespace', {\n      range: this._addSheetIdToRange(range),\n    });\n  }\n\n  /**\n   * Removes duplicate rows from a range based on specified columns\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#DeleteDuplicatesRequest\n   */\n  async deleteDuplicates(\n    /** The range to remove duplicates from (sheetId optional) */\n    range: GridRangeWithOptionalWorksheetId,\n    /** Columns to check for duplicates (if empty, all columns are used) */\n    comparisonColumns?: DimensionRange[]\n  ) {\n    await this._makeSingleUpdateRequest('deleteDuplicates', {\n      range: this._addSheetIdToRange(range),\n      ...comparisonColumns && { comparisonColumns },\n    });\n  }\n\n  async addSlicer() {\n    // Request type = `addSlicer`\n    // https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#AddSlicerRequest\n    throw new Error('Not implemented yet');\n  }\n\n  async updateSlicerSpec() {\n    // Request type = `updateSlicerSpec`\n    // https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#UpdateSlicerSpecRequest\n    throw new Error('Not implemented yet');\n  }\n\n  /**\n   * delete this worksheet\n   */\n  async delete() {\n    return this._spreadsheet.deleteSheet(this.sheetId);\n  }\n\n  /**\n   * copies this worksheet into another document/spreadsheet\n   *\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.sheets/copyTo\n   */\n  async copyToSpreadsheet(\n    destinationSpreadsheetId: SpreadsheetId\n  ) {\n    const req = this._spreadsheet.sheetsApi.post(`sheets/${this.sheetId}:copyTo`, {\n      json: {\n        destinationSpreadsheetId,\n      },\n    });\n    const data = await req.json<any>();\n    return data;\n  }\n\n  /**\n   * clear data in the sheet - either the entire sheet or a specific range\n   */\n  async clear(\n    /** optional A1 range to clear - defaults to entire sheet  */\n    a1Range?: A1Range\n  ) {\n    const range = a1Range ? `!${a1Range}` : '';\n    // sheet name without ie 'sheet1' rather than 'sheet1'!A1:B5 is all cells\n    await this._spreadsheet.sheetsApi.post(`values/${this.encodedA1SheetName}${range}:clear`);\n    this.resetLocalCache(true);\n  }\n\n  /**\n   * exports worksheet as CSV file (comma-separated values)\n   */\n  async downloadAsCSV(): Promise<ArrayBuffer>;\n  async downloadAsCSV(returnStreamInsteadOfBuffer: false): Promise<ArrayBuffer>;\n  async downloadAsCSV(returnStreamInsteadOfBuffer: true): Promise<ReadableStream>;\n  async downloadAsCSV(returnStreamInsteadOfBuffer = false) {\n    return this._spreadsheet._downloadAs('csv', this.sheetId, returnStreamInsteadOfBuffer);\n  }\n  /**\n   * exports worksheet as TSC file (tab-separated values)\n   */\n  async downloadAsTSV(): Promise<ArrayBuffer>;\n  async downloadAsTSV(returnStreamInsteadOfBuffer: false): Promise<ArrayBuffer>;\n  async downloadAsTSV(returnStreamInsteadOfBuffer: true): Promise<ReadableStream>;\n  async downloadAsTSV(returnStreamInsteadOfBuffer = false) {\n    return this._spreadsheet._downloadAs('tsv', this.sheetId, returnStreamInsteadOfBuffer);\n  }\n  /**\n   * exports worksheet as PDF\n   */\n  async downloadAsPDF(): Promise<ArrayBuffer>;\n  async downloadAsPDF(returnStreamInsteadOfBuffer: false): Promise<ArrayBuffer>;\n  async downloadAsPDF(returnStreamInsteadOfBuffer: true): Promise<ReadableStream>;\n  async downloadAsPDF(returnStreamInsteadOfBuffer = false) {\n    return this._spreadsheet._downloadAs('pdf', this.sheetId, returnStreamInsteadOfBuffer);\n  }\n}\n","/** single type to handle all valid auth types */\nexport type GoogleApiAuth =\n  // this simple interface should cover all google-auth-library auth methods\n  | { getRequestHeaders: () => Promise<any> }\n  // used to pass in an API key only\n  | { apiKey: string }\n  // used to pass in a raw token\n  | { token: string };\n\nexport enum AUTH_MODES {\n  GOOGLE_AUTH_CLIENT = 'google_auth',\n  RAW_ACCESS_TOKEN = 'raw_access_token',\n  API_KEY = 'api_key'\n}\n","import ky, { HTTPError, KyInstance, RetryOptions } from 'ky'; // eslint-disable-line import/no-extraneous-dependencies\nimport * as _ from './toolkit';\nimport { GoogleSpreadsheetWorksheet } from './GoogleSpreadsheetWorksheet';\nimport { getFieldMask } from './utils';\nimport {\n  DataFilter,\n  DataFilterObject,\n  DeveloperMetadata,\n  GridRange,\n  NamedRangeId,\n  ProtectedRange,\n  SpreadsheetId,\n  SpreadsheetProperties,\n  WorksheetId,\n  WorksheetProperties,\n} from './types/sheets-types';\nimport { PermissionRoles, PermissionsList, PublicPermissionRoles } from './types/drive-types';\nimport { RecursivePartial } from './types/util-types';\nimport { AUTH_MODES, GoogleApiAuth } from './types/auth-types';\n\n\nconst SHEETS_API_BASE_URL = 'https://sheets.googleapis.com/v4/spreadsheets';\nconst DRIVE_API_BASE_URL = 'https://www.googleapis.com/drive/v3/files';\n\nconst EXPORT_CONFIG: Record<string, { singleWorksheet?: boolean }> = {\n  html: {},\n  zip: {},\n  xlsx: {},\n  ods: {},\n  csv: { singleWorksheet: true },\n  tsv: { singleWorksheet: true },\n  pdf: { singleWorksheet: true },\n};\ntype ExportFileTypes = keyof typeof EXPORT_CONFIG;\n\n\n\n\nfunction getAuthMode(auth: GoogleApiAuth) {\n  if ('getRequestHeaders' in auth) return AUTH_MODES.GOOGLE_AUTH_CLIENT;\n  if ('token' in auth && auth.token) return AUTH_MODES.RAW_ACCESS_TOKEN;\n  // google-auth-library now has an empty `apiKey` property\n  if ('apiKey' in auth && auth.apiKey) return AUTH_MODES.API_KEY;\n  throw new Error('Invalid auth');\n}\n\nasync function getRequestAuthConfig(auth: GoogleApiAuth): Promise<{\n  headers?: Record<string, string>;\n  searchParams?: Record<string, string>\n}> {\n  // google-auth-libary methods all can call this method to get the right headers\n  // JWT | OAuth2Client | GoogleAuth | Impersonate | AuthClient\n  if ('getRequestHeaders' in auth) {\n    const headers = await auth.getRequestHeaders();\n\n    // google-auth-library v10 uses a Headers object rather than a plain object\n    if ('entries' in headers) {\n      return { headers: Object.fromEntries(headers.entries()) };\n    } if (_.isObject(headers)) {\n      return { headers: headers as Record<string, string> };\n    }\n    throw new Error('unexpected headers returned from getRequestHeaders');\n  }\n\n  // API key only access passes through the api key as a query param\n  // (note this can only provide read-only access)\n  if ('apiKey' in auth && auth.apiKey) {\n    return { searchParams: { key: auth.apiKey } };\n  }\n\n  // RAW ACCESS TOKEN\n  if ('token' in auth && auth.token) {\n    return { headers: { Authorization: `Bearer ${auth.token}` } };\n  }\n\n  throw new Error('Invalid auth');\n}\n\n/**\n * Google Sheets document\n *\n * @description\n * **This class represents an entire google spreadsheet document**\n * Provides methods to interact with document metadata/settings, formatting, manage sheets, and acts as the main gateway to interacting with sheets and data that the document contains.q\n *\n */\nexport class GoogleSpreadsheet {\n  readonly spreadsheetId: string;\n\n  public auth: GoogleApiAuth;\n  get authMode() {\n    return getAuthMode(this.auth);\n  }\n\n  private _rawSheets: any;\n  private _rawProperties = null as SpreadsheetProperties | null;\n  private _spreadsheetUrl = null as string | null;\n  private _deleted = false;\n\n  /**\n   * Sheets API [ky](https://github.com/sindresorhus/ky?tab=readme-ov-file#kycreatedefaultoptions) instance\n   * authentication is automatically attached\n   * can be used if unsupported sheets calls need to be made\n   * @see https://developers.google.com/sheets/api/reference/rest\n   * */\n  readonly sheetsApi: KyInstance;\n\n  /**\n   * Drive API [ky](https://github.com/sindresorhus/ky?tab=readme-ov-file#kycreatedefaultoptions) instance\n   * authentication automatically attached\n   * can be used if unsupported drive calls need to be made\n   * @topic permissions\n   * @see https://developers.google.com/drive/api/v3/reference\n   * */\n  readonly driveApi: KyInstance;\n\n\n  /**\n   * initialize new GoogleSpreadsheet\n   * @category Initialization\n   * */\n  constructor(\n    /** id of Google spreadsheet doc */\n    spreadsheetId: SpreadsheetId,\n    /** authentication to use with Google Sheets API */\n    auth: GoogleApiAuth,\n    /** Additional options */\n    options?: {\n      /**\n       * customize retry behavior --\n       * see the [ky documentation](https://github.com/sindresorhus/ky#retry) for details of the available options and defaults.\n       * */\n      retryConfig?: RetryOptions | number\n    }\n  ) {\n    const { retryConfig } = options || {};\n    this.spreadsheetId = spreadsheetId;\n    this.auth = auth;\n\n    this._rawSheets = {};\n    this._spreadsheetUrl = null;\n\n    // create a ky instance with sheet root URL and hooks to handle auth\n    this.sheetsApi = ky.create({\n      prefix: `${SHEETS_API_BASE_URL}/${spreadsheetId}`,\n      timeout: 180_000,\n      hooks: {\n        beforeRequest: [\n          ({ request }) => this._setAuthRequestHook(request),\n        ],\n        beforeError: [\n          ({ error }) => this._errorHook(error),\n        ],\n      },\n      retry: retryConfig,\n    });\n    this.driveApi = ky.create({\n      prefix: `${DRIVE_API_BASE_URL}/${spreadsheetId}`,\n      hooks: {\n        beforeRequest: [({ request }) => this._setAuthRequestHook(request)],\n        beforeError: [({ error }) => this._errorHook(error)],\n      },\n      retry: retryConfig,\n    });\n  }\n\n\n  // INTERNAL UTILITY FUNCTIONS ////////////////////////////////////////////////////////////////////\n\n  /** @internal */\n  async _setAuthRequestHook(req: Request): Promise<Request> {\n    const authConfig = await getRequestAuthConfig(this.auth);\n    if (authConfig.headers) {\n      Object.entries(authConfig.headers).forEach(([key, val]) => {\n        req.headers.set(key, String(val));\n      });\n    }\n\n    if (authConfig.searchParams) {\n      const url = new URL(req.url);\n      Object.entries(authConfig.searchParams).forEach(([key, val]) => {\n        url.searchParams.set(key, String(val));\n      });\n      // cannot change the URL with ky, so have to return a new request\n      return new Request(url, req);\n    }\n\n    return req;\n  }\n\n  /** @internal */\n  async _errorHook(error: Error) {\n    if (!(error instanceof HTTPError)) return error;\n\n    // ky pre-parses the response body into error.data (the response body is already consumed)\n    const errorData = typeof error.data === 'string' ? (() => {\n      try { return JSON.parse(error.data as string); } catch { return undefined; }\n    })() : error.data;\n\n    if (errorData?.error) {\n      const { code, message } = errorData.error;\n      error.message = `Google API error - [${code}] ${message}`;\n      return error;\n    }\n\n    if (error.response?.status === 403) {\n      if ('apiKey' in this.auth) {\n        throw new Error('Sheet is private. Use authentication or make public. (see https://github.com/theoephraim/node-google-spreadsheet#a-note-on-authentication for details)');\n      }\n    }\n    return error;\n  }\n\n  /** @internal */\n  async _makeSingleUpdateRequest(requestType: string, requestParams: any) {\n    const response = await this.sheetsApi.post(':batchUpdate', {\n      json: {\n        requests: [{ [requestType]: requestParams }],\n        includeSpreadsheetInResponse: true,\n        // responseRanges: [string]\n        // responseIncludeGridData: true\n      },\n    });\n    const data = await response.json<any>();\n\n    this._updateRawProperties(data.updatedSpreadsheet.properties);\n    _.each(data.updatedSpreadsheet.sheets, (s: any) => this._updateOrCreateSheet(s));\n    // console.log('API RESPONSE', response.data.replies[0][requestType]);\n    return data.replies[0][requestType];\n  }\n\n  // TODO: review these types\n  // currently only used in batching cell updates\n  /** @internal */\n  async _makeBatchUpdateRequest(requests: any[], responseRanges?: string | string[]) {\n    // this is used for updating batches of cells\n    const response = await this.sheetsApi.post(':batchUpdate', {\n      json: {\n        requests,\n        includeSpreadsheetInResponse: true,\n        ...responseRanges && {\n          responseIncludeGridData: true,\n          ...responseRanges !== '*' && { responseRanges },\n        },\n      },\n    });\n\n    const data = await response.json<any>();\n    this._updateRawProperties(data.updatedSpreadsheet.properties);\n    _.each(data.updatedSpreadsheet.sheets, (s: any) => this._updateOrCreateSheet(s));\n  }\n\n  /** @internal */\n  _ensureInfoLoaded() {\n    if (!this._rawProperties) throw new Error('You must call `doc.loadInfo()` before accessing this property');\n  }\n\n  /** @internal */\n  _updateRawProperties(newProperties: SpreadsheetProperties) { this._rawProperties = newProperties; }\n\n  /** @internal */\n  _updateOrCreateSheet(sheetInfo: { properties: WorksheetProperties, data: any, protectedRanges?: ProtectedRange[] }) {\n    const { properties, data, protectedRanges } = sheetInfo;\n    const { sheetId } = properties;\n    if (!this._rawSheets[sheetId]) {\n      this._rawSheets[sheetId] = new GoogleSpreadsheetWorksheet(this, properties, data, protectedRanges);\n    } else {\n      this._rawSheets[sheetId].updateRawData(properties, data, protectedRanges);\n    }\n  }\n\n  // BASIC PROPS //////////////////////////////////////////////////////////////////////////////\n  _getProp(param: keyof SpreadsheetProperties) {\n    this._ensureInfoLoaded();\n    // ideally ensureInfoLoaded would assert that _rawProperties is in fact loaded\n    // but this is not currently possible in TS - see https://github.com/microsoft/TypeScript/issues/49709\n    return this._rawProperties![param];\n  }\n\n  get title(): SpreadsheetProperties['title'] { return this._getProp('title'); }\n  get locale(): SpreadsheetProperties['locale'] { return this._getProp('locale'); }\n  get timeZone(): SpreadsheetProperties['timeZone'] { return this._getProp('timeZone'); }\n  get autoRecalc(): SpreadsheetProperties['autoRecalc'] { return this._getProp('autoRecalc'); }\n  get defaultFormat(): SpreadsheetProperties['defaultFormat'] { return this._getProp('defaultFormat'); }\n  get spreadsheetTheme(): SpreadsheetProperties['spreadsheetTheme'] { return this._getProp('spreadsheetTheme'); }\n  get iterativeCalculationSettings(): SpreadsheetProperties['iterativeCalculationSettings'] { return this._getProp('iterativeCalculationSettings'); }\n\n  /**\n   * update spreadsheet properties\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets#SpreadsheetProperties\n   * */\n  async updateProperties(properties: Partial<SpreadsheetProperties>) {\n    await this._makeSingleUpdateRequest('updateSpreadsheetProperties', {\n      properties,\n      fields: getFieldMask(properties),\n    });\n  }\n\n  // BASIC INFO ////////////////////////////////////////////////////////////////////////////////////\n  async loadInfo(includeCells = false) {\n    const response = await this.sheetsApi.get('', {\n      searchParams: {\n        ...includeCells && { includeGridData: true },\n      },\n    });\n    const data = await response.json<any>();\n    this._spreadsheetUrl = data.spreadsheetUrl;\n    this._rawProperties = data.properties;\n    data.sheets?.forEach((s: any) => this._updateOrCreateSheet(s));\n  }\n\n  resetLocalCache() {\n    this._rawProperties = null;\n    this._rawSheets = {};\n  }\n\n  // WORKSHEETS ////////////////////////////////////////////////////////////////////////////////////\n  get sheetCount() {\n    this._ensureInfoLoaded();\n    return _.values(this._rawSheets).length;\n  }\n\n  get sheetsById(): Record<WorksheetId, GoogleSpreadsheetWorksheet> {\n    this._ensureInfoLoaded();\n    return this._rawSheets;\n  }\n\n  get sheetsByIndex(): GoogleSpreadsheetWorksheet[] {\n    this._ensureInfoLoaded();\n    return _.sortBy(this._rawSheets, 'index');\n  }\n\n  get sheetsByTitle(): Record<string, GoogleSpreadsheetWorksheet> {\n    this._ensureInfoLoaded();\n    return _.keyBy(this._rawSheets, 'title');\n  }\n\n  /**\n   * Add new worksheet to document\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#AddSheetRequest\n   * */\n  async addSheet(\n    properties: Partial<\n    RecursivePartial<WorksheetProperties>\n    & {\n      headerValues: string[],\n      headerRowIndex: number\n    }\n    > = {}\n  ) {\n    const response = await this._makeSingleUpdateRequest('addSheet', {\n      properties: _.omit(properties, 'headerValues', 'headerRowIndex'),\n    });\n    // _makeSingleUpdateRequest already adds the sheet\n    const newSheetId = response.properties.sheetId;\n    const newSheet = this.sheetsById[newSheetId];\n\n    if (properties.headerValues) {\n      await newSheet.setHeaderRow(properties.headerValues, properties.headerRowIndex);\n    }\n\n    return newSheet;\n  }\n\n  /**\n   * delete a worksheet\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#DeleteSheetRequest\n   * */\n  async deleteSheet(sheetId: WorksheetId) {\n    await this._makeSingleUpdateRequest('deleteSheet', { sheetId });\n    delete this._rawSheets[sheetId];\n  }\n\n  // NAMED RANGES //////////////////////////////////////////////////////////////////////////////////\n\n  /**\n   * create a new named range\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#AddNamedRangeRequest\n   */\n  async addNamedRange(\n    /** name of new named range */\n    name: string,\n    /** GridRange object describing range */\n    range: GridRange,\n    /** id for named range (optional) */\n    namedRangeId?: string\n  ) {\n    // TODO: add named range to local cache\n    return this._makeSingleUpdateRequest('addNamedRange', {\n      namedRange: {\n        name,\n        namedRangeId,\n        range,\n      },\n    });\n  }\n\n  /**\n   * delete a named range\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#DeleteNamedRangeRequest\n   * */\n  async deleteNamedRange(\n    /** id of named range to delete */\n    namedRangeId: NamedRangeId\n  ) {\n    // TODO: remove named range from local cache\n    return this._makeSingleUpdateRequest('deleteNamedRange', { namedRangeId });\n  }\n\n  // LOADING CELLS /////////////////////////////////////////////////////////////////////////////////\n\n  /** fetch cell data into local cache */\n  async loadCells(\n    /**\n     * single filter or array of filters\n     * strings are treated as A1 ranges, objects are treated as GridRange objects,\n     * objects with a `developerMetadataLookup` key are treated as DeveloperMetadataLookup filters\n     * pass nothing to fetch all cells\n     * */\n    filters?: DataFilter | DataFilter[]\n  ) {\n    // TODO: switch to this mode if using a read-only auth token?\n    const readOnlyMode = this.authMode === AUTH_MODES.API_KEY;\n\n    const filtersArray = _.isArray(filters) ? filters : [filters];\n    const dataFilters = _.map(filtersArray, (filter) => {\n      if (_.isString(filter)) {\n        return readOnlyMode ? filter : { a1Range: filter };\n      }\n      if (_.isObject(filter)) {\n        if (readOnlyMode) {\n          throw new Error('Only A1 ranges are supported when fetching cells with read-only access (using only an API key)');\n        }\n        if ('developerMetadataLookup' in filter) {\n          return { developerMetadataLookup: filter.developerMetadataLookup };\n        }\n        return { gridRange: filter };\n      }\n      throw new Error('Each filter must be an A1 range string or a gridrange object');\n    });\n\n    let result;\n    // when using an API key only, we must use the regular get endpoint\n    // because :getByDataFilter requires higher access\n    if (this.authMode === AUTH_MODES.API_KEY) {\n      const params = new URLSearchParams();\n      params.append('includeGridData', 'true');\n      dataFilters.forEach((singleFilter) => {\n        if (!_.isString(singleFilter)) {\n          throw new Error('Only A1 ranges are supported when fetching cells with read-only access (using only an API key)');\n        }\n        params.append('ranges', singleFilter);\n      });\n      result = await this.sheetsApi.get('', {\n        searchParams: params,\n      });\n    // otherwise we use the getByDataFilter endpoint because it is more flexible\n    } else {\n      result = await this.sheetsApi.post(':getByDataFilter', {\n        json: {\n          includeGridData: true,\n          dataFilters,\n        },\n      });\n    }\n\n    const data = await result?.json<any>();\n    _.each(data.sheets, (sheet: any) => { this._updateOrCreateSheet(sheet); });\n  }\n\n  // EXPORTING /////////////////////////////////////////////////////////////\n\n  /**\n   * export/download helper, not meant to be called directly (use downloadAsX methods on spreadsheet and worksheet instead)\n   * @internal\n   */\n  async _downloadAs(\n    fileType: ExportFileTypes,\n    worksheetId: WorksheetId | undefined,\n    returnStreamInsteadOfBuffer?: boolean\n  ) {\n    // see https://stackoverflow.com/questions/11619805/using-the-google-drive-api-to-download-a-spreadsheet-in-csv-format/51235960#51235960\n\n    if (!EXPORT_CONFIG[fileType]) throw new Error(`unsupported export fileType - ${fileType}`);\n    if (EXPORT_CONFIG[fileType].singleWorksheet) {\n      if (worksheetId === undefined) throw new Error(`Must specify worksheetId when exporting as ${fileType}`);\n    } else if (worksheetId) throw new Error(`Cannot specify worksheetId when exporting as ${fileType}`);\n\n    // google UI shows \"html\" but passes through \"zip\"\n    if (fileType === 'html') fileType = 'zip';\n\n    if (!this._spreadsheetUrl) throw new Error('Cannot export sheet that is not fully loaded');\n\n    const exportUrl = this._spreadsheetUrl.replace('edit', 'export');\n    const response = await this.sheetsApi.get(exportUrl, {\n      prefix: '', // unset baseUrl since we're not hitting the normal sheets API\n      searchParams: {\n        id: this.spreadsheetId,\n        format: fileType,\n        // worksheetId can be 0\n        ...worksheetId !== undefined && { gid: worksheetId },\n      },\n    });\n    if (returnStreamInsteadOfBuffer) {\n      return response.body;\n    }\n    return response.arrayBuffer();\n  }\n\n  /**\n   * exports entire document as html file (zipped)\n   * @topic export\n   * */\n  async downloadAsZippedHTML(): Promise<ArrayBuffer>;\n  async downloadAsZippedHTML(returnStreamInsteadOfBuffer: false): Promise<ArrayBuffer>;\n  async downloadAsZippedHTML(returnStreamInsteadOfBuffer: true): Promise<ReadableStream>;\n  async downloadAsZippedHTML(returnStreamInsteadOfBuffer?: boolean) {\n    return this._downloadAs('html', undefined, returnStreamInsteadOfBuffer);\n  }\n\n  /**\n   * @deprecated\n   * use `doc.downloadAsZippedHTML()` instead\n   * */\n  async downloadAsHTML(returnStreamInsteadOfBuffer?: boolean) {\n    return this._downloadAs('html', undefined, returnStreamInsteadOfBuffer);\n  }\n\n  /**\n   * exports entire document as xlsx spreadsheet (Microsoft Office Excel)\n   * @topic export\n   * */\n  async downloadAsXLSX(): Promise<ArrayBuffer>;\n  async downloadAsXLSX(returnStreamInsteadOfBuffer: false): Promise<ArrayBuffer>;\n  async downloadAsXLSX(returnStreamInsteadOfBuffer: true): Promise<ReadableStream>;\n  async downloadAsXLSX(returnStreamInsteadOfBuffer = false) {\n    return this._downloadAs('xlsx', undefined, returnStreamInsteadOfBuffer);\n  }\n  /**\n   * exports entire document as ods spreadsheet (Open Office)\n   * @topic export\n  */\n  async downloadAsODS(): Promise<ArrayBuffer>;\n  async downloadAsODS(returnStreamInsteadOfBuffer: false): Promise<ArrayBuffer>;\n  async downloadAsODS(returnStreamInsteadOfBuffer: true): Promise<ReadableStream>;\n  async downloadAsODS(returnStreamInsteadOfBuffer = false) {\n    return this._downloadAs('ods', undefined, returnStreamInsteadOfBuffer);\n  }\n\n\n  async delete() {\n    await this.driveApi.delete('');\n    this._deleted = true;\n    // endpoint returns nothing when successful\n  }\n\n  // PERMISSIONS ///////////////////////////////////////////////////////////////////////////////////\n\n  /**\n   * list all permissions entries for doc\n   */\n  async listPermissions(): Promise<PermissionsList> {\n    const listReq = await this.driveApi.get('permissions', {\n      searchParams: {\n        fields: 'permissions(id,type,emailAddress,domain,role,displayName,photoLink,deleted)',\n      },\n    });\n    const data = await listReq.json<{ permissions: PermissionsList }>();\n    return data.permissions;\n  }\n\n  async setPublicAccessLevel(role: PublicPermissionRoles | false) {\n    const permissions = await this.listPermissions();\n    const existingPublicPermission = _.find(permissions, (p) => p.type === 'anyone');\n\n    if (role === false) {\n      if (!existingPublicPermission) {\n        // doc is already not public... could throw an error or just do nothing\n        return;\n      }\n      await this.driveApi.delete(`permissions/${existingPublicPermission.id}`);\n    } else {\n      const _shareReq = await this.driveApi.post('permissions', {\n        json: {\n          role: role || 'viewer',\n          type: 'anyone',\n        },\n      });\n    }\n  }\n\n  /** share document to email or domain */\n  async share(emailAddressOrDomain: string, opts?: {\n    /** set role level, defaults to owner */\n    role?: PermissionRoles,\n\n    /** set to true if email is for a group */\n    isGroup?: boolean,\n\n    /** set to string to include a custom message, set to false to skip sending a notification altogether */\n    emailMessage?: string | false,\n\n    // moveToNewOwnersRoot?: string,\n    // /** send a notification email (default = true) */\n    // sendNotificationEmail?: boolean,\n    // /** support My Drives and shared drives (default = false) */\n    // supportsAllDrives?: boolean,\n\n    // /** Issue the request as a domain administrator */\n    // useDomainAdminAccess?: boolean,\n  }) {\n    let emailAddress: string | undefined;\n    let domain: string | undefined;\n    if (emailAddressOrDomain.includes('@')) {\n      emailAddress = emailAddressOrDomain;\n    } else {\n      domain = emailAddressOrDomain;\n    }\n\n\n    const shareReq = await this.driveApi.post('permissions', {\n      searchParams: {\n        ...opts?.emailMessage === false && { sendNotificationEmail: false },\n        ..._.isString(opts?.emailMessage) && { emailMessage: opts?.emailMessage },\n        ...opts?.role === 'owner' && { transferOwnership: true },\n      },\n      json: {\n        role: opts?.role || 'writer',\n        ...emailAddress && {\n          type: opts?.isGroup ? 'group' : 'user',\n          emailAddress,\n        },\n        ...domain && {\n          type: 'domain',\n          domain,\n        },\n      },\n    });\n\n    return shareReq.json();\n  }\n\n  /**\n   * delete a permission by its ID\n   * @see https://developers.google.com/drive/api/v3/reference/permissions/delete\n   */\n  async deletePermission(permissionId: string) {\n    await this.driveApi.delete(`permissions/${permissionId}`);\n  }\n\n  // DEVELOPER METADATA ///////////////////////////////////////////////////////////////////////////\n\n  /**\n   * search for developer metadata entries matching the given filters\n   * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.developerMetadata/search\n   */\n  async searchDeveloperMetadata(\n    /** array of DataFilter objects to match against */\n    filters: DataFilterObject[]\n  ): Promise<DeveloperMetadata[]> {\n    const response = await this.sheetsApi.post('developerMetadata:search', {\n      json: { dataFilters: filters },\n    });\n    const data = await response.json<any>();\n    if (!data.matchedDeveloperMetadata) return [];\n    return data.matchedDeveloperMetadata.map((m: any) => m.developerMetadata);\n  }\n\n  //\n  // CREATE NEW DOC ////////////////////////////////////////////////////////////////////////////////\n  static async createNewSpreadsheetDocument(auth: GoogleApiAuth, properties?: Partial<SpreadsheetProperties>) {\n    // see updateProperties for more info about available properties\n\n    if (getAuthMode(auth) === AUTH_MODES.API_KEY) {\n      throw new Error('Cannot use api key only to create a new spreadsheet - it is only usable for read-only access of public docs');\n    }\n\n    // TODO: handle injecting default credentials if running on google infra\n\n    const authConfig = await getRequestAuthConfig(auth);\n\n    const response = await ky.post(SHEETS_API_BASE_URL, {\n      ...authConfig, // has the auth header\n      json: {\n        properties,\n      },\n    });\n\n    const data = await response.json<any>();\n    const newSpreadsheet = new GoogleSpreadsheet(data.spreadsheetId, auth);\n\n    // TODO ideally these things aren't public, might want to refactor anyway\n    newSpreadsheet._spreadsheetUrl = data.spreadsheetUrl;\n    newSpreadsheet._rawProperties = data.properties;\n    _.each(data.sheets, (s: any) => newSpreadsheet._updateOrCreateSheet(s));\n\n    return newSpreadsheet;\n  }\n}\n"],"mappings":"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"}