diff --git a/README.md b/README.md index a4f9149..721c3b1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ -# HollySheets +# HolySheets -HollySheets is a TypeScript library that simplifies the process of interacting with Google Sheets API. It provides a set of tools to read and write data from and to Google Sheets. +![Logo](logo.svg) + +HolySheets is a TypeScript library that simplifies the process of interacting with Google Sheets API. It provides a set of tools to read and write data from and to Google Sheets. ## Features @@ -10,19 +12,19 @@ HollySheets is a TypeScript library that simplifies the process of interacting w ## Installation -You can install HollySheets using npm: +You can install HolySheets using npm: ```bash -npm install hollysheets +npm install holysheets ``` ## Usage -To use `HollySheets` in your TypeScript project, you need to import it and initialize it with your Google Sheets credentials. Here's an example: +To use `HolySheets` in your TypeScript project, you need to import it and initialize it with your Google Sheets credentials. Here's an example: ## Limitations -While HollySheets provides a convenient way to interact with Google Sheets, it may not be suitable for all projects. Specifically, it may not be the best choice for: +While HolySheets provides a convenient way to interact with Google Sheets, it may not be suitable for all projects. Specifically, it may not be the best choice for: - Projects with large amounts of data: Google Sheets has requests limits and performance can degrade with very large sheets. If your project involves handling large datasets, a dedicated database system may be more appropriate. diff --git a/logo.svg b/logo.svg new file mode 100644 index 0000000..f77d316 --- /dev/null +++ b/logo.svg @@ -0,0 +1,255 @@ + + + + + Sheets-icon + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Sheets-icon + + + + diff --git a/package.json b/package.json index e6bb371..4b00f03 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "holly-sheets", - "version": "1.0.6", + "name": "holy-sheets", + "version": "1.0.0", "description": "A Node.js library for interacting with Google Sheets", "main": "dist/src/index.js", "types": "dist/src/index.d.ts", @@ -9,18 +9,19 @@ "clean": "rm -rf dist", "test": "jest", "coverage": "jest --coverage", + "sample": "npm run build && node dist/sample.js", "prepublishOnly": "npm run build" }, "repository": { "type": "git", - "url": "https://github.com/holly-sheets/holly-sheets.git" + "url": "https://github.com/holy-sheets/holy-sheets.git" }, "keywords": [ "google", "sheets", "spreadsheet", "api", - "holly sheets" + "holy sheets" ], "author": "Teles", "license": "MIT", @@ -28,15 +29,16 @@ "googleapis": "^137.1.0" }, "devDependencies": { + "@babel/preset-env": "^7.24.5", "@types/jest": "^29.5.12", "@types/node": "^20.12.8", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", + "babel-jest": "^29.7.0", "eslint": "^8.57.0", "eslint-config-standard-with-typescript": "^43.0.1", - "@babel/preset-env": "^7.24.5", - "babel-jest": "^29.7.0", "jest": "^29.7.0", + "lcov-badge2": "^1.1.2", "ts-jest": "^29.1.2", "typescript": "^5.4.5" } diff --git a/sample.ts b/sample.ts new file mode 100644 index 0000000..e0dc943 --- /dev/null +++ b/sample.ts @@ -0,0 +1,28 @@ +import HollySheets from './src/index' +import credentials from './.credentials.json' + +const hollySheets = new HollySheets({ + spreadsheetId: '10EQjKl96GqZFrSSoJKHdJFpAwRfvCzaQY3-u85IBpEA', + privateKey: credentials.private_key, + clientEmail: credentials.client_email +}) + +const main = async () => { + interface User { + name: string + email: string + age: number + } + + const user = hollySheets.base('Users') + const joses = await user.findMany({ + where: { + name: { + contains: 'Jose' + } + } + }) + console.log(JSON.stringify(joses, null, 4)) // eslint-disable-line +} + +void main() diff --git a/src/index.test.ts b/src/index.test.ts index ea2aea1..cdf8b7b 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -74,31 +74,31 @@ describe('HollySheets', () => { }); it('should initialize HollySheets with credentials', () => { - const hollySheets = new HollySheets(credentials); - expect(hollySheets).toBeTruthy(); + const holySheets = new HollySheets(credentials); + expect(holySheets).toBeTruthy(); }); it('should set table with base method', () => { - const hollySheets = new HollySheets(credentials); + const holySheets = new HollySheets(credentials); const table = 'TestTable'; const table2 = 'TestTable2'; - expect(hollySheets.table).not.toBe(table); - const baseInstance = hollySheets.base(table); - expect(baseInstance.table).toBe(table); - expect(hollySheets.table).not.toBe(table); - const baseInstance2 = hollySheets.base(table2); - expect(baseInstance2.table).toBe(table2); + expect(holySheets.sheet).not.toBe(table); + const baseInstance = holySheets.base(table); + expect(baseInstance.sheet).toBe(table); + expect(holySheets.sheet).not.toBe(table); + const baseInstance2 = holySheets.base(table2); + expect(baseInstance2.sheet).toBe(table2); }); // it('testing mockGoogleApis', async () => { - // const hollySheets = new HollySheets(credentials); + // const holySheets = new HollySheets(credentials); // interface User { // name: string; // email: string; // } - // const users = hollySheets.base('Users'); + // const users = holySheets.base('Users'); // const result = await users.findMany({ // where: { diff --git a/src/index.ts b/src/index.ts index 3dfb52e..f1c5da6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,7 +2,7 @@ import { sheets_v4 } from 'googleapis' import { google } from 'googleapis' import { JWT } from 'google-auth-library' -interface HollySheetsCredentials { +interface HolySheetsCredentials { clientEmail: string; privateKey: string; spreadsheetId: string; @@ -53,12 +53,12 @@ const decombine = >(record: RecordType * @param options.table - The name of the table. * @returns A promise that resolves to an array of SheetHeaders representing the headers of the table. */ -async function getHeaders(options: {table: TableName, sheets: sheets_v4.Sheets, spreadsheetId: string}): Promise { - const { table, sheets, spreadsheetId } = options +async function getHeaders(options: {sheet: SheetName, sheets: sheets_v4.Sheets, spreadsheetId: string}): Promise { + const { sheet, sheets, spreadsheetId } = options try { const response = await sheets.spreadsheets.values.get({ spreadsheetId, - range: `${table}!1:1` + range: `${sheet}!1:1` }) const values = response.data.values @@ -123,7 +123,7 @@ type WhereCondition = { [key in WhereFilterKey]?: WhereConditionAcceptedValues; }; -interface RowSet> { +interface SheetRecord> { range: string row: number fields: Partial @@ -165,17 +165,17 @@ type SelectClause = Partial<{[column in keyof RecordType]: boolean}> * Represents a wrapper class for interacting with Google Sheets using the Google Sheets API. * @typeparam RecordType - The type of the records in the table. */ -export default class HollySheets = any> { +export default class HolySheets = any> { public sheets: sheets_v4.Sheets - public table: string = '' + public sheet: string = '' public static spreadsheetId: string = '' - private readonly credentials: HollySheetsCredentials + private readonly credentials: HolySheetsCredentials /** - * Creates a new instance of the HollySheets class. + * Creates a new instance of the HolySheets class. * @param credentials - The credentials required to authenticate with the Google Sheets API. */ - constructor(credentials: HollySheetsCredentials) { + constructor(credentials: HolySheetsCredentials) { this.credentials = credentials const auth = new JWT({ @@ -183,24 +183,24 @@ export default class HollySheets = any> { key: credentials.privateKey, scopes: ['https://www.googleapis.com/auth/spreadsheets'] }) - HollySheets.spreadsheetId = credentials.spreadsheetId + HolySheets.spreadsheetId = credentials.spreadsheetId this.sheets = google.sheets({ version: 'v4', auth }) } /** - * Creates a new instance of HollySheets with the specified table. + * Creates a new instance of HolySheets with the specified table. * @param table - The name of the table to use. - * @returns A new instance of HollySheets with the specified table. + * @returns A new instance of HolySheets with the specified table. * @typeparam T - The type of the records in the table. */ - public base>(table: string): HollySheets { - const instance = new HollySheets(this.credentials) + public base>(table: string): HolySheets { + const instance = new HolySheets(this.credentials) instance.setTable(table) return instance } private setTable(table: string) { - this.table = table + this.sheet = table } /** @@ -212,24 +212,24 @@ export default class HollySheets = any> { */ public async insert(options: { data: RecordType[] }) { const { data } = options - const table = this.table + const sheet = this.sheet const response = await this.sheets.spreadsheets.values.get({ - spreadsheetId: HollySheets.spreadsheetId, - range: `${table}!${alphabet[0]}:${alphabet[alphabet.length - 1]}` + spreadsheetId: HolySheets.spreadsheetId, + range: `${sheet}!${alphabet[0]}:${alphabet[alphabet.length - 1]}` }) if(!response.data.values) { throw new Error('No data found in the sheet.') } const lastLine = response.data.values.length - const headers = await getHeaders({ table, sheets: this.sheets, spreadsheetId: HollySheets.spreadsheetId}) + const headers = await getHeaders({ sheet: sheet, sheets: this.sheets, spreadsheetId: HolySheets.spreadsheetId}) const valuesFromRecords = data.map(record => decombine(record, headers)) const range = `A${lastLine + 1}:${indexToColumn(headers.length - 1)}${lastLine + valuesFromRecords.length}` await write({ - tableName: table, + tableName: sheet, range, values: valuesFromRecords, - spreadsheetId: HollySheets.spreadsheetId, + spreadsheetId: HolySheets.spreadsheetId, sheets: this.sheets }) } @@ -243,25 +243,25 @@ export default class HollySheets = any> { * * @returns A promise that resolves to a `RowSet` object representing the first matching row, or `undefined` if no match is found. */ - public async findFirst(options: { where: WhereClause, select?: SelectClause }): Promise|undefined>{ + public async findFirst(options: { where: WhereClause, select?: SelectClause }): Promise|undefined>{ const { where } = options - const table = this.table - const headers = await getHeaders({ table, sheets: this.sheets, spreadsheetId: HollySheets.spreadsheetId}) + const sheet = this.sheet + const headers = await getHeaders({ sheet, sheets: this.sheets, spreadsheetId: HolySheets.spreadsheetId}) const columns = Object.keys(where) as (keyof RecordType)[] const header = headers.find(header => header.name === columns[0]) - const range = `${table}!${header?.column}:${header?.column}` + const range = `${sheet}!${header?.column}:${header?.column}` try { const response = await this.sheets.spreadsheets.values.get({ - spreadsheetId: HollySheets.spreadsheetId, + spreadsheetId: HolySheets.spreadsheetId, range }) const rowIndex = response.data.values?.findIndex(row => checkWhereFilter(where[columns[0]] as WhereCondition|string, row[0] as string)) if(rowIndex === -1 || !rowIndex) { return undefined } - const rowRange = `${table}!A${rowIndex + 1}:${indexToColumn(headers.length - 1)}${rowIndex + 1}` + const rowRange = `${sheet}!A${rowIndex + 1}:${indexToColumn(headers.length - 1)}${rowIndex + 1}` const rowResponse = await this.sheets.spreadsheets.values.get({ - spreadsheetId: HollySheets.spreadsheetId, + spreadsheetId: HolySheets.spreadsheetId, range: rowRange }) @@ -285,17 +285,17 @@ export default class HollySheets = any> { * @param options - The options for the query, including the `where` clause and optional `select` clause. * @returns A promise that resolves to an array of row sets matching the query. */ - public async findMany(options: { where: WhereClause, select?: SelectClause }): Promise[]> { + public async findMany(options: { where: WhereClause, select?: SelectClause }): Promise[]> { const { where, select } = options - const table = this.table - const headers = await getHeaders({ table, sheets: this.sheets, spreadsheetId: HollySheets.spreadsheetId}) + const sheet = this.sheet + const headers = await getHeaders({ sheet, sheets: this.sheets, spreadsheetId: HolySheets.spreadsheetId}) const columns = Object.keys(where) as (keyof RecordType)[] const header = headers.find(header => header.name === columns[0]) - const range = `${table}!${header?.column}:${header?.column}` + const range = `${sheet}!${header?.column}:${header?.column}` try { const response = await this.sheets.spreadsheets.values.get({ - spreadsheetId: HollySheets.spreadsheetId, + spreadsheetId: HolySheets.spreadsheetId, range }) const rowIndexes = response.data.values?.reduce((acc: number[], row, index) => { @@ -307,10 +307,10 @@ export default class HollySheets = any> { if(!rowIndexes || rowIndexes.length === 0) { return [] } - const rowsRange = rowIndexes.map(index => `${table}!A${index + 1}:${indexToColumn(headers.length - 1)}${index + 1}`) + const rowsRange = rowIndexes.map(index => `${sheet}!A${index + 1}:${indexToColumn(headers.length - 1)}${index + 1}`) const ranges = rowsRange const batchGetResponse = await this.sheets.spreadsheets.values.batchGet({ - spreadsheetId: HollySheets.spreadsheetId, + spreadsheetId: HolySheets.spreadsheetId, ranges: ranges }) @@ -388,7 +388,7 @@ export default class HollySheets = any> { * @returns A promise that resolves when the record is cleared. * @throws An error if no record is found to delete. */ - public async clearFirst(options: { where: WhereClause }): Promise> { + public async clearFirst(options: { where: WhereClause }): Promise> { const { where } = options const record = await this.findFirst({ where }) if (!record) { @@ -396,7 +396,7 @@ export default class HollySheets = any> { } const { range } = record await this.sheets.spreadsheets.values.clear({ - spreadsheetId: HollySheets.spreadsheetId, + spreadsheetId: HolySheets.spreadsheetId, range }) return record @@ -410,7 +410,7 @@ export default class HollySheets = any> { * @returns A Promise that resolves when the clear operation is complete. * @throws An error if no records are found to delete. */ - public async clearMany(options: { where: WhereClause }): Promise[]> { + public async clearMany(options: { where: WhereClause }): Promise[]> { const { where } = options const records = await this.findMany({ where }) if(records.length === 0) { @@ -418,7 +418,7 @@ export default class HollySheets = any> { } const ranges = records.map(record => record.range) await this.sheets.spreadsheets.values.batchClear({ - spreadsheetId: HollySheets.spreadsheetId, + spreadsheetId: HolySheets.spreadsheetId, requestBody: { ranges } @@ -434,7 +434,7 @@ export default class HollySheets = any> { */ public async getSheetId(title: string): Promise { const response = await this.sheets.spreadsheets.get({ - spreadsheetId: HollySheets.spreadsheetId, + spreadsheetId: HolySheets.spreadsheetId, includeGridData: false }) @@ -454,9 +454,9 @@ export default class HollySheets = any> { * @returns A promise that resolves when the record is deleted. * @throws An error if no record is found to delete. */ - public async deleteFirst(options: { where: WhereClause }): Promise> { + public async deleteFirst(options: { where: WhereClause }): Promise> { const { where } = options - const sheetId = await this.getSheetId(this.table) + const sheetId = await this.getSheetId(this.sheet) const record = await this.findFirst({ where }) const requests = [{ deleteDimension: { @@ -469,12 +469,12 @@ export default class HollySheets = any> { } }] await this.sheets.spreadsheets.batchUpdate({ - spreadsheetId: HollySheets.spreadsheetId, + spreadsheetId: HolySheets.spreadsheetId, requestBody: { requests } }) - return record as RowSet + return record as SheetRecord } /** @@ -485,13 +485,13 @@ export default class HollySheets = any> { * @returns A Promise that resolves when the delete operation is complete. * @throws An error if no records are found to delete. */ - public async deleteMany(options: { where: WhereClause }): Promise[]> { + public async deleteMany(options: { where: WhereClause }): Promise[]> { const { where } = options const records = await this.findMany({ where }) if(records.length === 0) { throw new Error('No records found to delete') } - const sheetId = await this.getSheetId(this.table) + const sheetId = await this.getSheetId(this.sheet) const requests = records .sort((a, b) => b.row - a.row) .map((record) => ({ @@ -506,7 +506,7 @@ export default class HollySheets = any> { )) await this.sheets.spreadsheets.batchUpdate({ - spreadsheetId: HollySheets.spreadsheetId, + spreadsheetId: HolySheets.spreadsheetId, requestBody: { requests }