{"record":{"id":"a89dfdbf58dc306d","repo":"ToolJet/ToolJet","slug":"invalid-query-options","errorCode":"INVALID_QUERY_OPTIONS","errorMessage":"Expected queryOptions to be an object","messagePattern":"Expected queryOptions to be an object","errorType":"validation","errorClass":"QueryError","httpStatus":null,"severity":"error","filePath":"marketplace/plugins/xero/lib/index.ts","lineNumber":61,"sourceCode":"    const encodedScope = encodeURIComponent(finalScope);\n    const baseUrl =\n      `https://login.xero.com/identity/connect/authorize?response_type=code&client_id=${clientId}` +\n      `&redirect_uri=${fullUrl}oauth2/authorize`;\n\n    return `${baseUrl}&scope=${encodedScope}&access_type=offline&prompt=consent`;\n  }\n\n  private convertQueryOptions(queryOptions: any, customHeaders?: Record<string, string>): any {\n    if (!queryOptions || typeof queryOptions !== 'object') {\n      const errorMessage = 'Expected queryOptions to be an object';\n      const errorDetails = {\n        message: errorMessage,\n        name: 'InvalidQueryOptionsError',\n        code: 'INVALID_QUERY_OPTIONS',\n        received: queryOptions,\n        expected: 'object',\n      };\n      throw new QueryError('Invalid configuration', errorMessage, errorDetails);\n    }\n\n    const { operation, params = {} } = queryOptions;\n    const method = typeof operation === 'string' ? operation.toLowerCase() : 'get';\n    const result: ConvertedFormat = {\n      method,\n      headers: customHeaders || {},\n    };\n\n    if (params?.query && Object.keys(params.query).length > 0) {\n      const urlParams = new URLSearchParams();\n      Object.entries(params.query).forEach(([key, value]) => {\n        if (value !== undefined && value !== null) {\n          if (Array.isArray(value)) {\n            value.forEach((v) => urlParams.append(key, String(v)));\n          } else {\n            urlParams.append(key, String(value));\n          }","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/ToolJet/ToolJet/blob/20602a8e101f2e59686c9afde0d1402aac2c8871/marketplace/plugins/xero/lib/index.ts#L43-L79","documentation":"Validation error (code INVALID_QUERY_OPTIONS) thrown by convertQueryOptions() (index.ts:61) when queryOptions is null/undefined or not typeof 'object'. It is a defensive type guard at the entry of query shaping, before any operation/params are read.","triggerScenarios":"Calling run() (or any path that invokes convertQueryOptions) with queryOptions = null, undefined, a string, a number, or an array; a caller passing a JSON-parsed value that came back non-object.","commonSituations":"Programmatic caller forgetting to construct queryOptions, a UI binding that sent an empty/null value, deserialisation returning a primitive instead of an object.","solutions":["Ensure queryOptions is a plain object (e.g. { operation: 'GET', params: {...} }) before calling the plugin.","Add a caller-side type check and default to {} if absent.","If passing JSON, validate it parses to an object, not a primitive."],"exampleFix":"// before\nqueryOptions = null;\n\n// after\nqueryOptions = { operation: 'GET', params: { path: '/Connections' } };","handlingStrategy":"type-guard","validationCode":"if (!queryOptions || typeof queryOptions !== 'object' || Array.isArray(queryOptions)) {\n  throw new Error('queryOptions must be a plain object');\n}","typeGuard":"function isQueryOptionsObject(q: any): q is Record<string, unknown> {\n  return !!q && typeof q === 'object' && !Array.isArray(q);\n}","tryCatchPattern":null,"preventionTips":["Default queryOptions to {} in the caller when absent.","Type-check at the boundary before invoking convertQueryOptions.","Ensure JSON deserialisation yields an object, not a primitive."],"tags":["xero","validation","type-guard","query-options","tooljet-plugin"],"backgroundTag":null,"analyzedSha":"20602a8e101f2e59686c9afde0d1402aac2c8871","analyzedAt":"2026-08-13T05:58:54.221Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}