bodevops-features 1.0.0 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cjs/index.js CHANGED
@@ -1,13 +1,1710 @@
1
1
  'use strict';
2
2
 
3
- const test = () => {
4
- console.log('test');
5
- };
3
+ var googleapis = require('googleapis');
4
+ var fs = require('fs');
5
+ var path = require('path');
6
+
7
+ function _interopNamespaceDefault(e) {
8
+ var n = Object.create(null);
9
+ if (e) {
10
+ Object.keys(e).forEach(function (k) {
11
+ if (k !== 'default') {
12
+ var d = Object.getOwnPropertyDescriptor(e, k);
13
+ Object.defineProperty(n, k, d.get ? d : {
14
+ enumerable: true,
15
+ get: function () { return e[k]; }
16
+ });
17
+ }
18
+ });
19
+ }
20
+ n.default = e;
21
+ return Object.freeze(n);
22
+ }
23
+
24
+ var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
25
+ var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
26
+
27
+ /**
28
+ * Google Drive Feature Library - Configuration Module
29
+ * @description Configuration management for Google Drive client initialization and authentication.
30
+ * @module gg-drive/config
31
+ */
32
+ /**
33
+ * Default OAuth scopes required for Google Drive operations.
34
+ * These scopes provide full access to Drive files.
35
+ */
36
+ const DEFAULT_DRIVE_SCOPES = [
37
+ 'https://www.googleapis.com/auth/drive',
38
+ 'https://www.googleapis.com/auth/drive.file',
39
+ ];
40
+ /**
41
+ * Google Drive Configuration Manager.
42
+ * Handles validation and normalization of configuration options for the Google Drive client.
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * // Using key file path
47
+ * const config = new GoogleDriveConfig({
48
+ * keyFilePath: './service-account.json'
49
+ * });
50
+ *
51
+ * // Using credentials object
52
+ * const config = new GoogleDriveConfig({
53
+ * credentials: {
54
+ * type: 'service_account',
55
+ * project_id: 'my-project',
56
+ * private_key: '-----BEGIN PRIVATE KEY-----\n...',
57
+ * client_email: 'service-account@my-project.iam.gserviceaccount.com',
58
+ * // ... other fields
59
+ * }
60
+ * });
61
+ * ```
62
+ */
63
+ class GoogleDriveConfig {
64
+ /**
65
+ * Creates a new GoogleDriveConfig instance.
66
+ *
67
+ * @param config - Configuration options for Google Drive client
68
+ * @throws Error if neither keyFilePath nor credentials is provided
69
+ */
70
+ constructor({ keyFilePath, credentials, scopes }) {
71
+ // Validate that at least one authentication method is provided
72
+ if (!keyFilePath && !credentials) {
73
+ throw new Error('GoogleDriveConfig: Either keyFilePath or credentials must be provided');
74
+ }
75
+ this.keyFilePath = keyFilePath;
76
+ this.credentials = credentials;
77
+ this.scopes = scopes || DEFAULT_DRIVE_SCOPES;
78
+ // Validate credentials structure if provided
79
+ if (credentials) {
80
+ this.validateCredentials(credentials);
81
+ }
82
+ }
83
+ /**
84
+ * Validates that the credentials object contains all required fields.
85
+ *
86
+ * @param credentials - The credentials object to validate
87
+ * @throws Error if required fields are missing
88
+ */
89
+ validateCredentials(credentials) {
90
+ const requiredFields = [
91
+ 'type',
92
+ 'project_id',
93
+ 'private_key',
94
+ 'client_email',
95
+ ];
96
+ for (const field of requiredFields) {
97
+ if (!credentials[field]) {
98
+ throw new Error(`GoogleDriveConfig: Missing required credential field: ${field}`);
99
+ }
100
+ }
101
+ if (credentials.type !== 'service_account') {
102
+ throw new Error(`GoogleDriveConfig: Invalid credential type. Expected 'service_account', got '${credentials.type}'`);
103
+ }
104
+ }
105
+ /**
106
+ * Returns the authentication configuration object suitable for googleapis.
107
+ *
108
+ * @returns Authentication options for Google Auth
109
+ */
110
+ getAuthOptions() {
111
+ if (this.keyFilePath) {
112
+ return {
113
+ keyFile: this.keyFilePath,
114
+ scopes: this.scopes,
115
+ };
116
+ }
117
+ return {
118
+ credentials: this.credentials,
119
+ scopes: this.scopes,
120
+ };
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Google Drive Feature Library - Utility Functions
126
+ * @description Helper functions for file handling, formatting, and validation in Google Drive operations.
127
+ * @module gg-drive/utils
128
+ */
129
+ /**
130
+ * Size units for human-readable byte formatting.
131
+ */
132
+ const SIZE_UNITS = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
133
+ /**
134
+ * Base value for byte conversions (1024 bytes = 1 KB).
135
+ */
136
+ const BYTE_BASE = 1024;
137
+ /**
138
+ * Converts a byte count into a human-readable string format.
139
+ *
140
+ * @param bytes - The number of bytes to format
141
+ * @returns A human-readable string representation (e.g., "1.5 GB")
142
+ *
143
+ * @example
144
+ * ```typescript
145
+ * formatBytes(0); // "0 Bytes"
146
+ * formatBytes(1024); // "1 KB"
147
+ * formatBytes(1536); // "1.5 KB"
148
+ * formatBytes(1073741824); // "1 GB"
149
+ * ```
150
+ */
151
+ function formatBytes({ bytes }) {
152
+ if (bytes === 0) {
153
+ return '0 Bytes';
154
+ }
155
+ const exponent = Math.floor(Math.log(bytes) / Math.log(BYTE_BASE));
156
+ const value = bytes / Math.pow(BYTE_BASE, exponent);
157
+ const formattedValue = parseFloat(value.toFixed(2));
158
+ return `${formattedValue} ${SIZE_UNITS[exponent]}`;
159
+ }
160
+ /**
161
+ * Normalizes a file path to use the correct path separators for the current OS.
162
+ * Also resolves relative paths to absolute paths.
163
+ *
164
+ * @param filePath - The file path to normalize
165
+ * @returns The normalized absolute file path
166
+ *
167
+ * @example
168
+ * ```typescript
169
+ * normalizeFilePath({ filePath: './documents/file.pdf' });
170
+ * // Returns: "D:\\MyFolder\\documents\\file.pdf" (on Windows)
171
+ * ```
172
+ */
173
+ function normalizeFilePath({ filePath }) {
174
+ // Resolve to absolute path if relative
175
+ const absolutePath = path__namespace.resolve(filePath);
176
+ // Normalize path separators for current OS
177
+ return path__namespace.normalize(absolutePath);
178
+ }
179
+ /**
180
+ * Validates that a file exists at the specified path.
181
+ *
182
+ * @param filePath - The absolute path to the file to validate
183
+ * @returns True if the file exists, false otherwise
184
+ *
185
+ * @example
186
+ * ```typescript
187
+ * const exists = validateFileExists({ filePath: 'D:\\MyFolder\\file.pdf' });
188
+ * if (!exists) {
189
+ * console.log('File not found!');
190
+ * }
191
+ * ```
192
+ */
193
+ function validateFileExists({ filePath }) {
194
+ try {
195
+ return fs__namespace.existsSync(filePath);
196
+ }
197
+ catch {
198
+ return false;
199
+ }
200
+ }
201
+ /**
202
+ * Gets detailed information about a local file.
203
+ *
204
+ * @param filePath - The absolute path to the file
205
+ * @returns File information object or null if file doesn't exist
206
+ *
207
+ * @example
208
+ * ```typescript
209
+ * const info = getFileInfo({ filePath: './document.pdf' });
210
+ * if (info) {
211
+ * console.log(`File size: ${info.sizeFormatted}`);
212
+ * }
213
+ * ```
214
+ */
215
+ function getFileInfo({ filePath }) {
216
+ const normalizedPath = normalizeFilePath({ filePath });
217
+ if (!validateFileExists({ filePath: normalizedPath })) {
218
+ return null;
219
+ }
220
+ const stats = fs__namespace.statSync(normalizedPath);
221
+ return {
222
+ name: path__namespace.basename(normalizedPath),
223
+ extension: path__namespace.extname(normalizedPath).slice(1),
224
+ directory: path__namespace.dirname(normalizedPath),
225
+ size: stats.size,
226
+ sizeFormatted: formatBytes({ bytes: stats.size }),
227
+ };
228
+ }
229
+ /**
230
+ * Parses a folder path string into an array of folder names.
231
+ *
232
+ * @param folderPath - The folder path to parse (e.g., "folder1/folder2/folder3")
233
+ * @returns An array of folder names
234
+ *
235
+ * @example
236
+ * ```typescript
237
+ * parseFolderPath({ folderPath: 'a/b/c' }); // ['a', 'b', 'c']
238
+ * parseFolderPath({ folderPath: '/' }); // []
239
+ * parseFolderPath({ folderPath: '' }); // []
240
+ * ```
241
+ */
242
+ function parseFolderPath({ folderPath }) {
243
+ if (!folderPath || folderPath === '/' || folderPath === '') {
244
+ return [];
245
+ }
246
+ return folderPath.split('/').filter((folder) => folder.trim() !== '');
247
+ }
248
+ /**
249
+ * Creates a readable stream for a local file.
250
+ *
251
+ * @param filePath - The absolute path to the file
252
+ * @returns A readable stream for the file
253
+ * @throws Error if the file doesn't exist
254
+ *
255
+ * @example
256
+ * ```typescript
257
+ * const stream = createFileReadStream({ filePath: './document.pdf' });
258
+ * // Use stream for upload
259
+ * ```
260
+ */
261
+ function createFileReadStream({ filePath }) {
262
+ const normalizedPath = normalizeFilePath({ filePath });
263
+ if (!validateFileExists({ filePath: normalizedPath })) {
264
+ throw new Error(`File does not exist: ${normalizedPath}`);
265
+ }
266
+ return fs__namespace.createReadStream(normalizedPath);
267
+ }
268
+
269
+ /**
270
+ * Google Drive Feature Library - Main Client Class
271
+ * @description A framework-agnostic client for interacting with Google Drive API.
272
+ * Provides methods for file upload, download, sharing, and storage management.
273
+ * @module gg-drive/google-drive
274
+ */
275
+ /**
276
+ * Google Drive Client for managing files and folders in Google Drive.
277
+ *
278
+ * @example
279
+ * ```typescript
280
+ * import { GoogleDriveClient } from 'bodevops-features/gg-drive';
281
+ *
282
+ * const client = new GoogleDriveClient({
283
+ * keyFilePath: './service-account.json'
284
+ * });
285
+ *
286
+ * // Get storage information
287
+ * const storage = await client.getStorageInfo();
288
+ * console.log(`Used: ${storage.formattedUsed} of ${storage.formattedTotal}`);
289
+ *
290
+ * // Upload a file
291
+ * const result = await client.uploadFile({
292
+ * localFilePath: './document.pdf',
293
+ * driveFolder: 'MyFolder/Documents',
294
+ * fileName: 'my-document.pdf'
295
+ * });
296
+ * console.log(`Uploaded: ${result.webViewLink}`);
297
+ * ```
298
+ */
299
+ class GoogleDriveClient {
300
+ /**
301
+ * Creates a new GoogleDriveClient instance.
302
+ *
303
+ * @param configOptions - Configuration options for the Google Drive client
304
+ */
305
+ constructor(configOptions) {
306
+ this.config = new GoogleDriveConfig(configOptions);
307
+ }
308
+ /**
309
+ * Creates and returns an authenticated Google Drive API client.
310
+ *
311
+ * @returns A Promise that resolves to an authenticated Drive API client
312
+ */
313
+ async getDriveClient() {
314
+ const authOptions = this.config.getAuthOptions();
315
+ const auth = new googleapis.google.auth.GoogleAuth({
316
+ keyFile: authOptions.keyFile,
317
+ credentials: authOptions.credentials,
318
+ scopes: authOptions.scopes,
319
+ });
320
+ const authClient = await auth.getClient();
321
+ return googleapis.google.drive({
322
+ version: 'v3',
323
+ auth: authClient,
324
+ });
325
+ }
326
+ /**
327
+ * Retrieves storage quota information for the Google Drive account.
328
+ *
329
+ * @returns A Promise that resolves to storage information including used/total space
330
+ * @throws Error if unable to retrieve storage information
331
+ *
332
+ * @example
333
+ * ```typescript
334
+ * const storage = await client.getStorageInfo();
335
+ * console.log(`Storage: ${storage.formattedUsed} / ${storage.formattedTotal} (${storage.percentage}%)`);
336
+ * ```
337
+ */
338
+ async getStorageInfo() {
339
+ const driveInstance = await this.getDriveClient();
340
+ const response = await driveInstance.about.get({
341
+ fields: 'storageQuota',
342
+ });
343
+ const quota = response.data.storageQuota;
344
+ if (!quota) {
345
+ throw new Error('Unable to retrieve storage quota information');
346
+ }
347
+ const used = parseInt(quota.usage || '0', 10);
348
+ const total = parseInt(quota.limit || '0', 10);
349
+ const usedInDrive = parseInt(quota.usageInDrive || '0', 10);
350
+ const percentage = total > 0 ? (used / total) * 100 : 0;
351
+ return {
352
+ used,
353
+ total,
354
+ usedInDrive,
355
+ percentage: parseFloat(percentage.toFixed(2)),
356
+ formattedUsed: formatBytes({ bytes: used }),
357
+ formattedTotal: formatBytes({ bytes: total }),
358
+ formattedUsedInDrive: formatBytes({ bytes: usedInDrive }),
359
+ };
360
+ }
361
+ /**
362
+ * Gets an existing folder by name or creates it if it doesn't exist.
363
+ *
364
+ * @param folderName - The name of the folder to find or create
365
+ * @param parentId - The ID of the parent folder (default: 'root')
366
+ * @returns A Promise that resolves to the folder ID
367
+ */
368
+ async getOrCreateFolder({ folderName, parentId = 'root', }) {
369
+ const driveInstance = await this.getDriveClient();
370
+ // Search for existing folder
371
+ const query = `name='${folderName}' and mimeType='application/vnd.google-apps.folder' and '${parentId}' in parents and trashed=false`;
372
+ const response = await driveInstance.files.list({
373
+ q: query,
374
+ fields: 'files(id, name)',
375
+ });
376
+ const folders = response.data.files || [];
377
+ if (folders.length > 0 && folders[0].id) {
378
+ // Folder exists, return its ID
379
+ return folders[0].id;
380
+ }
381
+ // Folder doesn't exist, create it
382
+ const folderMetadata = {
383
+ name: folderName,
384
+ mimeType: 'application/vnd.google-apps.folder',
385
+ parents: [parentId],
386
+ };
387
+ const folder = await driveInstance.files.create({
388
+ requestBody: folderMetadata,
389
+ fields: 'id',
390
+ });
391
+ if (!folder.data.id) {
392
+ throw new Error(`Failed to create folder: ${folderName}`);
393
+ }
394
+ return folder.data.id;
395
+ }
396
+ /**
397
+ * Creates a folder hierarchy from a path string (e.g., "a/b/c") and returns the ID of the final folder.
398
+ *
399
+ * @param folderPath - The folder path to create (e.g., "folder1/folder2/folder3")
400
+ * @returns A Promise that resolves to the ID of the innermost folder
401
+ */
402
+ async createFolderHierarchy({ folderPath }) {
403
+ const folders = parseFolderPath({ folderPath });
404
+ if (folders.length === 0) {
405
+ return 'root';
406
+ }
407
+ let currentParentId = 'root';
408
+ for (const folderName of folders) {
409
+ currentParentId = await this.getOrCreateFolder({
410
+ folderName,
411
+ parentId: currentParentId,
412
+ });
413
+ }
414
+ return currentParentId;
415
+ }
416
+ /**
417
+ * Uploads a file to Google Drive and automatically makes it public.
418
+ *
419
+ * @param params - Upload parameters including local file path and destination folder
420
+ * @returns A Promise that resolves to the upload result containing file ID and links
421
+ * @throws Error if the local file doesn't exist or upload fails
422
+ *
423
+ * @example
424
+ * ```typescript
425
+ * const result = await client.uploadFile({
426
+ * localFilePath: './document.pdf',
427
+ * driveFolder: 'MyFolder/Documents',
428
+ * fileName: 'my-document.pdf' // Optional
429
+ * });
430
+ * console.log(`View at: ${result.webViewLink}`);
431
+ * ```
432
+ */
433
+ async uploadFile({ localFilePath, driveFolder, fileName, }) {
434
+ // Normalize and validate the local file path
435
+ const normalizedPath = normalizeFilePath({ filePath: localFilePath });
436
+ if (!validateFileExists({ filePath: normalizedPath })) {
437
+ throw new Error(`File does not exist: ${normalizedPath}. Please check the file path and ensure the file exists.`);
438
+ }
439
+ // Use the local filename if fileName is not provided
440
+ const finalFileName = fileName || normalizedPath.split(/[\\/]/).pop() || 'unnamed';
441
+ const driveInstance = await this.getDriveClient();
442
+ // Create the folder hierarchy and get the ID of the innermost folder
443
+ const folderId = await this.createFolderHierarchy({ folderPath: driveFolder });
444
+ // File metadata
445
+ const fileMetadata = {
446
+ name: finalFileName,
447
+ parents: [folderId],
448
+ };
449
+ // Create media upload
450
+ const media = {
451
+ mimeType: 'application/octet-stream',
452
+ body: createFileReadStream({ filePath: normalizedPath }),
453
+ };
454
+ const file = await driveInstance.files.create({
455
+ requestBody: fileMetadata,
456
+ media: media,
457
+ fields: 'id, name, webViewLink, webContentLink',
458
+ });
459
+ const result = {
460
+ id: file.data.id || '',
461
+ name: file.data.name || '',
462
+ webViewLink: file.data.webViewLink || undefined,
463
+ webContentLink: file.data.webContentLink || undefined,
464
+ };
465
+ // Always make file public
466
+ await this.makeFilePublic({ fileId: result.id });
467
+ return result;
468
+ }
469
+ /**
470
+ * Uploads a file and shares it with a specified email address.
471
+ *
472
+ * @param params - Upload and share parameters
473
+ * @returns A Promise that resolves to the upload result
474
+ *
475
+ * @example
476
+ * ```typescript
477
+ * const result = await client.uploadFileAndShare({
478
+ * localFilePath: './report.pdf',
479
+ * driveFolder: 'SharedReports',
480
+ * shareWithEmail: 'colleague@example.com',
481
+ * role: 'writer'
482
+ * });
483
+ * ```
484
+ */
485
+ async uploadFileAndShare({ localFilePath, driveFolder, fileName, shareWithEmail, role = 'reader', }) {
486
+ // Upload file normally
487
+ const result = await this.uploadFile({
488
+ localFilePath,
489
+ driveFolder,
490
+ fileName,
491
+ });
492
+ // Get folder ID and share it with specified role
493
+ const folderId = await this.getFolderIdByPath({ folderPath: driveFolder });
494
+ await this.shareFolderWithEmail({
495
+ folderId,
496
+ emailAddress: shareWithEmail,
497
+ role,
498
+ });
499
+ return result;
500
+ }
501
+ /**
502
+ * Deletes a file from Google Drive.
503
+ * Note: Only the file owner can delete the file.
504
+ *
505
+ * @param params - Delete parameters including file ID
506
+ * @returns A Promise that resolves to true if deletion was successful
507
+ * @throws Error if deletion fails (e.g., permission denied)
508
+ *
509
+ * @example
510
+ * ```typescript
511
+ * await client.deleteFile({ fileId: '1abc123def456' });
512
+ * ```
513
+ */
514
+ async deleteFile({ fileId }) {
515
+ const driveInstance = await this.getDriveClient();
516
+ await driveInstance.files.delete({
517
+ fileId: fileId,
518
+ });
519
+ return true;
520
+ }
521
+ /**
522
+ * Lists all files and folders in a specified folder.
523
+ *
524
+ * @param params - List parameters including folder ID
525
+ * @returns A Promise that resolves to an array of file information objects
526
+ *
527
+ * @example
528
+ * ```typescript
529
+ * const files = await client.listFilesInFolder({ folderId: 'root' });
530
+ * for (const file of files) {
531
+ * console.log(`${file.name} (${file.mimeType})`);
532
+ * }
533
+ * ```
534
+ */
535
+ async listFilesInFolder({ folderId = 'root' } = {}) {
536
+ const driveInstance = await this.getDriveClient();
537
+ const query = `'${folderId}' in parents and trashed=false`;
538
+ const response = await driveInstance.files.list({
539
+ q: query,
540
+ fields: 'files(id, name, mimeType, webViewLink, parents)',
541
+ orderBy: 'name',
542
+ });
543
+ const files = response.data.files || [];
544
+ return files.map((file) => ({
545
+ id: file.id || '',
546
+ name: file.name || '',
547
+ mimeType: file.mimeType || '',
548
+ webViewLink: file.webViewLink || undefined,
549
+ parents: file.parents || undefined,
550
+ }));
551
+ }
552
+ /**
553
+ * Makes a file publicly accessible to anyone with the link.
554
+ *
555
+ * @param params - Parameters including file ID
556
+ * @returns A Promise that resolves to true if successful
557
+ *
558
+ * @example
559
+ * ```typescript
560
+ * await client.makeFilePublic({ fileId: '1abc123def456' });
561
+ * // File is now accessible via its webViewLink
562
+ * ```
563
+ */
564
+ async makeFilePublic({ fileId }) {
565
+ const driveInstance = await this.getDriveClient();
566
+ await driveInstance.permissions.create({
567
+ fileId: fileId,
568
+ requestBody: {
569
+ role: 'reader',
570
+ type: 'anyone',
571
+ },
572
+ });
573
+ return true;
574
+ }
575
+ /**
576
+ * Transfers ownership of a file to another user.
577
+ *
578
+ * @param params - Transfer parameters including file ID and new owner email
579
+ * @returns A Promise that resolves to true if successful
580
+ *
581
+ * @example
582
+ * ```typescript
583
+ * await client.transferFileOwnership({
584
+ * fileId: '1abc123def456',
585
+ * newOwnerEmail: 'newowner@example.com'
586
+ * });
587
+ * ```
588
+ */
589
+ async transferFileOwnership({ fileId, newOwnerEmail, role = 'reader', }) {
590
+ const driveInstance = await this.getDriveClient();
591
+ await driveInstance.permissions.create({
592
+ fileId: fileId,
593
+ requestBody: {
594
+ role: role,
595
+ type: 'user',
596
+ emailAddress: newOwnerEmail,
597
+ },
598
+ transferOwnership: true,
599
+ sendNotificationEmail: true,
600
+ });
601
+ return true;
602
+ }
603
+ /**
604
+ * Shares a folder with a specified email address.
605
+ *
606
+ * @param params - Share parameters including folder ID, email, and role
607
+ * @returns A Promise that resolves to true if successful
608
+ *
609
+ * @example
610
+ * ```typescript
611
+ * await client.shareFolderWithEmail({
612
+ * folderId: '1abc123def456',
613
+ * emailAddress: 'user@example.com',
614
+ * role: 'writer'
615
+ * });
616
+ * ```
617
+ */
618
+ async shareFolderWithEmail({ folderId, emailAddress, role = 'writer', }) {
619
+ const driveInstance = await this.getDriveClient();
620
+ const requestBody = {
621
+ role: role,
622
+ type: 'user',
623
+ emailAddress: emailAddress,
624
+ };
625
+ const requestOptions = {
626
+ fileId: folderId,
627
+ requestBody: requestBody,
628
+ };
629
+ // Configure notification email based on role
630
+ if (role === 'owner') {
631
+ requestOptions.transferOwnership = true;
632
+ requestOptions.sendNotificationEmail = true;
633
+ }
634
+ else {
635
+ requestOptions.sendNotificationEmail = false;
636
+ }
637
+ await driveInstance.permissions.create(requestOptions);
638
+ return true;
639
+ }
640
+ /**
641
+ * Gets a folder ID by its path, creating the folder hierarchy if it doesn't exist.
642
+ *
643
+ * @param params - Parameters including folder path
644
+ * @returns A Promise that resolves to the folder ID
645
+ *
646
+ * @example
647
+ * ```typescript
648
+ * const folderId = await client.getFolderIdByPath({
649
+ * folderPath: 'folder1/folder2/folder3'
650
+ * });
651
+ * ```
652
+ */
653
+ async getFolderIdByPath({ folderPath }) {
654
+ return this.createFolderHierarchy({ folderPath });
655
+ }
656
+ /**
657
+ * Checks if a file with the specified name exists in a folder.
658
+ *
659
+ * @param params - Parameters including file name and folder ID
660
+ * @returns A Promise that resolves to the file ID if found, or null if not found
661
+ *
662
+ * @example
663
+ * ```typescript
664
+ * const fileId = await client.fileExistsInFolder({
665
+ * fileName: 'document.pdf',
666
+ * folderId: 'root'
667
+ * });
668
+ * if (fileId) {
669
+ * console.log(`File exists with ID: ${fileId}`);
670
+ * }
671
+ * ```
672
+ */
673
+ async fileExistsInFolder({ fileName, folderId = 'root', }) {
674
+ const driveInstance = await this.getDriveClient();
675
+ const query = `name='${fileName}' and '${folderId}' in parents and trashed=false`;
676
+ const response = await driveInstance.files.list({
677
+ q: query,
678
+ fields: 'files(id, name)',
679
+ });
680
+ const files = response.data.files || [];
681
+ if (files.length > 0 && files[0].id) {
682
+ return files[0].id;
683
+ }
684
+ return null;
685
+ }
686
+ }
687
+
688
+ /**
689
+ * Google Drive Feature Library
690
+ * @description A framework-agnostic library for interacting with Google Drive API.
691
+ * Provides easy-to-use methods for file upload, sharing, and storage management.
692
+ * @module gg-drive
693
+ *
694
+ * @example
695
+ * ```typescript
696
+ * import { GoogleDriveClient } from 'bodevops-features/gg-drive';
697
+ *
698
+ * const client = new GoogleDriveClient({
699
+ * keyFilePath: './service-account.json'
700
+ * });
701
+ *
702
+ * // Upload a file
703
+ * const result = await client.uploadFile({
704
+ * localFilePath: './document.pdf',
705
+ * driveFolder: 'MyFolder/Documents'
706
+ * });
707
+ *
708
+ * // Get storage info
709
+ * const storage = await client.getStorageInfo();
710
+ * console.log(`Used: ${storage.formattedUsed}`);
711
+ * ```
712
+ */
713
+ // Export main client class
714
+
715
+ var index$1 = /*#__PURE__*/Object.freeze({
716
+ __proto__: null,
717
+ DEFAULT_DRIVE_SCOPES: DEFAULT_DRIVE_SCOPES,
718
+ GoogleDriveClient: GoogleDriveClient,
719
+ GoogleDriveConfig: GoogleDriveConfig,
720
+ createFileReadStream: createFileReadStream,
721
+ formatBytes: formatBytes,
722
+ getFileInfo: getFileInfo,
723
+ normalizeFilePath: normalizeFilePath,
724
+ parseFolderPath: parseFolderPath,
725
+ validateFileExists: validateFileExists
726
+ });
727
+
728
+ /**
729
+ * Google Sheet Feature Library - Configuration Module
730
+ * @description Configuration management for Google Sheet client initialization and authentication.
731
+ * @module gg-sheet/config
732
+ */
733
+ /**
734
+ * Default OAuth scope required for Google Sheets operations.
735
+ */
736
+ const DEFAULT_SHEET_SCOPES = ['https://www.googleapis.com/auth/spreadsheets'];
737
+ /**
738
+ * Google Sheet Configuration Manager.
739
+ * Handles validation and normalization of configuration options for the Google Sheet client.
740
+ *
741
+ * @example
742
+ * ```typescript
743
+ * // Using key file path
744
+ * const config = new GoogleSheetConfig({
745
+ * keyFilePath: './service-account.json'
746
+ * });
747
+ *
748
+ * // Using credentials object
749
+ * const config = new GoogleSheetConfig({
750
+ * credentials: {
751
+ * type: 'service_account',
752
+ * project_id: 'my-project',
753
+ * private_key: '-----BEGIN PRIVATE KEY-----\n...',
754
+ * client_email: 'service-account@my-project.iam.gserviceaccount.com',
755
+ * // ... other fields
756
+ * }
757
+ * });
758
+ * ```
759
+ */
760
+ class GoogleSheetConfig {
761
+ /**
762
+ * Creates a new GoogleSheetConfig instance.
763
+ *
764
+ * @param config - Configuration options for Google Sheet client
765
+ * @throws Error if neither keyFilePath nor credentials is provided
766
+ */
767
+ constructor({ keyFilePath, credentials, scopes }) {
768
+ // Validate that at least one authentication method is provided
769
+ if (!keyFilePath && !credentials) {
770
+ throw new Error('GoogleSheetConfig: Either keyFilePath or credentials must be provided');
771
+ }
772
+ this.keyFilePath = keyFilePath;
773
+ this.credentials = credentials;
774
+ this.scopes = scopes || DEFAULT_SHEET_SCOPES;
775
+ // Validate credentials structure if provided
776
+ if (credentials) {
777
+ this.validateCredentials(credentials);
778
+ }
779
+ }
780
+ /**
781
+ * Validates that the credentials object contains all required fields.
782
+ *
783
+ * @param credentials - The credentials object to validate
784
+ * @throws Error if required fields are missing
785
+ */
786
+ validateCredentials(credentials) {
787
+ const requiredFields = [
788
+ 'type',
789
+ 'project_id',
790
+ 'private_key',
791
+ 'client_email',
792
+ ];
793
+ for (const field of requiredFields) {
794
+ if (!credentials[field]) {
795
+ throw new Error(`GoogleSheetConfig: Missing required credential field: ${field}`);
796
+ }
797
+ }
798
+ if (credentials.type !== 'service_account') {
799
+ throw new Error(`GoogleSheetConfig: Invalid credential type. Expected 'service_account', got '${credentials.type}'`);
800
+ }
801
+ }
802
+ /**
803
+ * Returns the authentication configuration object suitable for googleapis.
804
+ *
805
+ * @returns Authentication options for Google Auth
806
+ */
807
+ getAuthOptions() {
808
+ if (this.keyFilePath) {
809
+ return {
810
+ keyFile: this.keyFilePath,
811
+ scopes: this.scopes,
812
+ };
813
+ }
814
+ return {
815
+ credentials: this.credentials,
816
+ scopes: this.scopes,
817
+ };
818
+ }
819
+ }
820
+
821
+ /**
822
+ * Google Sheet Feature Library - Type Definitions
823
+ * @description Type definitions for Google Sheet operations including reading, writing, and exporting data.
824
+ * @module gg-sheet/types
825
+ */
826
+ /**
827
+ * Export type enumeration for sheet export operations.
828
+ */
829
+ var ETypeExport;
830
+ (function (ETypeExport) {
831
+ /** Append data to the end of existing data */
832
+ ETypeExport["Append"] = "Append";
833
+ /** Overwrite all existing data starting from row 1 */
834
+ ETypeExport["Overwrite"] = "Overwrite";
835
+ })(ETypeExport || (ETypeExport = {}));
836
+
837
+ /**
838
+ * Google Sheet Feature Library - Utility Functions
839
+ * @description Helper functions for sheet operations, column conversion, and data transformation.
840
+ * @module gg-sheet/utils
841
+ */
842
+ /**
843
+ * Regular expression pattern to extract spreadsheet ID from a Google Sheets URL.
844
+ */
845
+ const SPREADSHEET_ID_PATTERN = /\/spreadsheets\/d\/([a-zA-Z0-9-_]+)/;
846
+ /**
847
+ * Extracts the spreadsheet ID from a Google Sheets URL.
848
+ *
849
+ * @param sheetUrl - The full URL of the Google Spreadsheet
850
+ * @returns The spreadsheet ID or null if not found
851
+ *
852
+ * @example
853
+ * ```typescript
854
+ * const id = getSheetIdFromUrl({
855
+ * sheetUrl: 'https://docs.google.com/spreadsheets/d/1abc123def/edit'
856
+ * });
857
+ * // Returns: '1abc123def'
858
+ * ```
859
+ */
860
+ function getSheetIdFromUrl({ sheetUrl }) {
861
+ const match = sheetUrl.match(SPREADSHEET_ID_PATTERN);
862
+ if (match && match[1]) {
863
+ return match[1];
864
+ }
865
+ return null;
866
+ }
867
+ /**
868
+ * Converts a 0-based column index to a column letter (e.g., 0 → A, 25 → Z, 26 → AA).
869
+ *
870
+ * @param columnIndex - The 0-based column index
871
+ * @returns The column letter(s) (e.g., "A", "B", "AA", "AB")
872
+ *
873
+ * @example
874
+ * ```typescript
875
+ * convertIndexToColumnName({ columnIndex: 0 }); // "A"
876
+ * convertIndexToColumnName({ columnIndex: 25 }); // "Z"
877
+ * convertIndexToColumnName({ columnIndex: 26 }); // "AA"
878
+ * convertIndexToColumnName({ columnIndex: 701 }); // "ZZ"
879
+ * ```
880
+ */
881
+ function convertIndexToColumnName({ columnIndex }) {
882
+ let columnName = '';
883
+ let index = columnIndex;
884
+ while (index >= 0) {
885
+ columnName = String.fromCharCode((index % 26) + 'A'.charCodeAt(0)) + columnName;
886
+ index = Math.floor(index / 26) - 1;
887
+ }
888
+ return columnName;
889
+ }
890
+ /**
891
+ * Converts a column letter to a 0-based column index (e.g., A → 0, Z → 25, AA → 26).
892
+ *
893
+ * @param columnName - The column letter(s) (e.g., "A", "B", "AA")
894
+ * @returns The 0-based column index
895
+ * @throws Error if the column name contains invalid characters
896
+ *
897
+ * @example
898
+ * ```typescript
899
+ * convertColumnNameToIndex({ columnName: 'A' }); // 0
900
+ * convertColumnNameToIndex({ columnName: 'Z' }); // 25
901
+ * convertColumnNameToIndex({ columnName: 'AA' }); // 26
902
+ * convertColumnNameToIndex({ columnName: 'ZZ' }); // 701
903
+ * ```
904
+ */
905
+ function convertColumnNameToIndex({ columnName }) {
906
+ // Convert to uppercase to handle both lower and upper case
907
+ const upperColumnName = columnName.toUpperCase().trim();
908
+ // Validate input - only accept A-Z characters
909
+ if (!/^[A-Z]+$/.test(upperColumnName)) {
910
+ throw new Error(`Invalid column name: '${columnName}'. Only letters A-Z are allowed.`);
911
+ }
912
+ let result = 0;
913
+ for (let i = 0; i < upperColumnName.length; i++) {
914
+ const charCode = upperColumnName.charCodeAt(i) - 'A'.charCodeAt(0);
915
+ result = result * 26 + (charCode + 1);
916
+ }
917
+ // Convert to 0-based index
918
+ return result - 1;
919
+ }
920
+ /**
921
+ * Converts raw sheet values (2D array) into an array of typed objects.
922
+ * Uses the first row (or row at rowOffset) as keys for the objects.
923
+ *
924
+ * @param values - Raw 2D array from sheet
925
+ * @param rowOffset - Number of rows to skip before the header row (default: 0)
926
+ * @returns Array of typed objects, or null if values is null/undefined
927
+ *
928
+ * @example
929
+ * ```typescript
930
+ * const rawData = [
931
+ * ['name', 'age', 'email'],
932
+ * ['John', '30', 'john@example.com'],
933
+ * ['Jane', '25', 'jane@example.com']
934
+ * ];
935
+ *
936
+ * interface Person { name: string; age: string; email: string; }
937
+ *
938
+ * const people = convertValueSheet<Person>({ values: rawData });
939
+ * // Returns: [
940
+ * // { name: 'John', age: '30', email: 'john@example.com' },
941
+ * // { name: 'Jane', age: '25', email: 'jane@example.com' }
942
+ * // ]
943
+ * ```
944
+ */
945
+ function convertValueSheet({ values, rowOffset = 0, }) {
946
+ if (!values || values.length === 0) {
947
+ return null;
948
+ }
949
+ // Get header row (keys for the objects)
950
+ const keys = values[rowOffset];
951
+ if (!keys || keys.length === 0) {
952
+ return null;
953
+ }
954
+ // Map remaining rows to objects
955
+ return values.slice(rowOffset + 1).map((row) => {
956
+ return keys.reduce((acc, key, index) => {
957
+ acc[key] = row[index] || '';
958
+ return acc;
959
+ }, {});
960
+ });
961
+ }
962
+ /**
963
+ * Gets the index of a column key in the list of keys.
964
+ *
965
+ * @param key - The key to find
966
+ * @param listKeys - Array of all keys
967
+ * @returns The index of the key, or -1 if not found
968
+ *
969
+ * @example
970
+ * ```typescript
971
+ * interface Person { id: string; name: string; email: string; }
972
+ * const keys: (keyof Person)[] = ['id', 'name', 'email'];
973
+ *
974
+ * getIndexCol({ key: 'name', listKeys: keys }); // 1
975
+ * getIndexCol({ key: 'email', listKeys: keys }); // 2
976
+ * ```
977
+ */
978
+ function getIndexCol({ key, listKeys }) {
979
+ return listKeys.indexOf(key);
980
+ }
981
+ /**
982
+ * Extracts column headers and data values from a result set for export.
983
+ * Takes an object mapping field keys to column names and an array of items.
984
+ *
985
+ * @param colsForSheet - Object mapping field keys to column header names
986
+ * @param resultItems - Array of items to export
987
+ * @returns Object containing listCols (headers) and valsExport (data matrix)
988
+ *
989
+ * @example
990
+ * ```typescript
991
+ * const colsMapping = { id: 'ID', name: 'Full Name', email: 'Email Address' };
992
+ * const items = [
993
+ * { id: '1', name: 'John Doe', email: 'john@example.com' },
994
+ * { id: '2', name: 'Jane Doe', email: 'jane@example.com' }
995
+ * ];
996
+ *
997
+ * const { listCols, valsExport } = getListColsAndValsExport({
998
+ * colsForSheet: colsMapping,
999
+ * resultItems: items
1000
+ * });
1001
+ * // listCols: ['ID', 'Full Name', 'Email Address']
1002
+ * // valsExport: [['1', 'John Doe', 'john@example.com'], ['2', 'Jane Doe', 'jane@example.com']]
1003
+ * ```
1004
+ */
1005
+ function getListColsAndValsExport({ colsForSheet, resultItems, }) {
1006
+ // Extract column headers from the mapping values
1007
+ const listCols = Object.values(colsForSheet);
1008
+ // Extract data rows
1009
+ const valsExport = [];
1010
+ for (const item of resultItems) {
1011
+ const row = [];
1012
+ // Iterate through colsForSheet keys to ensure correct order
1013
+ for (const fieldKey of Object.keys(colsForSheet)) {
1014
+ const fieldValue = item[fieldKey];
1015
+ // Convert value to string, handling different data types
1016
+ let cellValue = '';
1017
+ if (fieldValue === null || fieldValue === undefined) {
1018
+ cellValue = '';
1019
+ }
1020
+ else if (fieldValue instanceof Date) {
1021
+ cellValue = fieldValue.toISOString();
1022
+ }
1023
+ else if (typeof fieldValue === 'object') {
1024
+ // Handle nested objects (like relations)
1025
+ cellValue = JSON.stringify(fieldValue);
1026
+ }
1027
+ else {
1028
+ cellValue = String(fieldValue);
1029
+ }
1030
+ row.push(cellValue);
1031
+ }
1032
+ valsExport.push(row);
1033
+ }
1034
+ return { listCols, valsExport };
1035
+ }
1036
+ /**
1037
+ * Validates that a sheet URL is in the correct format.
1038
+ *
1039
+ * @param sheetUrl - The URL to validate
1040
+ * @returns True if the URL is valid, false otherwise
1041
+ *
1042
+ * @example
1043
+ * ```typescript
1044
+ * isValidSheetUrl({ sheetUrl: 'https://docs.google.com/spreadsheets/d/1abc123/edit' }); // true
1045
+ * isValidSheetUrl({ sheetUrl: 'https://example.com/sheet' }); // false
1046
+ * ```
1047
+ */
1048
+ function isValidSheetUrl({ sheetUrl }) {
1049
+ return SPREADSHEET_ID_PATTERN.test(sheetUrl);
1050
+ }
1051
+ /**
1052
+ * Calculates the actual row index in the sheet based on the data row index and offset.
1053
+ * This accounts for header row(s) and any additional offset rows.
1054
+ *
1055
+ * @param dataRowIndex - The 0-based index in the data (not counting headers)
1056
+ * @param rowOffset - Additional rows to skip after the header (default: 0)
1057
+ * @returns The 1-based row number in the actual sheet
1058
+ *
1059
+ * @example
1060
+ * ```typescript
1061
+ * // With rowOffset=0: header at row 1, data starts at row 2
1062
+ * calculateActualRow({ dataRowIndex: 0, rowOffset: 0 }); // 2 (first data row)
1063
+ * calculateActualRow({ dataRowIndex: 5, rowOffset: 0 }); // 7 (sixth data row)
1064
+ *
1065
+ * // With rowOffset=1: header at row 1, skip row 2, data starts at row 3
1066
+ * calculateActualRow({ dataRowIndex: 0, rowOffset: 1 }); // 3 (first data row)
1067
+ * ```
1068
+ */
1069
+ function calculateActualRow({ dataRowIndex, rowOffset = 0, }) {
1070
+ // Row 1 is header, so data starts at row 2
1071
+ // Add 2 for: 0-based to 1-based conversion + header row
1072
+ return dataRowIndex + 2 + rowOffset;
1073
+ }
1074
+
1075
+ /**
1076
+ * Google Sheet Feature Library - Main Client Class
1077
+ * @description A framework-agnostic client for interacting with Google Sheets API.
1078
+ * Provides methods for reading, writing, and managing spreadsheet data.
1079
+ * @module gg-sheet/google-sheet
1080
+ */
1081
+ /**
1082
+ * Google Sheet Client for managing spreadsheet data.
1083
+ *
1084
+ * @example
1085
+ * ```typescript
1086
+ * import { GoogleSheetClient } from 'bodevops-features/gg-sheet';
1087
+ *
1088
+ * const client = new GoogleSheetClient({
1089
+ * keyFilePath: './service-account.json'
1090
+ * });
1091
+ *
1092
+ * // Read data from a sheet
1093
+ * const data = await client.getValues({
1094
+ * sheetUrl: 'https://docs.google.com/spreadsheets/d/...',
1095
+ * sheetName: 'Sheet1'
1096
+ * });
1097
+ *
1098
+ * // Update specific cells
1099
+ * await client.updateValuesMultiCells({
1100
+ * sheetUrl: 'https://docs.google.com/spreadsheets/d/...',
1101
+ * sheetName: 'Sheet1',
1102
+ * cells: [
1103
+ * { row: 0, col: 0, content: 'Hello' },
1104
+ * { row: 0, col: 1, content: 'World' }
1105
+ * ]
1106
+ * });
1107
+ * ```
1108
+ */
1109
+ class GoogleSheetClient {
1110
+ /**
1111
+ * Creates a new GoogleSheetClient instance.
1112
+ *
1113
+ * @param configOptions - Configuration options for the Google Sheet client
1114
+ */
1115
+ constructor(configOptions) {
1116
+ this.config = new GoogleSheetConfig(configOptions);
1117
+ }
1118
+ /**
1119
+ * Creates and returns an authenticated Google Sheets API client.
1120
+ *
1121
+ * @returns A Promise that resolves to an authenticated Sheets API client
1122
+ */
1123
+ async getSheetsClient() {
1124
+ const authOptions = this.config.getAuthOptions();
1125
+ const auth = new googleapis.google.auth.GoogleAuth({
1126
+ keyFile: authOptions.keyFile,
1127
+ credentials: authOptions.credentials,
1128
+ scopes: authOptions.scopes,
1129
+ });
1130
+ const authClient = await auth.getClient();
1131
+ return googleapis.google.sheets({
1132
+ version: 'v4',
1133
+ auth: authClient,
1134
+ });
1135
+ }
1136
+ /**
1137
+ * Extracts the spreadsheet ID from a URL and validates it.
1138
+ *
1139
+ * @param sheetUrl - The Google Sheets URL
1140
+ * @returns The spreadsheet ID
1141
+ * @throws Error if the URL is invalid
1142
+ */
1143
+ extractSheetId({ sheetUrl }) {
1144
+ const sheetId = getSheetIdFromUrl({ sheetUrl });
1145
+ if (!sheetId) {
1146
+ throw new Error(`Invalid Google Sheet URL: ${sheetUrl}`);
1147
+ }
1148
+ return sheetId;
1149
+ }
1150
+ /**
1151
+ * Retrieves information about a Google Spreadsheet, including all sheet tabs.
1152
+ *
1153
+ * @param params - Parameters including the sheet URL
1154
+ * @returns A Promise that resolves to spreadsheet information
1155
+ *
1156
+ * @example
1157
+ * ```typescript
1158
+ * const info = await client.getSheetInfo({
1159
+ * sheetUrl: 'https://docs.google.com/spreadsheets/d/...'
1160
+ * });
1161
+ * console.log(`Spreadsheet: ${info.spreadsheetTitle}`);
1162
+ * console.log(`Sheets: ${info.sheets.map(s => s.title).join(', ')}`);
1163
+ * ```
1164
+ */
1165
+ async getSheetInfo({ sheetUrl }) {
1166
+ const sheetsInstance = await this.getSheetsClient();
1167
+ const spreadsheetId = this.extractSheetId({ sheetUrl });
1168
+ const response = await sheetsInstance.spreadsheets.get({
1169
+ spreadsheetId,
1170
+ includeGridData: false,
1171
+ });
1172
+ const spreadsheetTitle = response.data.properties?.title || '';
1173
+ const sheets = response.data.sheets?.map((sheet) => ({
1174
+ title: sheet.properties?.title || '',
1175
+ sheetId: sheet.properties?.sheetId || 0,
1176
+ rowCount: sheet.properties?.gridProperties?.rowCount || 0,
1177
+ columnCount: sheet.properties?.gridProperties?.columnCount || 0,
1178
+ })) || [];
1179
+ return {
1180
+ spreadsheetTitle,
1181
+ sheets,
1182
+ };
1183
+ }
1184
+ /**
1185
+ * Reads all values from a specific sheet tab.
1186
+ *
1187
+ * @param params - Parameters including sheet URL, sheet name, and optional row limit
1188
+ * @returns A Promise that resolves to a 2D array of string values
1189
+ *
1190
+ * @example
1191
+ * ```typescript
1192
+ * const data = await client.getValues({
1193
+ * sheetUrl: 'https://docs.google.com/spreadsheets/d/...',
1194
+ * sheetName: 'Sheet1',
1195
+ * endRow: 100 // Optional: limit to first 100 rows
1196
+ * });
1197
+ *
1198
+ * for (const row of data) {
1199
+ * console.log(row.join(', '));
1200
+ * }
1201
+ * ```
1202
+ */
1203
+ async getValues({ sheetUrl, sheetName, endRow }) {
1204
+ const sheetsInstance = await this.getSheetsClient();
1205
+ const spreadsheetId = this.extractSheetId({ sheetUrl });
1206
+ // Get sheet info to determine column count
1207
+ const sheetInfo = await this.getSheetInfo({ sheetUrl });
1208
+ const sheet = sheetInfo.sheets.find((s) => s.title === sheetName);
1209
+ if (!sheet) {
1210
+ throw new Error(`Sheet not found: ${sheetName}`);
1211
+ }
1212
+ // Build the range
1213
+ let range = sheetName;
1214
+ if (endRow) {
1215
+ const endCol = convertIndexToColumnName({
1216
+ columnIndex: sheet.columnCount - 1,
1217
+ });
1218
+ range = `${sheetName}!A1:${endCol}${endRow}`;
1219
+ }
1220
+ const result = await sheetsInstance.spreadsheets.values.get({
1221
+ spreadsheetId,
1222
+ range,
1223
+ });
1224
+ return result.data.values || [];
1225
+ }
1226
+ /**
1227
+ * Finds the row index (0-based) where a specific value appears in a column.
1228
+ *
1229
+ * @param params - Parameters including sheet URL, sheet name, column name, and value to find
1230
+ * @returns A Promise that resolves to the row index (0-based), or -1 if not found
1231
+ *
1232
+ * @example
1233
+ * ```typescript
1234
+ * const rowIndex = await client.getIdxRow({
1235
+ * sheetUrl: 'https://docs.google.com/spreadsheets/d/...',
1236
+ * sheetName: 'Sheet1',
1237
+ * colName: 'A',
1238
+ * value: 'John Doe'
1239
+ * });
1240
+ *
1241
+ * if (rowIndex >= 0) {
1242
+ * console.log(`Found at row index: ${rowIndex}`);
1243
+ * }
1244
+ * ```
1245
+ */
1246
+ async getIdxRow({ sheetUrl, sheetName, colName, value, }) {
1247
+ const sheetsInstance = await this.getSheetsClient();
1248
+ const spreadsheetId = this.extractSheetId({ sheetUrl });
1249
+ // Get sheet info to determine row count
1250
+ const sheetInfo = await this.getSheetInfo({ sheetUrl });
1251
+ const sheet = sheetInfo.sheets.find((s) => s.title === sheetName);
1252
+ if (!sheet) {
1253
+ throw new Error(`Sheet not found: ${sheetName}`);
1254
+ }
1255
+ const range = `${sheetName}!${colName}1:${colName}${sheet.rowCount}`;
1256
+ const result = await sheetsInstance.spreadsheets.values.get({
1257
+ spreadsheetId,
1258
+ range,
1259
+ });
1260
+ const values = result.data.values || [];
1261
+ // Find the index (0-based)
1262
+ const index = values.findIndex((row) => {
1263
+ if (!row || row.length === 0)
1264
+ return false;
1265
+ return row[0] === value;
1266
+ });
1267
+ return index;
1268
+ }
1269
+ /**
1270
+ * Exports data to a Google Sheet with either Append or Overwrite mode.
1271
+ *
1272
+ * @param params - Export parameters including data and export type
1273
+ * @returns A Promise that resolves to true if successful
1274
+ *
1275
+ * @example
1276
+ * ```typescript
1277
+ * // Overwrite existing data
1278
+ * await client.export({
1279
+ * sheetUrl: 'https://docs.google.com/spreadsheets/d/...',
1280
+ * sheetName: 'Sheet1',
1281
+ * listCols: ['Name', 'Email', 'Age'],
1282
+ * valsExport: [
1283
+ * ['John', 'john@example.com', '30'],
1284
+ * ['Jane', 'jane@example.com', '25']
1285
+ * ],
1286
+ * typeExport: ETypeExport.Overwrite
1287
+ * });
1288
+ *
1289
+ * // Append to existing data
1290
+ * await client.export({
1291
+ * sheetUrl: 'https://docs.google.com/spreadsheets/d/...',
1292
+ * sheetName: 'Sheet1',
1293
+ * listCols: ['Name', 'Email', 'Age'],
1294
+ * valsExport: [['New User', 'new@example.com', '28']],
1295
+ * typeExport: ETypeExport.Append
1296
+ * });
1297
+ * ```
1298
+ */
1299
+ async export({ sheetUrl, sheetName, listCols, valsExport, typeExport, }) {
1300
+ const sheetsInstance = await this.getSheetsClient();
1301
+ const spreadsheetId = this.extractSheetId({ sheetUrl });
1302
+ if (typeExport === ETypeExport.Overwrite) {
1303
+ return this.executeOverwriteExport({
1304
+ sheetsInstance,
1305
+ spreadsheetId,
1306
+ sheetName,
1307
+ listCols,
1308
+ valsExport,
1309
+ });
1310
+ }
1311
+ else if (typeExport === ETypeExport.Append) {
1312
+ return this.executeAppendExport({
1313
+ sheetsInstance,
1314
+ spreadsheetId,
1315
+ sheetName,
1316
+ listCols,
1317
+ valsExport,
1318
+ });
1319
+ }
1320
+ else {
1321
+ throw new Error(`Invalid export type: ${typeExport}`);
1322
+ }
1323
+ }
1324
+ /**
1325
+ * Executes an overwrite export - writes headers and data from row 1.
1326
+ */
1327
+ async executeOverwriteExport({ sheetsInstance, spreadsheetId, sheetName, listCols, valsExport, }) {
1328
+ // Prepare data matrix: headers + data rows
1329
+ const exportData = [listCols, ...valsExport];
1330
+ const numCols = listCols.length;
1331
+ const numRows = exportData.length;
1332
+ // Calculate range: A1 to [LastCol][LastRow]
1333
+ const endCol = convertIndexToColumnName({ columnIndex: numCols - 1 });
1334
+ const range = `${sheetName}!A1:${endCol}${numRows}`;
1335
+ await sheetsInstance.spreadsheets.values.batchUpdate({
1336
+ spreadsheetId,
1337
+ requestBody: {
1338
+ valueInputOption: 'RAW',
1339
+ data: [
1340
+ {
1341
+ range,
1342
+ values: exportData,
1343
+ },
1344
+ ],
1345
+ },
1346
+ });
1347
+ return true;
1348
+ }
1349
+ /**
1350
+ * Executes an append export - finds empty rows and appends data.
1351
+ */
1352
+ async executeAppendExport({ sheetsInstance, spreadsheetId, sheetName, listCols, valsExport, }) {
1353
+ // Get sheet info to determine max rows
1354
+ const sheetInfo = await sheetsInstance.spreadsheets.get({
1355
+ spreadsheetId,
1356
+ ranges: [sheetName],
1357
+ includeGridData: false,
1358
+ });
1359
+ const sheet = sheetInfo.data.sheets?.find((s) => s.properties?.title === sheetName);
1360
+ const maxRows = sheet?.properties?.gridProperties?.rowCount || 1000;
1361
+ // Read current data to find the last used row
1362
+ const readRange = `${sheetName}!A1:A${maxRows}`;
1363
+ const readResponse = await sheetsInstance.spreadsheets.values.get({
1364
+ spreadsheetId,
1365
+ range: readRange,
1366
+ });
1367
+ const currentData = readResponse.data.values || [];
1368
+ // Find the first empty row (last row with data + 1)
1369
+ let startRow = 1;
1370
+ if (currentData.length > 0) {
1371
+ let lastUsedRow = 0;
1372
+ for (let i = currentData.length - 1; i >= 0; i--) {
1373
+ if (currentData[i] &&
1374
+ currentData[i].length > 0 &&
1375
+ currentData[i][0] &&
1376
+ String(currentData[i][0]).trim() !== '') {
1377
+ lastUsedRow = i + 1;
1378
+ break;
1379
+ }
1380
+ }
1381
+ startRow = lastUsedRow + 1;
1382
+ }
1383
+ // Check if we need to write headers
1384
+ let dataToWrite = valsExport;
1385
+ const writeStartRow = startRow;
1386
+ // If sheet is empty, include headers
1387
+ if (startRow === 1) {
1388
+ dataToWrite = [listCols, ...valsExport];
1389
+ }
1390
+ // Calculate range for writing
1391
+ const numCols = listCols.length;
1392
+ const numRows = dataToWrite.length;
1393
+ const endCol = convertIndexToColumnName({ columnIndex: numCols - 1 });
1394
+ const endRow = writeStartRow + numRows - 1;
1395
+ const writeRange = `${sheetName}!A${writeStartRow}:${endCol}${endRow}`;
1396
+ await sheetsInstance.spreadsheets.values.batchUpdate({
1397
+ spreadsheetId,
1398
+ requestBody: {
1399
+ valueInputOption: 'RAW',
1400
+ data: [
1401
+ {
1402
+ range: writeRange,
1403
+ values: dataToWrite,
1404
+ },
1405
+ ],
1406
+ },
1407
+ });
1408
+ return true;
1409
+ }
1410
+ /**
1411
+ * Updates multiple cells at specific row and column positions.
1412
+ *
1413
+ * @param params - Update parameters including cells to update
1414
+ * @returns A Promise that resolves to true if successful
1415
+ *
1416
+ * @example
1417
+ * ```typescript
1418
+ * await client.updateValuesMultiCells({
1419
+ * sheetUrl: 'https://docs.google.com/spreadsheets/d/...',
1420
+ * sheetName: 'Sheet1',
1421
+ * cells: [
1422
+ * { row: 0, col: 0, content: 'Updated A2' },
1423
+ * { row: 1, col: 1, content: 'Updated B3' },
1424
+ * { row: 2, col: 2, content: 'Updated C4' }
1425
+ * ],
1426
+ * rowOffset: 0 // Header at row 1, data starts at row 2
1427
+ * });
1428
+ * ```
1429
+ */
1430
+ async updateValuesMultiCells({ sheetUrl, sheetName, cells, rowOffset = 0, }) {
1431
+ if (!cells || cells.length === 0) {
1432
+ throw new Error('No cells provided for update');
1433
+ }
1434
+ const sheetsInstance = await this.getSheetsClient();
1435
+ const spreadsheetId = this.extractSheetId({ sheetUrl });
1436
+ // Create update requests for each cell
1437
+ const requests = cells.map((cell) => {
1438
+ const colName = convertIndexToColumnName({ columnIndex: cell.col });
1439
+ const actualRow = calculateActualRow({
1440
+ dataRowIndex: cell.row,
1441
+ rowOffset,
1442
+ });
1443
+ const sheetRange = `${sheetName}!${colName}${actualRow}`;
1444
+ return {
1445
+ range: sheetRange,
1446
+ values: [[cell.content]],
1447
+ };
1448
+ });
1449
+ await sheetsInstance.spreadsheets.values.batchUpdate({
1450
+ spreadsheetId,
1451
+ requestBody: {
1452
+ valueInputOption: 'RAW',
1453
+ data: requests,
1454
+ },
1455
+ });
1456
+ return true;
1457
+ }
1458
+ /**
1459
+ * Updates multiple columns in a single row.
1460
+ *
1461
+ * @param params - Update parameters including row and column values
1462
+ * @returns A Promise that resolves to true if successful
1463
+ *
1464
+ * @example
1465
+ * ```typescript
1466
+ * await client.updateValuesMultiColsByRow({
1467
+ * sheetUrl: 'https://docs.google.com/spreadsheets/d/...',
1468
+ * sheetName: 'Sheet1',
1469
+ * row: 5,
1470
+ * values: [
1471
+ * { col: 0, content: 'Value for A7' },
1472
+ * { col: 1, content: 'Value for B7' },
1473
+ * { col: 2, content: 'Value for C7' }
1474
+ * ]
1475
+ * });
1476
+ * ```
1477
+ */
1478
+ async updateValuesMultiColsByRow({ sheetUrl, sheetName, row, values, rowOffset = 0, }) {
1479
+ if (!values || values.length === 0) {
1480
+ throw new Error('No values provided for update');
1481
+ }
1482
+ const sheetsInstance = await this.getSheetsClient();
1483
+ const spreadsheetId = this.extractSheetId({ sheetUrl });
1484
+ const actualRow = calculateActualRow({ dataRowIndex: row, rowOffset });
1485
+ const requests = values.map((valPair) => {
1486
+ const colName = convertIndexToColumnName({ columnIndex: valPair.col });
1487
+ const sheetRange = `${sheetName}!${colName}${actualRow}`;
1488
+ return {
1489
+ range: sheetRange,
1490
+ values: [[valPair.content]],
1491
+ };
1492
+ });
1493
+ await sheetsInstance.spreadsheets.values.batchUpdate({
1494
+ spreadsheetId,
1495
+ requestBody: {
1496
+ valueInputOption: 'RAW',
1497
+ data: requests,
1498
+ },
1499
+ });
1500
+ return true;
1501
+ }
1502
+ /**
1503
+ * Updates multiple rows in a single column.
1504
+ *
1505
+ * @param params - Update parameters including column and row values
1506
+ * @returns A Promise that resolves to true if successful
1507
+ *
1508
+ * @example
1509
+ * ```typescript
1510
+ * await client.updateValuesMultiRowsByCol({
1511
+ * sheetUrl: 'https://docs.google.com/spreadsheets/d/...',
1512
+ * sheetName: 'Sheet1',
1513
+ * col: 2,
1514
+ * values: [
1515
+ * { row: 0, content: 'Row 1 Col C' },
1516
+ * { row: 1, content: 'Row 2 Col C' },
1517
+ * { row: 2, content: 'Row 3 Col C' }
1518
+ * ]
1519
+ * });
1520
+ * ```
1521
+ */
1522
+ async updateValuesMultiRowsByCol({ sheetUrl, sheetName, col, values, rowOffset = 0, }) {
1523
+ if (!values || values.length === 0) {
1524
+ throw new Error('No values provided for update');
1525
+ }
1526
+ const sheetsInstance = await this.getSheetsClient();
1527
+ const spreadsheetId = this.extractSheetId({ sheetUrl });
1528
+ const colName = convertIndexToColumnName({ columnIndex: col });
1529
+ const requests = values.map((valPair) => {
1530
+ const actualRow = calculateActualRow({
1531
+ dataRowIndex: valPair.row,
1532
+ rowOffset,
1533
+ });
1534
+ const sheetRange = `${sheetName}!${colName}${actualRow}`;
1535
+ return {
1536
+ range: sheetRange,
1537
+ values: [[valPair.content]],
1538
+ };
1539
+ });
1540
+ await sheetsInstance.spreadsheets.values.batchUpdate({
1541
+ spreadsheetId,
1542
+ requestBody: {
1543
+ valueInputOption: 'RAW',
1544
+ data: requests,
1545
+ },
1546
+ });
1547
+ return true;
1548
+ }
1549
+ /**
1550
+ * Updates a range of multiple rows and columns at once.
1551
+ *
1552
+ * @param params - Update parameters including value matrix
1553
+ * @returns A Promise that resolves to true if successful
1554
+ *
1555
+ * @example
1556
+ * ```typescript
1557
+ * await client.updateValuesMultiRowsMultiCols({
1558
+ * sheetUrl: 'https://docs.google.com/spreadsheets/d/...',
1559
+ * sheetName: 'Sheet1',
1560
+ * values: [
1561
+ * ['A1', 'B1', 'C1'],
1562
+ * ['A2', 'B2', 'C2'],
1563
+ * ['A3', 'B3', 'C3']
1564
+ * ],
1565
+ * startRow: 0,
1566
+ * startCol: 0
1567
+ * });
1568
+ * ```
1569
+ */
1570
+ async updateValuesMultiRowsMultiCols({ sheetUrl, sheetName, values, startRow = 0, endRow, startCol = 0, rowOffset = 0, }) {
1571
+ if (!values || values.length === 0 || values[0].length === 0) {
1572
+ throw new Error('Invalid values matrix: no data to update');
1573
+ }
1574
+ const sheetsInstance = await this.getSheetsClient();
1575
+ const spreadsheetId = this.extractSheetId({ sheetUrl });
1576
+ const numRows = values.length;
1577
+ const numCols = values[0].length;
1578
+ const startColName = convertIndexToColumnName({ columnIndex: startCol });
1579
+ const endColName = convertIndexToColumnName({
1580
+ columnIndex: startCol + numCols - 1,
1581
+ });
1582
+ const startRowIndex = calculateActualRow({
1583
+ dataRowIndex: startRow,
1584
+ rowOffset,
1585
+ });
1586
+ let endRowIndex;
1587
+ if (endRow !== undefined) {
1588
+ endRowIndex = calculateActualRow({ dataRowIndex: endRow, rowOffset });
1589
+ }
1590
+ else {
1591
+ endRowIndex = startRowIndex + numRows - 1;
1592
+ }
1593
+ const sheetRange = `${sheetName}!${startColName}${startRowIndex}:${endColName}${endRowIndex}`;
1594
+ await sheetsInstance.spreadsheets.values.batchUpdate({
1595
+ spreadsheetId,
1596
+ requestBody: {
1597
+ valueInputOption: 'RAW',
1598
+ data: [
1599
+ {
1600
+ range: sheetRange,
1601
+ values,
1602
+ },
1603
+ ],
1604
+ },
1605
+ });
1606
+ return true;
1607
+ }
1608
+ /**
1609
+ * Deletes a row from a sheet.
1610
+ *
1611
+ * @param params - Delete parameters including row index
1612
+ * @returns A Promise that resolves to true if successful
1613
+ *
1614
+ * @example
1615
+ * ```typescript
1616
+ * await client.deleteRowSheet({
1617
+ * sheetUrl: 'https://docs.google.com/spreadsheets/d/...',
1618
+ * sheetName: 'Sheet1',
1619
+ * row: 5 // Delete data row at index 5
1620
+ * });
1621
+ * ```
1622
+ */
1623
+ async deleteRowSheet({ sheetUrl, sheetName, row, rowOffset = 0, }) {
1624
+ const sheetsInstance = await this.getSheetsClient();
1625
+ const spreadsheetId = this.extractSheetId({ sheetUrl });
1626
+ // Get sheet ID
1627
+ const sheetInfo = await sheetsInstance.spreadsheets.get({
1628
+ spreadsheetId,
1629
+ ranges: [sheetName],
1630
+ includeGridData: false,
1631
+ });
1632
+ const sheet = sheetInfo.data.sheets?.find((s) => s.properties?.title === sheetName);
1633
+ const sheetId = sheet?.properties?.sheetId;
1634
+ if (sheetId === undefined || sheetId === null) {
1635
+ throw new Error(`Sheet not found: ${sheetName}`);
1636
+ }
1637
+ // Calculate actual row index
1638
+ // +1 for header row, +rowOffset
1639
+ const actualRowIndex = row + 1 + rowOffset;
1640
+ const request = {
1641
+ deleteDimension: {
1642
+ range: {
1643
+ sheetId,
1644
+ dimension: 'ROWS',
1645
+ startIndex: actualRowIndex,
1646
+ endIndex: actualRowIndex + 1,
1647
+ },
1648
+ },
1649
+ };
1650
+ await sheetsInstance.spreadsheets.batchUpdate({
1651
+ spreadsheetId,
1652
+ requestBody: {
1653
+ requests: [request],
1654
+ },
1655
+ });
1656
+ return true;
1657
+ }
1658
+ }
1659
+
1660
+ /**
1661
+ * Google Sheet Feature Library
1662
+ * @description A framework-agnostic library for interacting with Google Sheets API.
1663
+ * Provides easy-to-use methods for reading, writing, and managing spreadsheet data.
1664
+ * @module gg-sheet
1665
+ *
1666
+ * @example
1667
+ * ```typescript
1668
+ * import { GoogleSheetClient, ETypeExport } from 'bodevops-features/gg-sheet';
1669
+ *
1670
+ * const client = new GoogleSheetClient({
1671
+ * keyFilePath: './service-account.json'
1672
+ * });
1673
+ *
1674
+ * // Read data from a sheet
1675
+ * const data = await client.getValues({
1676
+ * sheetUrl: 'https://docs.google.com/spreadsheets/d/...',
1677
+ * sheetName: 'Sheet1'
1678
+ * });
1679
+ *
1680
+ * // Export data
1681
+ * await client.export({
1682
+ * sheetUrl: 'https://docs.google.com/spreadsheets/d/...',
1683
+ * sheetName: 'Sheet1',
1684
+ * listCols: ['Name', 'Email'],
1685
+ * valsExport: [['John', 'john@example.com']],
1686
+ * typeExport: ETypeExport.Append
1687
+ * });
1688
+ * ```
1689
+ */
1690
+ // Export main client class
6
1691
 
7
1692
  var index = /*#__PURE__*/Object.freeze({
8
1693
  __proto__: null,
9
- test: test
1694
+ DEFAULT_SHEET_SCOPES: DEFAULT_SHEET_SCOPES,
1695
+ get ETypeExport () { return ETypeExport; },
1696
+ GoogleSheetClient: GoogleSheetClient,
1697
+ GoogleSheetConfig: GoogleSheetConfig,
1698
+ calculateActualRow: calculateActualRow,
1699
+ convertColumnNameToIndex: convertColumnNameToIndex,
1700
+ convertIndexToColumnName: convertIndexToColumnName,
1701
+ convertValueSheet: convertValueSheet,
1702
+ getIndexCol: getIndexCol,
1703
+ getListColsAndValsExport: getListColsAndValsExport,
1704
+ getSheetIdFromUrl: getSheetIdFromUrl,
1705
+ isValidSheetUrl: isValidSheetUrl
10
1706
  });
11
1707
 
12
- exports.GGDrive = index;
1708
+ exports.GGDrive = index$1;
1709
+ exports.GGSheet = index;
13
1710
  //# sourceMappingURL=index.js.map