import * as z from "zod" import { defineAction } from "../../../automation/actions" import { assertSourceRowNumbers, getGoogleSheetsApi, normalizeSpreadsheetId, resolveRowSource, toGoogleCellData, } from "../lib/google-sheets" import { GOOGLE_SHEETS_WRITE_REQUIREMENT } from "../lib/scopes" import { ROW_MUTATION_RESULT_SCHEMA, ROW_SOURCE_SCHEMA, ROW_VALUES_SCHEMA, SPREADSHEET_REFERENCE_SCHEMA, VALUE_INPUT_MODE_SCHEMA, } from "../lib/schemas" /** Applies partial header-keyed updates to explicitly numbered rows. */ export const updateGoogleSheetsRows = defineAction("Update Google Sheets rows") .describe("Updates only the supplied columns on one or more rows.") .account("google", GOOGLE_SHEETS_WRITE_REQUIREMENT) .input( z.object({ /** Whether strings are parsed like UI input or stored literally. */ inputMode: VALUE_INPUT_MODE_SCHEMA.optional(), /** Header-backed sheet or native table. */ source: ROW_SOURCE_SCHEMA, /** Spreadsheet ID or standard Google Sheets URL. */ spreadsheet: SPREADSHEET_REFERENCE_SCHEMA, /** Row numbers and partial column-value patches. */ updates: z .object({ /** One-based row number. */ rowNumber: z.number().int().positive(), /** Exact header-value pairs. Null clears a cell. */ values: ROW_VALUES_SCHEMA.refine( (values) => Object.keys(values).length > 0, "At least one column must be updated.", ), }) .array() .min(1), }), ) .output(ROW_MUTATION_RESULT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const sheetsApi = getGoogleSheetsApi(account.secret) const spreadsheetId = normalizeSpreadsheetId(input.spreadsheet) const source = await resolveRowSource( sheetsApi, spreadsheetId, input.source, ) const rowNumbers = input.updates.map(({ rowNumber }) => rowNumber) assertSourceRowNumbers(source, rowNumbers) const requests = input.updates.flatMap(({ rowNumber, values }) => Object.entries(values).map(([header, value]) => { const columnIndex = source.headers.indexOf(header) if (columnIndex === -1) { throw new Error( `Unknown Google Sheets column "${header}". Available columns: ${source.headers.join(", ")}.`, ) } return { updateCells: { fields: "userEnteredValue", range: { endColumnIndex: columnIndex + 1, endRowIndex: rowNumber, sheetId: source.sheetId, startColumnIndex: columnIndex, startRowIndex: rowNumber - 1, }, rows: [ { values: [toGoogleCellData(value, input.inputMode)], }, ], }, } }), ) await sheetsApi.spreadsheets.batchUpdate({ requestBody: { requests }, spreadsheetId, }) return { affectedCells: requests.length, rowNumbers, } })