{ "kind": "open-flow-workflow", "version": 1, "name": "API Operations Schema Export", "content": { "modelVersion": 14, "document": { "bindings": {}, "graph": { "nodes": { "n_e1b106d3f802a007": { "name": "Service export request", "kind": "start", "inputDefs": [ { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "service" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "url" } ] }, "n_2df059f3b43fdb9d": { "inputs": { "service": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_e1b106d3f802a007", "output": "service" } ] }, "url": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_e1b106d3f802a007", "output": "url" } ] }, "spreadsheetId": { "kind": "value", "value": "YOUR_SPREADSHEET_ID" }, "range": { "kind": "value", "value": "'Operations'" } }, "name": "Prepare operations export", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "service" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "url" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "spreadsheetId" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "range" } ], "outputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "service" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "url" }, { "jsonSchema": { "type": "string", "minLength": 1, "description": "The spreadsheet ID." }, "nullable": false, "handle": "spreadsheetId" }, { "jsonSchema": { "type": "string", "minLength": 1, "description": "The A1 notation of the range to retrieve." }, "nullable": false, "handle": "range" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "createdAt" } ], "name": "Prepare operations export", "moduleId": "n_8dd3be9babe890a8", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_57c1c292c5ec9f3e": { "inputs": { "spreadsheetId": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_2df059f3b43fdb9d", "output": "spreadsheetId" } ] }, "range": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_2df059f3b43fdb9d", "output": "range" } ] }, "majorDimension": { "kind": "value", "value": "ROWS" }, "valueRenderOption": { "kind": "value", "value": "UNFORMATTED_VALUE" }, "dateTimeRenderOption": { "kind": "value", "value": "FORMATTED_STRING" } }, "name": "Read operations table", "kind": "task", "task": { "inputs": [ { "description": "The spreadsheet ID.", "jsonSchema": { "type": "string", "minLength": 1, "description": "The spreadsheet ID." }, "nullable": true, "handle": "spreadsheetId", "value": null }, { "description": "The A1 notation of the range to retrieve.", "jsonSchema": { "type": "string", "minLength": 1, "description": "The A1 notation of the range to retrieve." }, "nullable": false, "handle": "range" }, { "description": "The major dimension for the returned values.", "jsonSchema": { "type": "string", "enum": [ "DIMENSION_UNSPECIFIED", "ROWS", "COLUMNS" ], "description": "The major dimension for the returned values." }, "nullable": true, "handle": "majorDimension", "value": null }, { "description": "How values should be rendered in the output.", "jsonSchema": { "type": "string", "enum": [ "FORMATTED_VALUE", "UNFORMATTED_VALUE", "FORMULA" ], "description": "How values should be rendered in the output." }, "nullable": true, "handle": "valueRenderOption", "value": null }, { "description": "How dates and times should be rendered in the output.", "jsonSchema": { "type": "string", "enum": [ "SERIAL_NUMBER", "FORMATTED_STRING" ], "description": "How dates and times should be rendered in the output." }, "nullable": true, "handle": "dateTimeRenderOption", "value": null } ], "outputs": [ { "description": "The A1 notation of the range.", "jsonSchema": { "type": "string", "description": "The A1 notation of the range." }, "nullable": false, "handle": "range" }, { "description": "The major dimension of the values (ROWS or COLUMNS).", "jsonSchema": { "type": "string", "description": "The major dimension of the values (ROWS or COLUMNS)." }, "nullable": false, "handle": "majorDimension" }, { "description": "The data values in the range.", "jsonSchema": { "type": "array", "items": { "type": "array", "items": { "anyOf": [ { "type": "string", "description": "A union variant." }, { "type": "number", "description": "A union variant." }, { "type": "boolean", "description": "A union variant." }, { "const": null, "description": "A union variant." } ], "description": "The cell value schema." }, "description": "An array item." }, "description": "The data values in the range." }, "nullable": false, "handle": "values" } ], "name": "Read operations table", "executor": { "kind": "connector", "action": "googlesheets.values_get" } } }, "n_0bd0fef8291cb044": { "inputs": { "values": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_57c1c292c5ec9f3e", "output": "values" } ] }, "majorDimension": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_57c1c292c5ec9f3e", "output": "majorDimension" } ] }, "service": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_2df059f3b43fdb9d", "output": "service" } ] }, "url": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_2df059f3b43fdb9d", "output": "url" } ] }, "createdAt": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_2df059f3b43fdb9d", "output": "createdAt" } ] } }, "name": "Build custom operations JSON", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "array", "items": { "type": "array", "items": { "anyOf": [ { "type": "string", "description": "A union variant." }, { "type": "number", "description": "A union variant." }, { "type": "boolean", "description": "A union variant." }, { "const": null, "description": "A union variant." } ], "description": "The cell value schema." }, "description": "An array item." }, "description": "The data values in the range." }, "nullable": false, "handle": "values" }, { "jsonSchema": { "type": "string", "description": "The major dimension of the values (ROWS or COLUMNS)." }, "nullable": false, "handle": "majorDimension" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "service" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "url" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "createdAt" } ], "outputs": [ { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "hasOperations" }, { "jsonSchema": { "type": "integer", "minimum": 0 }, "nullable": false, "handle": "operationCount" }, { "jsonSchema": { "type": "string", "minLength": 1, "description": "The name of the file." }, "nullable": false, "handle": "filename" }, { "jsonSchema": { "type": "string", "description": "Connector text media upload content." }, "nullable": false, "handle": "text" } ], "name": "Build custom operations JSON", "moduleId": "n_026cbbf70e840614", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_959f958db033dae1": { "inputs": {}, "name": "Matching operations found?", "kind": "condition", "matchMode": "first", "cases": [ { "output": "with_operations", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_0bd0fef8291cb044", "output": "hasOperations" } }, "operator": "==", "right": { "kind": "value", "value": true } } ] } ] } ] }, "n_b638d470c3017233": { "inputs": { "name": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_0bd0fef8291cb044", "output": "filename" } ] }, "text": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_0bd0fef8291cb044", "output": "text" } ] }, "mimeType": { "kind": "value", "value": "text/plain" } }, "name": "Save JSON text to Drive", "kind": "task", "task": { "inputs": [ { "description": "The name of the file.", "jsonSchema": { "type": "string", "minLength": 1, "description": "The name of the file." }, "nullable": true, "handle": "name", "value": null }, { "description": "The MIME type of the file.", "jsonSchema": { "type": "string", "minLength": 1, "description": "The MIME type of the file." }, "nullable": true, "handle": "mimeType", "value": null }, { "description": "A short description of the file.", "jsonSchema": { "type": "string", "minLength": 1, "description": "A short description of the file." }, "nullable": true, "handle": "description", "value": null }, { "description": "A list of parent folder IDs to place the file in.", "jsonSchema": { "type": "array", "items": { "type": "string", "minLength": 1, "description": "Schema for nonEmptyString." }, "minItems": 1, "description": "A list of parent folder IDs to place the file in." }, "nullable": true, "handle": "parents", "value": null }, { "description": "Whether the user has starred the file.", "jsonSchema": { "type": "boolean", "description": "Whether the user has starred the file." }, "nullable": true, "handle": "starred", "value": null }, { "description": "A collection of arbitrary key-value pairs which are private to the requesting app.", "jsonSchema": { "type": "object", "additionalProperties": { "type": "string", "description": "A record value." }, "description": "A collection of arbitrary key-value pairs which are private to the requesting app." }, "nullable": true, "handle": "appProperties", "value": null }, { "description": "A collection of arbitrary key-value pairs which are visible to all apps.", "jsonSchema": { "type": "object", "additionalProperties": { "type": "string", "description": "A record value." }, "description": "A collection of arbitrary key-value pairs which are visible to all apps." }, "nullable": true, "handle": "properties", "value": null }, { "description": "Connector media upload content encoded as a Base64 string.", "jsonSchema": { "type": "string", "minLength": 1, "description": "Connector media upload content encoded as a Base64 string." }, "nullable": true, "handle": "contentBase64", "value": null }, { "description": "Connector text media upload content.", "jsonSchema": { "type": "string", "description": "Connector text media upload content." }, "nullable": true, "handle": "text", "value": null } ], "outputs": [ { "description": "The unique identifier of the file.", "jsonSchema": { "type": "string", "description": "The unique identifier of the file." }, "nullable": false, "handle": "id" }, { "description": "The name of the file.", "jsonSchema": { "type": "string", "description": "The name of the file." }, "nullable": false, "handle": "name" }, { "description": "The MIME type of the file.", "jsonSchema": { "type": "string", "description": "The MIME type of the file." }, "nullable": false, "handle": "mimeType" }, { "description": "A link for opening the file in a relevant Google editor or viewer in a browser.", "jsonSchema": { "type": [ "string", "null" ], "description": "A link for opening the file in a relevant Google editor or viewer in a browser." }, "nullable": true, "handle": "webViewLink" }, { "description": "The time at which the file was created (RFC 3339 date-time).", "jsonSchema": { "type": [ "string", "null" ], "description": "The time at which the file was created (RFC 3339 date-time)." }, "nullable": true, "handle": "createdTime" }, { "description": "The last time the file was modified by anyone (RFC 3339 date-time).", "jsonSchema": { "type": [ "string", "null" ], "description": "The last time the file was modified by anyone (RFC 3339 date-time)." }, "nullable": true, "handle": "modifiedTime" }, { "description": "The size of the file's content in bytes.", "jsonSchema": { "type": [ "integer", "null" ], "description": "The size of the file's content in bytes." }, "nullable": true, "handle": "sizeBytes" }, { "description": "The ID of the shared drive the file belongs to.", "jsonSchema": { "type": [ "string", "null" ], "description": "The ID of the shared drive the file belongs to." }, "nullable": true, "handle": "driveId" }, { "description": "The IDs of the parent folders containing the file.", "jsonSchema": { "type": "array", "items": { "type": "string", "description": "An array item." }, "description": "The IDs of the parent folders containing the file." }, "nullable": true, "handle": "parents" }, { "description": "The owners of the file.", "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "displayName": { "type": [ "string", "null" ], "description": "The display name of the owner." }, "emailAddress": { "type": [ "string", "null" ], "description": "The email address of the owner." }, "permissionId": { "type": [ "string", "null" ], "description": "The permission ID of the owner." }, "photoLink": { "type": [ "string", "null" ], "description": "A link to the owner's profile photo." } }, "required": [ "displayName", "emailAddress", "permissionId", "photoLink" ], "additionalProperties": false, "description": "Schema for driveOwnerSchema." }, "description": "The owners of the file." }, "nullable": true, "handle": "owners" }, { "description": "Whether the file has been shared.", "jsonSchema": { "type": "boolean", "description": "Whether the file has been shared." }, "nullable": true, "handle": "shared" }, { "description": "Whether the user has starred the file.", "jsonSchema": { "type": "boolean", "description": "Whether the user has starred the file." }, "nullable": true, "handle": "starred" }, { "description": "Whether the file has been trashed.", "jsonSchema": { "type": "boolean", "description": "Whether the file has been trashed." }, "nullable": true, "handle": "trashed" }, { "description": "Whether the authenticated user owns the file.", "jsonSchema": { "type": "boolean", "description": "Whether the authenticated user owns the file." }, "nullable": true, "handle": "ownedByMe" }, { "description": "The projected Drive capabilities for the authenticated user.", "jsonSchema": { "type": "object", "properties": { "canShare": { "type": "boolean", "description": "Whether the authenticated user may modify sharing settings." } }, "required": [ "canShare" ], "additionalProperties": false, "description": "The projected Drive capabilities for the authenticated user." }, "nullable": true, "handle": "capabilities" } ], "name": "Save JSON text to Drive", "executor": { "kind": "connector", "action": "googledrive.files.create" } } }, "n_02c37ad090806c8f": { "inputs": { "exported": { "kind": "value", "value": true }, "id": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_b638d470c3017233", "output": "id" } ] }, "filename": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_0bd0fef8291cb044", "output": "filename" } ] } }, "name": "Confirm saved export", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "exported" }, { "jsonSchema": { "type": "string", "description": "The unique identifier of the file." }, "nullable": false, "handle": "id" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "filename" } ], "outputs": [ { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "exported" }, { "jsonSchema": { "type": "object", "properties": { "id": { "type": [ "string", "null" ] }, "filename": { "type": [ "string", "null" ] } }, "required": [ "id", "filename" ], "additionalProperties": false }, "nullable": false, "handle": "response" } ], "name": "Confirm saved export", "moduleId": "n_b71d35ed4a3c3b6a", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_4b7dc35f55572624": { "inputs": { "exported": { "kind": "value", "value": false }, "id": { "kind": "value", "value": "" }, "filename": { "kind": "value", "value": "" } }, "name": "Return no operations", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "exported" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "id" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "filename" } ], "outputs": [ { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "exported" }, { "jsonSchema": { "type": "object", "properties": { "id": { "type": [ "string", "null" ] }, "filename": { "type": [ "string", "null" ] } }, "required": [ "id", "filename" ], "additionalProperties": false }, "nullable": false, "handle": "response" } ], "name": "Return no operations", "moduleId": "n_2ba023d28e8f0797", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_b594159956f5e420": { "name": "Operations export result", "kind": "outputs", "outputDefs": [ { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "exported" }, { "jsonSchema": { "type": "object", "properties": { "id": { "type": [ "string", "null" ] }, "filename": { "type": [ "string", "null" ] } }, "required": [ "id", "filename" ], "additionalProperties": false }, "nullable": false, "handle": "response" } ], "outputMappings": { "exported": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_02c37ad090806c8f", "output": "exported" }, { "kind": "node", "nodeId": "n_4b7dc35f55572624", "output": "exported" } ] }, "response": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_02c37ad090806c8f", "output": "response" }, { "kind": "node", "nodeId": "n_4b7dc35f55572624", "output": "response" } ] } } } }, "edges": [ { "source": "n_e1b106d3f802a007", "target": "n_2df059f3b43fdb9d" }, { "source": "n_2df059f3b43fdb9d", "target": "n_57c1c292c5ec9f3e" }, { "source": "n_57c1c292c5ec9f3e", "target": "n_0bd0fef8291cb044" }, { "source": "n_0bd0fef8291cb044", "target": "n_959f958db033dae1" }, { "source": "n_959f958db033dae1", "target": "n_4b7dc35f55572624", "sourceHandle": "otherwise" }, { "source": "n_959f958db033dae1", "target": "n_b638d470c3017233", "sourceHandle": "with_operations" }, { "source": "n_b638d470c3017233", "target": "n_02c37ad090806c8f" }, { "source": "n_02c37ad090806c8f", "target": "n_b594159956f5e420" }, { "source": "n_4b7dc35f55572624", "target": "n_b594159956f5e420" } ] } }, "modules": { "n_8dd3be9babe890a8": { "name": "Prepare operations export", "imports": [], "source": "export default function prepareOperationsExport({ service, url, spreadsheetId, range, eventType }) {\n if (eventType != null && eventType !== 'generate') throw new Error('This component handles only generate requests.');\n if (typeof service !== 'string' || !service.trim()) throw new Error('Supply the exact service name used in the operations sheet.');\n if (typeof url !== 'string' || !url.trim()) throw new Error('Supply the service documentation URL.');\n if (typeof spreadsheetId !== 'string' || !/^[A-Za-z0-9_-]+$/.test(spreadsheetId) || /YOUR_SPREADSHEET_ID/.test(spreadsheetId)) throw new Error('Configure your operations spreadsheet ID.');\n if (typeof range !== 'string' || !/^'(?:[^'\\u0000-\\u001f\\u007f]|'')+'$/.test(range)) throw new Error('Configure the complete operations tab as a single-quoted A1 sheet name.');\n return { service, url, spreadsheetId, range, createdAt: new Date().toISOString() };\n}\n" }, "n_026cbbf70e840614": { "name": "Build custom operations JSON", "imports": [], "source": "function sheetTable({ values, majorDimension = \"ROWS\" }) {\n if (majorDimension !== \"ROWS\" || !Array.isArray(values) || values.some((row) => !Array.isArray(row)))\n throw Error(\"Google Sheets must return row-oriented cell values.\");\n const isEmpty = (value) => value == null || value === \"\", nonempty = (row) => row.some((value) => !isEmpty(value)), headerIndex = values.findIndex(nonempty);\n if (headerIndex < 0)\n return { columns: [], records: [] };\n const headers = values[headerIndex];\n if (headers.some((name) => typeof name !== \"string\" || !name.trim() || name === \"row_number\") || new Set(headers).size !== headers.length)\n throw Error(\"Use unique nonempty text headers; row_number is reserved.\");\n const columns = [\"row_number\", ...headers], records = [];\n for (let index = headerIndex + 1;index < values.length; index++) {\n const cells = values[index];\n if (!nonempty(cells))\n continue;\n if (cells.slice(headers.length).some((value) => !isEmpty(value)))\n throw Error(\"Every populated column needs a header.\");\n records.push(Object.fromEntries([[\"row_number\", index + 1], ...headers.map((name, column) => [name, cells[column] ?? \"\"])]));\n }\n return { columns, records };\n}\n\nexport default function buildOperationsExport({ values, majorDimension, service, url, createdAt }) {\n const table = sheetTable({ values, majorDimension });\n const required = ['service', 'resource', 'operation', 'description', 'url', 'method'];\n if (table.columns.length && required.some(name => !table.columns.includes(name))) throw new Error('The operations tab needs service, resource, operation, description, url and method columns.');\n const groups = new Map();\n let operationCount = 0;\n for (const row of table.records) {\n if (String(row.service) !== service) continue;\n for (const key of ['resource', 'operation', 'url', 'method']) if (typeof row[key] !== 'string' || !row[key].trim()) throw new Error('Each matching operation needs resource, operation, url and method text.');\n if (typeof row.description !== 'string') throw new Error('Operation descriptions must be text.');\n if (row.documentation_url != null && typeof row.documentation_url !== 'string') throw new Error('Method documentation URLs must be text when supplied.');\n const resource = row.resource.toLowerCase().trim().replace(/\\bapi\\b/g, '').replace(/\\s+/g, ' ').trim();\n if (!resource) continue;\n if (!groups.has(resource)) groups.set(resource, { resource: resource[0].toUpperCase() + resource.slice(1), operations: [] });\n const description = row.description.trim(), period = description.indexOf('.');\n const operation = row.operation.trim();\n groups.get(resource).operations.push({ operation: operation[0].toUpperCase() + operation.slice(1), description: period > 0 ? description.slice(0, period + 1) : description, ApiUrl: row.url, method: row.method.trim().toUpperCase(), method_documentation_url: row.documentation_url || '' });\n operationCount++;\n }\n if (typeof createdAt !== 'string' || Number.isNaN(Date.parse(createdAt))) throw new Error('The export needs its run-start timestamp.');\n const timestamp = new Date(createdAt).toISOString().slice(0, 19).replace(/[-:]/g, '').replace('T', '_') + 'Z';\n const prefix = service.replace(/[^\\p{L}\\p{N}_-]+/gu, '_').replace(/^_+|_+$/g, '').toLowerCase() || 'service';\n const filename = prefix + '_api_operations_' + timestamp + '.json';\n const text = JSON.stringify({ documentation_url: url, endpoints: [...groups.values()] }, null, 4);\n if (new TextEncoder().encode(text).byteLength > 5000000) throw new Error('The generated JSON text exceeds the 5,000,000-byte content budget.');\n return { hasOperations: operationCount > 0, operationCount, filename, text };\n}\n" }, "n_b71d35ed4a3c3b6a": { "name": "Confirm saved export", "imports": [], "source": "export default function operationsExportReply({ exported, id, filename }) {\n if (!exported) return { exported: false, response: { id: null, filename: null } };\n if (typeof id !== 'string' || !id.trim() || typeof filename !== 'string' || !filename.trim()) throw new Error('Google Drive did not acknowledge the created export file.');\n return { exported: true, response: { id, filename } };\n}\n" }, "n_2ba023d28e8f0797": { "name": "Return no operations", "imports": [], "source": "export default function operationsExportReply({ exported, id, filename }) {\n if (!exported) return { exported: false, response: { id: null, filename: null } };\n if (typeof id !== 'string' || !id.trim() || typeof filename !== 'string' || !filename.trim()) throw new Error('Google Drive did not acknowledge the created export file.');\n return { exported: true, response: { id, filename } };\n}\n" } } }, "presentation": { "designer": { "version": 1, "flow": { "direction": "vertical" } } } }