Skip to content

Commit 74cdc44

Browse files
theoephraimclaude
andauthored
feat: add searchDeveloperMetadata and metadata filters in loadCells (#752)
* feat: add searchDeveloperMetadata and support metadata filters in loadCells Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: add loadCells with developer metadata filter test Tests both doc-level and sheet-level loadCells with a developerMetadataLookup filter on row-level metadata. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent fd12a3b commit 74cdc44

7 files changed

Lines changed: 117 additions & 10 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"google-spreadsheet": minor
3+
---
4+
5+
Add searchDeveloperMetadata method and support DeveloperMetadataLookup filters in loadCells

docs/classes/google-spreadsheet-worksheet.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ The cell-based interface lets you load and update individual cells in a sheeet,
153153
154154
!> This method does not return the cells it loads, instead they are kept in a local cache managed by the sheet. See methods below (`getCell` and `getCellByA1`) to access them.
155155

156-
You can filter the cells you want to fetch in several ways. See [Data Filters](https://developers.google.com/sheets/api/reference/rest/v4/DataFilter) for more info. Strings are treated as A1 ranges, objects are detected to be a [GridRange](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/other#GridRange) with sheetId not required.
156+
You can filter the cells you want to fetch in several ways. See [Data Filters](https://developers.google.com/sheets/api/reference/rest/v4/DataFilter) for more info. Strings are treated as A1 ranges, objects are detected to be a [GridRange](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/other#GridRange) with sheetId not required. Objects with a `developerMetadataLookup` key are treated as [DeveloperMetadataLookup](https://developers.google.com/sheets/api/reference/rest/v4/DataFilter#DeveloperMetadataLookup) filters.
157157

158158
```javascript
159159
await sheet.loadCells(); // no filter - will load ALL cells in the sheet
@@ -163,6 +163,9 @@ await sheet.loadCells({ // GridRange object
163163
});
164164
await sheet.loadCells({ startRowIndex: 50 }); // not all props required
165165
await sheet.loadCells(['B2:D5', 'B50:D55']); // can pass an array of filters
166+
await sheet.loadCells({ // DeveloperMetadataLookup filter
167+
developerMetadataLookup: { metadataKey: 'my-key' }
168+
});
166169
```
167170

168171
!> If using an API key (read-only access), only A1 ranges are supported

docs/classes/google-spreadsheet.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,23 @@ Param|Type|Required|Description
216216

217217
- ↩️ **Returns** - Buffer (or stream) containing ODS data
218218

219+
### Developer Metadata
220+
221+
#### `searchDeveloperMetadata(filters)` (async) :id=fn-searchDeveloperMetadata
222+
> Search for developer metadata entries matching the given filters
223+
224+
Param|Type|Required|Description
225+
---|---|---|---
226+
`filters`|Array<[DataFilter](https://developers.google.com/sheets/api/reference/rest/v4/DataFilter)>|✅|Array of DataFilter objects to match against
227+
228+
- ↩️ **Returns** - `Promise<DeveloperMetadata[]>` - array of matching [DeveloperMetadata](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.developerMetadata#DeveloperMetadata) objects
229+
230+
```javascript
231+
const results = await doc.searchDeveloperMetadata([
232+
{ developerMetadataLookup: { metadataKey: 'my-key' } },
233+
]);
234+
```
235+
219236
### Deletion
220237
#### `delete()` (async) :id=fn-delete
221238
> delete the document

src/lib/GoogleSpreadsheet.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { GoogleSpreadsheetWorksheet } from './GoogleSpreadsheetWorksheet';
44
import { getFieldMask } from './utils';
55
import {
66
DataFilter,
7+
DataFilterObject,
8+
DeveloperMetadata,
79
GridRange,
810
NamedRangeId,
911
ProtectedRange,
@@ -412,15 +414,12 @@ export class GoogleSpreadsheet {
412414
async loadCells(
413415
/**
414416
* single filter or array of filters
415-
* strings are treated as A1 ranges, objects are treated as GridRange objects
417+
* strings are treated as A1 ranges, objects are treated as GridRange objects,
418+
* objects with a `developerMetadataLookup` key are treated as DeveloperMetadataLookup filters
416419
* pass nothing to fetch all cells
417420
* */
418421
filters?: DataFilter | DataFilter[]
419422
) {
420-
// TODO: make it support DeveloperMetadataLookup objects
421-
422-
423-
424423
// TODO: switch to this mode if using a read-only auth token?
425424
const readOnlyMode = this.authMode === AUTH_MODES.API_KEY;
426425

@@ -433,7 +432,9 @@ export class GoogleSpreadsheet {
433432
if (readOnlyMode) {
434433
throw new Error('Only A1 ranges are supported when fetching cells with read-only access (using only an API key)');
435434
}
436-
// TODO: make this support Developer Metadata filters
435+
if ('developerMetadataLookup' in filter) {
436+
return { developerMetadataLookup: filter.developerMetadataLookup };
437+
}
437438
return { gridRange: filter };
438439
}
439440
throw new Error('Each filter must be an A1 range string or a gridrange object');
@@ -648,6 +649,24 @@ export class GoogleSpreadsheet {
648649
await this.driveApi.delete(`permissions/${permissionId}`);
649650
}
650651

652+
// DEVELOPER METADATA ///////////////////////////////////////////////////////////////////////////
653+
654+
/**
655+
* search for developer metadata entries matching the given filters
656+
* @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.developerMetadata/search
657+
*/
658+
async searchDeveloperMetadata(
659+
/** array of DataFilter objects to match against */
660+
filters: DataFilterObject[]
661+
): Promise<DeveloperMetadata[]> {
662+
const response = await this.sheetsApi.post('developerMetadata:search', {
663+
json: { dataFilters: filters },
664+
});
665+
const data = await response.json<any>();
666+
if (!data.matchedDeveloperMetadata) return [];
667+
return data.matchedDeveloperMetadata.map((m: any) => m.developerMetadata);
668+
}
669+
651670
//
652671
// CREATE NEW DOC ////////////////////////////////////////////////////////////////////////////////
653672
static async createNewSpreadsheetDocument(auth: GoogleApiAuth, properties?: Partial<SpreadsheetProperties>) {

src/lib/GoogleSpreadsheetWorksheet.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,10 @@ export class GoogleSpreadsheetWorksheet {
250250
return `${this.a1SheetName}!${filter}`;
251251
}
252252
if (_.isObject(filter)) {
253-
// TODO: detect and support DeveloperMetadata filters
253+
// pass through developer metadata filters without adding sheetId
254+
if ('developerMetadataLookup' in filter) {
255+
return filter;
256+
}
254257

255258
// check if the user passed in a sheet id
256259
const filterAny = filter as any;

src/lib/types/sheets-types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -410,8 +410,8 @@ export type GridRange = {
410410
};
411411
export type GridRangeWithoutWorksheetId = Omit<GridRange, 'sheetId'>;
412412
export type GridRangeWithOptionalWorksheetId = MakeOptional<GridRange, 'sheetId'>;
413-
export type DataFilter = A1Range | GridRange;
414-
export type DataFilterWithoutWorksheetId = A1Range | GridRangeWithoutWorksheetId;
413+
export type DataFilter = A1Range | GridRange | DataFilterObject;
414+
export type DataFilterWithoutWorksheetId = A1Range | GridRangeWithoutWorksheetId | DataFilterObject;
415415

416416
/**
417417
* A coordinate in a sheet. All indexes are zero-based.

src/test/manage.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1376,6 +1376,66 @@ describe('Managing doc info and sheets', () => {
13761376
);
13771377
});
13781378

1379+
it('can search developer metadata', async () => {
1380+
// create a metadata entry to search for
1381+
await sheet.createDeveloperMetadata({
1382+
metadataKey: 'search-test-key',
1383+
metadataValue: 'search-test-value',
1384+
location: { sheetId: sheet.sheetId },
1385+
visibility: 'DOCUMENT',
1386+
});
1387+
1388+
const results = await doc.searchDeveloperMetadata([
1389+
{ developerMetadataLookup: { metadataKey: 'search-test-key' } },
1390+
]);
1391+
1392+
expect(results).toHaveLength(1);
1393+
expect(results[0].metadataKey).toBe('search-test-key');
1394+
expect(results[0].metadataValue).toBe('search-test-value');
1395+
1396+
// clean up
1397+
await sheet.deleteDeveloperMetadata({
1398+
developerMetadataLookup: { metadataKey: 'search-test-key' },
1399+
});
1400+
});
1401+
1402+
it('can load cells using a developer metadata filter', async () => {
1403+
// create row-level metadata on row 0
1404+
await sheet.createDeveloperMetadata({
1405+
metadataKey: 'row-meta-key',
1406+
metadataValue: 'row-meta-value',
1407+
location: {
1408+
dimensionRange: {
1409+
sheetId: sheet.sheetId,
1410+
dimension: 'ROWS',
1411+
startIndex: 0,
1412+
endIndex: 1,
1413+
},
1414+
},
1415+
visibility: 'DOCUMENT',
1416+
});
1417+
1418+
// load cells using developer metadata filter on doc
1419+
await doc.loadCells({
1420+
developerMetadataLookup: { metadataKey: 'row-meta-key' },
1421+
});
1422+
1423+
// load cells using developer metadata filter on sheet
1424+
sheet.resetLocalCache(true);
1425+
await sheet.loadCells({
1426+
developerMetadataLookup: { metadataKey: 'row-meta-key' },
1427+
});
1428+
1429+
// verify cells were loaded (row 0 should be accessible)
1430+
const cell = sheet.getCell(0, 0);
1431+
expect(cell).toBeTruthy();
1432+
1433+
// clean up
1434+
await sheet.deleteDeveloperMetadata({
1435+
developerMetadataLookup: { metadataKey: 'row-meta-key' },
1436+
});
1437+
});
1438+
13791439
it('can delete developer metadata', async () => {
13801440
await sheet.deleteDeveloperMetadata({
13811441
developerMetadataLookup: {

0 commit comments

Comments
 (0)