Skip to main content

Overview

The @databite/build package provides the core functionality for creating connectors using a fluent API. It includes the ConnectorBuilder class, action and sync creators, and helper functions for building type-safe connectors.

Installation

npm install @databite/build @databite/types @databite/flow
Peer Dependencies:
npm install zod typescript

Core Classes

ConnectorBuilder

The main class for building connectors with a fluent API:
import { createConnector } from "@databite/build";
import { z } from "zod";

const connector = createConnector()
  .withIdentity("my-service", "My Service")
  .withVersion("1.0.0")
  .withAuthor("Your Name")
  .withLogo("https://example.com/logo.png")
  .withDescription("Connector for My Service")
  .build();

Builder Methods

Identity & Metadata

// Set connector identity
.withIdentity(id: string, name: string)

// Set version
.withVersion(version: string)

// Set author
.withAuthor(author: string)

// Set logo URL
.withLogo(logo: string)

// Set documentation URL
.withDocumentationUrl(url: string)

// Set description
.withDescription(description: string)

Configuration

// Add integration configuration schema
.withIntegrationConfig(config: ZodSchema)

// Add connection configuration schema
.withConnectionConfig(config: ZodSchema)

// Add tags for categorization
.withTags(...tags: string[])

// Add categories
.withCategories(...categories: ConnectorCategory[])

Flows

// Set authentication flow
.withAuthenticationFlow(flow: Flow<TConnectionConfig>)

// Set refresh function
.withRefresh(refresh: (connection: Connection<TConnectionConfig>) => Promise<z.infer<TConnectionConfig>>)

Actions & Syncs

// Add actions
.withActions(actions: Record<string, Action>)

// Add syncs
.withSyncs(syncs: Record<string, Sync>)

Helper Functions

createAction

Creates an action with automatic retry logic and timeout handling:
import { createAction } from "@databite/build";
import { z } from "zod";

const action = createAction({
  label: "Get User",
  description: "Fetch user by ID",
  inputSchema: z.object({ id: z.string() }),
  outputSchema: z.object({
    user: z.object({ id: z.string(), name: z.string() }),
  }),
  maxRetries: 3,
  timeout: 30000,
  handler: async (params, connection) => {
    // Your implementation
    return { user: { id: params.id, name: "John Doe" } };
  },
});

createSync

Creates a sync operation for data synchronization:
import { createSync } from "@databite/build";

const sync = createSync({
  label: "Sync Users",
  description: "Synchronize user data",
  schedule: "0 9 * * *", // Daily at 9 AM
  outputSchema: z.array(z.object({ id: z.string() })),
  maxRetries: 3,
  timeout: 60000,
  handler: async (connection) => {
    // Your sync implementation
    return [{ id: "1" }, { id: "2" }];
  },
});

Complete Example

Basic Connector

import { createConnector, createAction, createSync } from "@databite/build";
import { createFlow } from "@databite/flow";
import { z } from "zod";

// Define configuration schemas
const integrationConfig = z.object({
  apiKey: z.string(),
  baseUrl: z.string().url(),
});

const connectionConfig = z.object({
  userId: z.string(),
  accessToken: z.string(),
});

// Create authentication flow
const authFlow = createFlow("authenticate").httpBlock("validate", {
  url: (input) => `${input.baseUrl}/auth/validate`,
  method: "POST",
  headers: { Authorization: `Bearer ${input.apiKey}` },
});

// Create the connector
const myConnector = createConnector()
  .withIdentity("my-service", "My Service")
  .withVersion("1.0.0")
  .withAuthor("Your Name")
  .withLogo("https://example.com/logo.png")
  .withDescription("Connector for My Service API")
  .withIntegrationConfig(integrationConfig)
  .withConnectionConfig(connectionConfig)
  .withAuthenticationFlow(authFlow)
  .withActions({
    getUser: createAction({
      label: "Get User",
      description: "Fetch user information",
      inputSchema: z.object({ id: z.string() }),
      outputSchema: z.object({ user: z.any() }),
      handler: async (params, connection) => {
        const response = await fetch(
          `${connection.config.baseUrl}/users/${params.id}`,
          {
            headers: {
              Authorization: `Bearer ${connection.config.accessToken}`,
            },
          }
        );
        return { user: await response.json() };
      },
    }),
  })
  .withSyncs({
    syncUsers: createSync({
      label: "Sync Users",
      description: "Synchronize all users",
      schedule: "0 2 * * *", // Daily at 2 AM
      outputSchema: z.array(z.any()),
      handler: async (connection) => {
        const response = await fetch(`${connection.config.baseUrl}/users`, {
          headers: { Authorization: `Bearer ${connection.config.accessToken}` },
        });
        return await response.json();
      },
    }),
  })
  .withTags("api", "users", "saas")
  .build();

Advanced Connector with Error Handling

import { createConnector, createAction } from "@databite/build";
import { z } from "zod";

const advancedConnector = createConnector()
  .withIdentity("advanced-service", "Advanced Service")
  .withVersion("2.0.0")
  .withAuthor("Advanced Team")
  .withLogo("https://example.com/advanced-logo.png")
  .withDescription("Advanced connector with comprehensive error handling")
  .withIntegrationConfig(
    z.object({
      apiKey: z.string(),
      retryAttempts: z.number().default(3),
      timeout: z.number().default(30000),
    })
  )
  .withConnectionConfig(
    z.object({
      accessToken: z.string(),
      refreshToken: z.string().optional(),
    })
  )
  .withActions({
    complexOperation: createAction({
      label: "Complex Operation",
      description: "Perform a complex operation with error handling",
      inputSchema: z.object({
        data: z.any(),
        options: z
          .object({
            validate: z.boolean().default(true),
            timeout: z.number().default(30000),
          })
          .optional(),
      }),
      outputSchema: z.object({
        result: z.any(),
        metadata: z.object({
          processedAt: z.string(),
          duration: z.number(),
        }),
      }),
      maxRetries: 5,
      timeout: 60000,
      handler: async (params, connection) => {
        const startTime = Date.now();

        try {
          // Validate input if requested
          if (params.options?.validate) {
            // Add validation logic here
          }

          // Perform the operation
          const result = await performComplexOperation(params.data, connection);

          return {
            result,
            metadata: {
              processedAt: new Date().toISOString(),
              duration: Date.now() - startTime,
            },
          };
        } catch (error) {
          console.error("Complex operation failed:", error);
          throw error;
        }
      },
    }),
  })
  .build();

async function performComplexOperation(data: any, connection: any) {
  // Your complex operation implementation
  return { processed: data };
}

Configuration

Integration Configuration

Define the schema for integration-level configuration:
const integrationConfig = z.object({
  apiKey: z.string().min(1, "API key is required"),
  baseUrl: z.string().url("Must be a valid URL"),
  timeout: z.number().min(1000).max(60000).default(30000),
  retryAttempts: z.number().min(0).max(10).default(3),
});

Connection Configuration

Define the schema for connection-level configuration:
const connectionConfig = z.object({
  accessToken: z.string(),
  refreshToken: z.string().optional(),
  userId: z.string(),
  permissions: z.array(z.string()).optional(),
});

Best Practices

1. Use Descriptive Names

// Good
.withIdentity("slack-api", "Slack API Integration")

// Avoid
.withIdentity("slack", "Slack")

2. Provide Clear Descriptions

// Good
.withDescription("Connect to Slack workspace to send messages, manage channels, and sync user data")

// Avoid
.withDescription("Slack connector")

3. Use Proper Error Handling

const action = createAction({
  // ... other config
  handler: async (params, connection) => {
    try {
      const result = await apiCall(params);
      return result;
    } catch (error) {
      if (error.status === 401) {
        throw new Error(
          "Authentication failed. Please check your credentials."
        );
      }
      if (error.status === 429) {
        throw new Error("Rate limit exceeded. Please try again later.");
      }
      throw new Error(`API call failed: ${error.message}`);
    }
  },
});

4. Use Appropriate Timeouts

// For quick operations
const quickAction = createAction({
  timeout: 10000, // 10 seconds
  // ...
});

// For data-intensive operations
const dataSync = createSync({
  timeout: 300000, // 5 minutes
  // ...
});

Testing

Unit Testing Actions

import { createAction } from "@databite/build";

const testAction = createAction({
  label: "Test Action",
  description: "Test action for unit testing",
  inputSchema: z.object({ input: z.string() }),
  outputSchema: z.object({ output: z.string() }),
  handler: async (params) => {
    return { output: `processed: ${params.input}` };
  },
});

// Test the action
const result = await testAction.handler({ input: "test" }, mockConnection);
expect(result).toEqual({ output: "processed: test" });

TypeScript Support

The package provides full TypeScript support with automatic type inference:
// Types are automatically inferred from schemas
const connector = createConnector()
  .withIntegrationConfig(
    z.object({
      apiKey: z.string(),
      baseUrl: z.string(),
    })
  )
  .withConnectionConfig(
    z.object({
      accessToken: z.string(),
    })
  )
  .build();

// TypeScript knows the exact shape of integration and connection configs
type IntegrationConfig = z.infer<typeof connector.integrationConfig>;
type ConnectionConfig = z.infer<typeof connector.connectionConfig>;
I