multiwoven-integrations 0.1.34 → 0.1.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/multiwoven/integrations/core/constants.rb +6 -2
- data/lib/multiwoven/integrations/destination/airtable/client.rb +152 -0
- data/lib/multiwoven/integrations/destination/airtable/config/catalog.json +6 -0
- data/lib/multiwoven/integrations/destination/airtable/config/meta.json +15 -0
- data/lib/multiwoven/integrations/destination/airtable/config/spec.json +22 -0
- data/lib/multiwoven/integrations/destination/airtable/icon.svg +6 -0
- data/lib/multiwoven/integrations/destination/airtable/schema_helper.rb +141 -0
- data/lib/multiwoven/integrations/rollout.rb +2 -1
- data/lib/multiwoven/integrations.rb +1 -0
- metadata +7 -1
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 154bbe9f4e36d95f4cd7fdb8094077e2ff724a3eef95ec76264950747d619fe2
         | 
| 4 | 
            +
              data.tar.gz: b3da4210a997a0143b637a32d4d4b0dc6ba741742e5a1f73a3a465c8187479de
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: '052295ce22be1a08561f2d907bb1974c465302f2ec12cd6ae50afa976354bc13eb9ac0d178cd1b9eca86945cb18c4cb316ef05a44e6deac14ccc49f03fb57029'
         | 
| 7 | 
            +
              data.tar.gz: 35e541a5b3ea70f9761ff0c54b527a6334fcc91d5321018d4ce926163f8611eb4a4db714a2b36ad01c8e869854544ec63142bef2e2e99bd7cc0d3d2e022ebe2d
         | 
| @@ -18,6 +18,8 @@ module Multiwoven | |
| 18 18 | 
             
                  SNOWFLAKE_DRIVER_PATH = ENV["SNOWFLAKE_DRIVER_PATH"] || SNOWFLAKE_MAC_DRIVER_PATH
         | 
| 19 19 | 
             
                  DATABRICKS_DRIVER_PATH = ENV["DATABRICKS_DRIVER_PATH"] || DATABRICKS_MAC_DRIVER_PATH
         | 
| 20 20 |  | 
| 21 | 
            +
                  JSON_SCHEMA_URL = "https://json-schema.org/draft-07/schema#"
         | 
| 22 | 
            +
             | 
| 21 23 | 
             
                  # CONNECTORS
         | 
| 22 24 | 
             
                  KLAVIYO_AUTH_ENDPOINT = "https://a.klaviyo.com/api/lists/"
         | 
| 23 25 | 
             
                  KLAVIYO_AUTH_PAYLOAD = {
         | 
| @@ -31,14 +33,16 @@ module Multiwoven | |
| 31 33 |  | 
| 32 34 | 
             
                  FACEBOOK_AUDIENCE_GET_ALL_ACCOUNTS = "https://graph.facebook.com/v18.0/me/adaccounts?fields=id,name"
         | 
| 33 35 |  | 
| 36 | 
            +
                  AIRTABLE_URL_BASE = "https://api.airtable.com/v0/"
         | 
| 37 | 
            +
                  AIRTABLE_BASES_ENDPOINT = "https://api.airtable.com/v0/meta/bases"
         | 
| 38 | 
            +
                  AIRTABLE_GET_BASE_SCHEMA_ENDPOINT = "https://api.airtable.com/v0/meta/bases/{baseId}/tables"
         | 
| 39 | 
            +
             | 
| 34 40 | 
             
                  # HTTP
         | 
| 35 41 | 
             
                  HTTP_GET = "GET"
         | 
| 36 42 | 
             
                  HTTP_POST = "POST"
         | 
| 37 43 | 
             
                  HTTP_PUT = "PUT"
         | 
| 38 44 | 
             
                  HTTP_DELETE = "DELETE"
         | 
| 39 45 |  | 
| 40 | 
            -
                  JSON_SCHEMA_URL = "http://json-schema.org/draft-07/schema#"
         | 
| 41 | 
            -
             | 
| 42 46 | 
             
                  # google sheets
         | 
| 43 47 | 
             
                  GOOGLE_SHEETS_SCOPE = "https://www.googleapis.com/auth/drive"
         | 
| 44 48 | 
             
                  GOOGLE_SPREADSHEET_ID_REGEX = %r{/d/([-\w]{20,})/}.freeze
         | 
| @@ -0,0 +1,152 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative "schema_helper"
         | 
| 4 | 
            +
            module Multiwoven
         | 
| 5 | 
            +
              module Integrations
         | 
| 6 | 
            +
                module Destination
         | 
| 7 | 
            +
                  module Airtable
         | 
| 8 | 
            +
                    include Multiwoven::Integrations::Core
         | 
| 9 | 
            +
                    class Client < DestinationConnector # rubocop:disable Metrics/ClassLength
         | 
| 10 | 
            +
                      MAX_CHUNK_SIZE = 10
         | 
| 11 | 
            +
                      def check_connection(connection_config)
         | 
| 12 | 
            +
                        connection_config = connection_config.with_indifferent_access
         | 
| 13 | 
            +
                        bases = Multiwoven::Integrations::Core::HttpClient.request(
         | 
| 14 | 
            +
                          AIRTABLE_BASES_ENDPOINT,
         | 
| 15 | 
            +
                          HTTP_GET,
         | 
| 16 | 
            +
                          headers: auth_headers(connection_config[:api_key])
         | 
| 17 | 
            +
                        )
         | 
| 18 | 
            +
                        if success?(bases)
         | 
| 19 | 
            +
                          base_id_exists?(bases, connection_config[:base_id])
         | 
| 20 | 
            +
                          success_status
         | 
| 21 | 
            +
                        else
         | 
| 22 | 
            +
                          failure_status(nil)
         | 
| 23 | 
            +
                        end
         | 
| 24 | 
            +
                      rescue StandardError => e
         | 
| 25 | 
            +
                        failure_status(e)
         | 
| 26 | 
            +
                      end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                      def discover(connection_config)
         | 
| 29 | 
            +
                        connection_config = connection_config.with_indifferent_access
         | 
| 30 | 
            +
                        base_id = connection_config[:base_id]
         | 
| 31 | 
            +
                        api_key = connection_config[:api_key]
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                        bases = Multiwoven::Integrations::Core::HttpClient.request(
         | 
| 34 | 
            +
                          AIRTABLE_BASES_ENDPOINT,
         | 
| 35 | 
            +
                          HTTP_GET,
         | 
| 36 | 
            +
                          headers: auth_headers(api_key)
         | 
| 37 | 
            +
                        )
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                        base = extract_bases(bases).find { |b| b["id"] == base_id }
         | 
| 40 | 
            +
                        base_name = base["name"]
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                        schema = Multiwoven::Integrations::Core::HttpClient.request(
         | 
| 43 | 
            +
                          AIRTABLE_GET_BASE_SCHEMA_ENDPOINT.gsub("{baseId}", base_id),
         | 
| 44 | 
            +
                          HTTP_GET,
         | 
| 45 | 
            +
                          headers: auth_headers(api_key)
         | 
| 46 | 
            +
                        )
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                        catalog = build_catalog_from_schema(extract_body(schema), base_id, base_name)
         | 
| 49 | 
            +
                        catalog.to_multiwoven_message
         | 
| 50 | 
            +
                      rescue StandardError => e
         | 
| 51 | 
            +
                        handle_exception("AIRTABLE:DISCOVER:EXCEPTION", "error", e)
         | 
| 52 | 
            +
                      end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                      def write(sync_config, records, _action = "create")
         | 
| 55 | 
            +
                        connection_config = sync_config.destination.connection_specification.with_indifferent_access
         | 
| 56 | 
            +
                        api_key = connection_config[:api_key]
         | 
| 57 | 
            +
                        url = sync_config.stream.url
         | 
| 58 | 
            +
                        write_success = 0
         | 
| 59 | 
            +
                        write_failure = 0
         | 
| 60 | 
            +
                        records.each_slice(MAX_CHUNK_SIZE) do |chunk|
         | 
| 61 | 
            +
                          payload = create_payload(chunk)
         | 
| 62 | 
            +
                          response = Multiwoven::Integrations::Core::HttpClient.request(
         | 
| 63 | 
            +
                            url,
         | 
| 64 | 
            +
                            sync_config.stream.request_method,
         | 
| 65 | 
            +
                            payload: payload,
         | 
| 66 | 
            +
                            headers: auth_headers(api_key)
         | 
| 67 | 
            +
                          )
         | 
| 68 | 
            +
                          if success?(response)
         | 
| 69 | 
            +
                            write_success += chunk.size
         | 
| 70 | 
            +
                          else
         | 
| 71 | 
            +
                            write_failure += chunk.size
         | 
| 72 | 
            +
                          end
         | 
| 73 | 
            +
                        rescue StandardError => e
         | 
| 74 | 
            +
                          handle_exception("AIRTABLE:RECORD:WRITE:EXCEPTION", "error", e)
         | 
| 75 | 
            +
                          write_failure += chunk.size
         | 
| 76 | 
            +
                        end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                        tracker = Multiwoven::Integrations::Protocol::TrackingMessage.new(
         | 
| 79 | 
            +
                          success: write_success,
         | 
| 80 | 
            +
                          failed: write_failure
         | 
| 81 | 
            +
                        )
         | 
| 82 | 
            +
                        tracker.to_multiwoven_message
         | 
| 83 | 
            +
                      rescue StandardError => e
         | 
| 84 | 
            +
                        handle_exception("AIRTABLE:WRITE:EXCEPTION", "error", e)
         | 
| 85 | 
            +
                      end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                      private
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                      def create_payload(records)
         | 
| 90 | 
            +
                        {
         | 
| 91 | 
            +
                          "records" => records.map do |record|
         | 
| 92 | 
            +
                            {
         | 
| 93 | 
            +
                              "fields" => record
         | 
| 94 | 
            +
                            }
         | 
| 95 | 
            +
                          end
         | 
| 96 | 
            +
                        }
         | 
| 97 | 
            +
                      end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                      def auth_headers(access_token)
         | 
| 100 | 
            +
                        {
         | 
| 101 | 
            +
                          "Accept" => "application/json",
         | 
| 102 | 
            +
                          "Authorization" => "Bearer #{access_token}",
         | 
| 103 | 
            +
                          "Content-Type" => "application/json"
         | 
| 104 | 
            +
                        }
         | 
| 105 | 
            +
                      end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                      def base_id_exists?(bases, base_id)
         | 
| 108 | 
            +
                        return if extract_data(bases).any? { |base| base["id"] == base_id }
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                        raise ArgumentError, "base_id not found"
         | 
| 111 | 
            +
                      end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                      def extract_bases(response)
         | 
| 114 | 
            +
                        response_body = extract_body(response)
         | 
| 115 | 
            +
                        response_body["bases"] if response_body
         | 
| 116 | 
            +
                      end
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                      def extract_body(response)
         | 
| 119 | 
            +
                        response_body = response.body
         | 
| 120 | 
            +
                        JSON.parse(response_body) if response_body
         | 
| 121 | 
            +
                      end
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                      def load_catalog
         | 
| 124 | 
            +
                        read_json(CATALOG_SPEC_PATH)
         | 
| 125 | 
            +
                      end
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                      def create_stream(table, base_id, base_name)
         | 
| 128 | 
            +
                        {
         | 
| 129 | 
            +
                          name: "#{base_name}/#{SchemaHelper.clean_name(table["name"])}",
         | 
| 130 | 
            +
                          action: "create",
         | 
| 131 | 
            +
                          method: HTTP_POST,
         | 
| 132 | 
            +
                          url: "#{AIRTABLE_URL_BASE}#{base_id}/#{table["id"]}",
         | 
| 133 | 
            +
                          json_schema: SchemaHelper.get_json_schema(table),
         | 
| 134 | 
            +
                          supported_sync_modes: %w[incremental],
         | 
| 135 | 
            +
                          batch_support: true,
         | 
| 136 | 
            +
                          batch_size: 10
         | 
| 137 | 
            +
             | 
| 138 | 
            +
                        }.with_indifferent_access
         | 
| 139 | 
            +
                      end
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                      def build_catalog_from_schema(schema, base_id, base_name)
         | 
| 142 | 
            +
                        catalog = build_catalog(load_catalog)
         | 
| 143 | 
            +
                        schema["tables"].each do |table|
         | 
| 144 | 
            +
                          catalog.streams << build_stream(create_stream(table, base_id, base_name))
         | 
| 145 | 
            +
                        end
         | 
| 146 | 
            +
                        catalog
         | 
| 147 | 
            +
                      end
         | 
| 148 | 
            +
                    end
         | 
| 149 | 
            +
                  end
         | 
| 150 | 
            +
                end
         | 
| 151 | 
            +
              end
         | 
| 152 | 
            +
            end
         | 
| @@ -0,0 +1,15 @@ | |
| 1 | 
            +
            {
         | 
| 2 | 
            +
              "data": {
         | 
| 3 | 
            +
                "name": "Airtable",
         | 
| 4 | 
            +
                "title": "airtable",
         | 
| 5 | 
            +
                "connector_type": "destination",
         | 
| 6 | 
            +
                "category": "Productivity Tools",
         | 
| 7 | 
            +
                "documentation_url": "https://docs.multiwoven.com/destinations/productivity-tools/airtable",
         | 
| 8 | 
            +
                "github_issue_label": "destination-airtable",
         | 
| 9 | 
            +
                "icon": "icon.svg",
         | 
| 10 | 
            +
                "license": "MIT",
         | 
| 11 | 
            +
                "release_stage": "alpha",
         | 
| 12 | 
            +
                "support_level": "community",
         | 
| 13 | 
            +
                "tags": ["language:ruby", "multiwoven"]
         | 
| 14 | 
            +
              }
         | 
| 15 | 
            +
            }
         | 
| @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            {
         | 
| 2 | 
            +
              "documentation_url": "https://docs.multiwoven.com/integrations/destination/airtable",
         | 
| 3 | 
            +
              "stream_type": "dynamic",
         | 
| 4 | 
            +
              "connection_specification": {
         | 
| 5 | 
            +
                "$schema": "http://json-schema.org/draft-07/schema#",
         | 
| 6 | 
            +
                "title": "Airtable",
         | 
| 7 | 
            +
                "type": "object",
         | 
| 8 | 
            +
                "required": ["api_key", "base_id"],
         | 
| 9 | 
            +
                "properties": {
         | 
| 10 | 
            +
                  "api_key": {
         | 
| 11 | 
            +
                    "type": "string",
         | 
| 12 | 
            +
                    "title": "Personal access token",
         | 
| 13 | 
            +
                    "order": 0
         | 
| 14 | 
            +
                  },
         | 
| 15 | 
            +
                  "base_id": {
         | 
| 16 | 
            +
                    "type": "string",
         | 
| 17 | 
            +
                    "title": "Airtable base id",
         | 
| 18 | 
            +
                    "order": 1
         | 
| 19 | 
            +
                  }
         | 
| 20 | 
            +
                }
         | 
| 21 | 
            +
              }
         | 
| 22 | 
            +
            }
         | 
| @@ -0,0 +1,6 @@ | |
| 1 | 
            +
            <svg xmlns="http://www.w3.org/2000/svg" width="32" height="27" shape-rendering="geometricPrecision" viewBox="0 0 200 170">
         | 
| 2 | 
            +
              <path fill="#FCB400" d="M90.039 12.367L24.079 39.66c-3.667 1.519-3.63 6.729.062 8.192l66.235 26.266a24.575 24.575 0 0018.12 0l66.236-26.266c3.69-1.463 3.729-6.673.06-8.191l-65.958-27.294a24.578 24.578 0 00-18.795 0"></path>
         | 
| 3 | 
            +
              <path fill="#18BFFF" d="M105.312 88.46v65.617c0 3.12 3.147 5.258 6.048 4.108l73.806-28.648a4.418 4.418 0 002.79-4.108V59.813c0-3.121-3.147-5.258-6.048-4.108l-73.806 28.648a4.42 4.42 0 00-2.79 4.108"></path>
         | 
| 4 | 
            +
              <path fill="#F82B60" d="M88.078 91.846l-21.904 10.576-2.224 1.075-46.238 22.155c-2.93 1.414-6.672-.722-6.672-3.978V60.088c0-1.178.604-2.195 1.414-2.96a5.024 5.024 0 011.12-.84c1.104-.663 2.68-.84 4.02-.31L87.71 83.76c3.564 1.414 3.844 6.408.368 8.087"></path>
         | 
| 5 | 
            +
              <path fill="rgba(0, 0, 0, 0.25)" d="M88.078 91.846l-21.904 10.576-53.72-45.295a5.024 5.024 0 011.12-.839c1.104-.663 2.68-.84 4.02-.31L87.71 83.76c3.564 1.414 3.844 6.408.368 8.087"></path>
         | 
| 6 | 
            +
            </svg>
         | 
| @@ -0,0 +1,141 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Multiwoven
         | 
| 4 | 
            +
              module Integrations
         | 
| 5 | 
            +
                module Destination
         | 
| 6 | 
            +
                  module Airtable
         | 
| 7 | 
            +
                    module SchemaHelper
         | 
| 8 | 
            +
                      include Core::Constants
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                      module_function
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                      def clean_name(name_str)
         | 
| 13 | 
            +
                        name_str.strip.gsub(" ", "_")
         | 
| 14 | 
            +
                      end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                      def get_json_schema(table)
         | 
| 17 | 
            +
                        fields = table["fields"] || {}
         | 
| 18 | 
            +
                        properties = fields.each_with_object({}) do |field, props|
         | 
| 19 | 
            +
                          name, schema = process_field(field)
         | 
| 20 | 
            +
                          props[name] = schema
         | 
| 21 | 
            +
                        end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                        build_schema(properties)
         | 
| 24 | 
            +
                      end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                      def process_field(field)
         | 
| 27 | 
            +
                        name = clean_name(field.fetch("name", ""))
         | 
| 28 | 
            +
                        original_type = field.fetch("type", "")
         | 
| 29 | 
            +
                        options = field.fetch("options", {})
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                        schema = determine_schema(original_type, options)
         | 
| 32 | 
            +
                        [name, schema]
         | 
| 33 | 
            +
                      end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                      def determine_schema(original_type, options)
         | 
| 36 | 
            +
                        if COMPLEX_AIRTABLE_TYPES.keys.include?(original_type)
         | 
| 37 | 
            +
                          complex_type = deep_copy(COMPLEX_AIRTABLE_TYPES[original_type])
         | 
| 38 | 
            +
                          adjust_complex_type(original_type, complex_type, options)
         | 
| 39 | 
            +
                        elsif SIMPLE_AIRTABLE_TYPES.keys.include?(original_type)
         | 
| 40 | 
            +
                          simple_type_schema(original_type, options)
         | 
| 41 | 
            +
                        else
         | 
| 42 | 
            +
                          SCHEMA_TYPES[:STRING]
         | 
| 43 | 
            +
                        end
         | 
| 44 | 
            +
                      end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                      def adjust_complex_type(original_type, complex_type, options)
         | 
| 47 | 
            +
                        exec_type = options.dig("result", "type") || "simpleText"
         | 
| 48 | 
            +
                        if complex_type == SCHEMA_TYPES[:ARRAY_WITH_ANY]
         | 
| 49 | 
            +
                          adjust_array_with_any(original_type, complex_type, exec_type, options)
         | 
| 50 | 
            +
                        else
         | 
| 51 | 
            +
                          complex_type
         | 
| 52 | 
            +
                        end
         | 
| 53 | 
            +
                      end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                      def adjust_array_with_any(original_type, complex_type, exec_type, options)
         | 
| 56 | 
            +
                        if original_type == "formula" && %w[number currency percent duration].include?(exec_type)
         | 
| 57 | 
            +
                          complex_type = SCHEMA_TYPES[:NUMBER]
         | 
| 58 | 
            +
                        elsif original_type == "formula" && ARRAY_FORMULAS.none? { |x| options.fetch("formula", "").start_with?(x) }
         | 
| 59 | 
            +
                          complex_type = SCHEMA_TYPES[:STRING]
         | 
| 60 | 
            +
                        elsif SIMPLE_AIRTABLE_TYPES.keys.include?(exec_type)
         | 
| 61 | 
            +
                          complex_type["items"] = deep_copy(SIMPLE_AIRTABLE_TYPES[exec_type])
         | 
| 62 | 
            +
                        else
         | 
| 63 | 
            +
                          complex_type["items"] = SCHEMA_TYPES[:STRING]
         | 
| 64 | 
            +
                        end
         | 
| 65 | 
            +
                        complex_type
         | 
| 66 | 
            +
                      end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                      def simple_type_schema(original_type, options)
         | 
| 69 | 
            +
                        exec_type = options.dig("result", "type") || original_type
         | 
| 70 | 
            +
                        deep_copy(SIMPLE_AIRTABLE_TYPES[exec_type])
         | 
| 71 | 
            +
                      end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                      def build_schema(properties)
         | 
| 74 | 
            +
                        {
         | 
| 75 | 
            +
                          "$schema" => JSON_SCHEMA_URL,
         | 
| 76 | 
            +
                          "type" => "object",
         | 
| 77 | 
            +
                          "additionalProperties" => true,
         | 
| 78 | 
            +
                          "properties" => properties
         | 
| 79 | 
            +
                        }
         | 
| 80 | 
            +
                      end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                      def deep_copy(object)
         | 
| 83 | 
            +
                        Marshal.load(Marshal.dump(object))
         | 
| 84 | 
            +
                      end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                      SCHEMA_TYPES = {
         | 
| 87 | 
            +
                        STRING: { "type": %w[null string] },
         | 
| 88 | 
            +
                        NUMBER: { "type": %w[null number] },
         | 
| 89 | 
            +
                        BOOLEAN: { "type": %w[null boolean] },
         | 
| 90 | 
            +
                        DATE: { "type": %w[null string], "format": "date" },
         | 
| 91 | 
            +
                        DATETIME: { "type": %w[null string], "format": "date-time" },
         | 
| 92 | 
            +
                        ARRAY_WITH_STRINGS: { "type": %w[null array], "items": { "type": %w[null string] } },
         | 
| 93 | 
            +
                        ARRAY_WITH_ANY: { "type": %w[null array], "items": {} }
         | 
| 94 | 
            +
                      }.freeze.with_indifferent_access
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                      SIMPLE_AIRTABLE_TYPES = {
         | 
| 97 | 
            +
                        "multipleAttachments" => SCHEMA_TYPES[:STRING],
         | 
| 98 | 
            +
                        "autoNumber" => SCHEMA_TYPES[:NUMBER],
         | 
| 99 | 
            +
                        "barcode" => SCHEMA_TYPES[:STRING],
         | 
| 100 | 
            +
                        "button" => SCHEMA_TYPES[:STRING],
         | 
| 101 | 
            +
                        "checkbox" => :BOOLEAN,
         | 
| 102 | 
            +
                        "singleCollaborator" => SCHEMA_TYPES[:STRING],
         | 
| 103 | 
            +
                        "count" => SCHEMA_TYPES[:NUMBER],
         | 
| 104 | 
            +
                        "createdBy" => SCHEMA_TYPES[:STRING],
         | 
| 105 | 
            +
                        "createdTime" => SCHEMA_TYPES[:DATETIME],
         | 
| 106 | 
            +
                        "currency" => SCHEMA_TYPES[:NUMBER],
         | 
| 107 | 
            +
                        "email" => SCHEMA_TYPES[:STRING],
         | 
| 108 | 
            +
                        "date" => SCHEMA_TYPES[:DATE],
         | 
| 109 | 
            +
                        "dateTime" => SCHEMA_TYPES[:DATETIME],
         | 
| 110 | 
            +
                        "duration" => SCHEMA_TYPES[:NUMBER],
         | 
| 111 | 
            +
                        "lastModifiedBy" => SCHEMA_TYPES[:STRING],
         | 
| 112 | 
            +
                        "lastModifiedTime" => SCHEMA_TYPES[:DATETIME],
         | 
| 113 | 
            +
                        "multipleRecordLinks" => SCHEMA_TYPES[:ARRAY_WITH_STRINGS],
         | 
| 114 | 
            +
                        "multilineText" => SCHEMA_TYPES[:STRING],
         | 
| 115 | 
            +
                        "multipleCollaborators" => SCHEMA_TYPES[:ARRAY_WITH_STRINGS],
         | 
| 116 | 
            +
                        "multipleSelects" => SCHEMA_TYPES[:ARRAY_WITH_STRINGS],
         | 
| 117 | 
            +
                        "number" => SCHEMA_TYPES[:NUMBER],
         | 
| 118 | 
            +
                        "percent" => SCHEMA_TYPES[:NUMBER],
         | 
| 119 | 
            +
                        "phoneNumber" => SCHEMA_TYPES[:STRING],
         | 
| 120 | 
            +
                        "rating" => SCHEMA_TYPES[:NUMBER],
         | 
| 121 | 
            +
                        "richText" => SCHEMA_TYPES[:STRING],
         | 
| 122 | 
            +
                        "singleLineText" => SCHEMA_TYPES[:STRING],
         | 
| 123 | 
            +
                        "singleSelect" => SCHEMA_TYPES[:STRING],
         | 
| 124 | 
            +
                        "externalSyncSource" => SCHEMA_TYPES[:STRING],
         | 
| 125 | 
            +
                        "url" => SCHEMA_TYPES[:STRING],
         | 
| 126 | 
            +
                        "simpleText" => SCHEMA_TYPES[:STRING]
         | 
| 127 | 
            +
                      }.freeze
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                      COMPLEX_AIRTABLE_TYPES = {
         | 
| 130 | 
            +
                        "formula" => SCHEMA_TYPES[:ARRAY_WITH_ANY],
         | 
| 131 | 
            +
                        "lookup" => SCHEMA_TYPES[:ARRAY_WITH_ANY],
         | 
| 132 | 
            +
                        "multipleLookupValues" => SCHEMA_TYPES[:ARRAY_WITH_ANY],
         | 
| 133 | 
            +
                        "rollup" => SCHEMA_TYPES[:ARRAY_WITH_ANY]
         | 
| 134 | 
            +
                      }.freeze.with_indifferent_access
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                      ARRAY_FORMULAS = %w[ARRAYCOMPACT ARRAYFLATTEN ARRAYUNIQUE ARRAYSLICE].freeze
         | 
| 137 | 
            +
                    end
         | 
| 138 | 
            +
                  end
         | 
| 139 | 
            +
                end
         | 
| 140 | 
            +
              end
         | 
| 141 | 
            +
            end
         | 
| @@ -48,6 +48,7 @@ require_relative "integrations/destination/facebook_custom_audience/client" | |
| 48 48 | 
             
            require_relative "integrations/destination/slack/client"
         | 
| 49 49 | 
             
            require_relative "integrations/destination/hubspot/client"
         | 
| 50 50 | 
             
            require_relative "integrations/destination/google_sheets/client"
         | 
| 51 | 
            +
            require_relative "integrations/destination/airtable/client"
         | 
| 51 52 |  | 
| 52 53 | 
             
            module Multiwoven
         | 
| 53 54 | 
             
              module Integrations
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: multiwoven-integrations
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.1. | 
| 4 | 
            +
              version: 0.1.35
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Subin T P
         | 
| @@ -330,6 +330,12 @@ files: | |
| 330 330 | 
             
            - lib/multiwoven/integrations/core/rate_limiter.rb
         | 
| 331 331 | 
             
            - lib/multiwoven/integrations/core/source_connector.rb
         | 
| 332 332 | 
             
            - lib/multiwoven/integrations/core/utils.rb
         | 
| 333 | 
            +
            - lib/multiwoven/integrations/destination/airtable/client.rb
         | 
| 334 | 
            +
            - lib/multiwoven/integrations/destination/airtable/config/catalog.json
         | 
| 335 | 
            +
            - lib/multiwoven/integrations/destination/airtable/config/meta.json
         | 
| 336 | 
            +
            - lib/multiwoven/integrations/destination/airtable/config/spec.json
         | 
| 337 | 
            +
            - lib/multiwoven/integrations/destination/airtable/icon.svg
         | 
| 338 | 
            +
            - lib/multiwoven/integrations/destination/airtable/schema_helper.rb
         | 
| 333 339 | 
             
            - lib/multiwoven/integrations/destination/facebook_custom_audience/client.rb
         | 
| 334 340 | 
             
            - lib/multiwoven/integrations/destination/facebook_custom_audience/config/catalog.json
         | 
| 335 341 | 
             
            - lib/multiwoven/integrations/destination/facebook_custom_audience/config/meta.json
         |