Edits history of script submission #23492 for ' Search Records (airtable)'

  • bunnative
    One script reply has been approved by the moderators
    Ap­pro­ved
    //native
    
    export type DynSelect_base_id = string
    export type DynSelect_table_id = string
    export type DynSelect_search_field = string
    
    // Dropdown of the bases the token can access.
    export async function base_id(auth: RT.Airtable) {
      const options: { value: string; label: string }[] = []
      let offset: string | undefined = undefined
      do {
        const url = new URL("https://api.airtable.com/v0/meta/bases")
        if (offset) {
          url.searchParams.append("offset", offset)
        }
        const response = await fetch(url, {
          headers: {
            Authorization: `Bearer ${auth.apiKey}`,
            Accept: "application/json",
          },
        })
        if (!response.ok) {
          throw new Error(`${response.status} ${await response.text()}`)
        }
        const page = (await response.json()) as {
          bases: { id: string; name: string }[]
          offset?: string
        }
        options.push(...page.bases.map((b) => ({ value: b.id, label: b.name })))
        offset = page.offset
      } while (offset)
      return options
    }
    
    async function listTables(auth: RT.Airtable, base_id: string) {
      if (!base_id) {
        return []
      }
      const response = await fetch(
        `https://api.airtable.com/v0/meta/bases/${base_id}/tables`,
        {
          headers: {
            Authorization: `Bearer ${auth.apiKey}`,
            Accept: "application/json",
          },
        }
      )
      if (!response.ok) {
        throw new Error(`${response.status} ${await response.text()}`)
      }
      const { tables } = (await response.json()) as {
        tables: {
          id: string
          name: string
          fields: { id: string; name: string; type: string }[]
          views: { id: string; name: string }[]
        }[]
      }
      return tables
    }
    
    // Dropdown of the tables in the chosen base.
    export async function table_id(auth: RT.Airtable, base_id: DynSelect_base_id) {
      return (await listTables(auth, base_id)).map((t) => ({
        value: t.id,
        label: t.name,
      }))
    }
    
    // Dropdown of the fields in the chosen table.
    export async function search_field(
      auth: RT.Airtable,
      base_id: DynSelect_base_id,
      table_id: DynSelect_table_id
    ) {
      const table = (await listTables(auth, base_id)).find((t) => t.id === table_id)
      return (table?.fields ?? []).map((f) => ({
        value: f.name,
        label: `${f.name} (${f.type})`,
      }))
    }
    
    /**
     * Search Records
     * Find records with an Airtable formula, or by matching search_value against search_field (substring match on text fields, exact match on number and checkbox fields). The formula wins when both are given. Returns every matching record. The field + value form reads the table schema, so it requires the schema.bases:read scope.
     */
    export async function main(
      auth: RT.Airtable,
      base_id: DynSelect_base_id,
      table_id: DynSelect_table_id,
      search_formula: string | undefined,
      search_field: DynSelect_search_field | undefined,
      search_value: string | undefined,
      return_fields_by_field_id: boolean | undefined
    ) {
      let formula = search_formula
      if (formula == null || formula === "") {
        if (!search_field || search_value == null || search_value === "") {
          throw new Error(
            "Provide either search_formula, or both search_field and search_value."
          )
        }
        const table = (await listTables(auth, base_id)).find(
          (t) => t.id === table_id || t.name === table_id
        )
        const type = table?.fields.find((f) => f.name === search_field)?.type
        const quoted = `"${search_value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`
        if (type === "checkbox") {
          formula = `{${search_field}} = ${search_value.toLowerCase() === "true" ? 1 : 0}`
        } else if (
          [
            "number",
            "percent",
            "currency",
            "rating",
            "duration",
            "count",
            "autoNumber",
          ].includes(type ?? "")
        ) {
          if (!Number.isFinite(Number(search_value))) {
            throw new Error(
              `"${search_value}" is not a number (field "${search_field}" is ${type}).`
            )
          }
          formula = `{${search_field}} = ${Number(search_value)}`
        } else {
          formula = `FIND(${quoted}, {${search_field}} & "")`
        }
      }
    
      const records: any[] = []
      let offset: string | undefined = undefined
    
      do {
        const url = new URL(`https://api.airtable.com/v0/${base_id}/${table_id}`)
        url.searchParams.append("filterByFormula", formula)
        if (return_fields_by_field_id === true) {
          url.searchParams.append("returnFieldsByFieldId", "true")
        }
        if (offset) {
          url.searchParams.append("offset", offset)
        }
    
        const response = await fetch(url, {
          headers: {
            Authorization: `Bearer ${auth.apiKey}`,
            Accept: "application/json",
          },
        })
        if (!response.ok) {
          throw new Error(`${response.status} ${await response.text()}`)
        }
    
        const page = await response.json()
        records.push(...(page.records ?? []))
        offset = page.offset
      } while (offset)
    
      return { records }
    }
    

    Submitted by hugo989 3 hours ago