npm package: @flowyr/sdk
Description: Official Node.js SDK for the Flowyr API
Author: Flowyr
Latest version: 1.0.0

# @flowyr/sdk

Official Node.js/TypeScript SDK for the Flowyr API. Build powerful social media automation tools with type-safe, intuitive API access.

## Features

- **Zero Configuration**: Auto-detects API key from environment variables
- **Full TypeScript Support**: Complete type definitions with auto-completion
- **Intuitive API**: Resource-based organization matching REST endpoints
- **Consistent Error Handling**: Predictable `{ data, error }` response pattern
- **Production Ready**: Built-in timeouts, automatic retries, and rate limit handling
- **Idempotency Keys**: Prevent duplicate operations with built-in client-side caching
- **Request Cancellation**: Cancel in-flight requests using AbortController
- **Debug Logging**: Optional detailed request/response logging
- **Request Tracking**: Unique request IDs for debugging
- **Webhook Security**: Built-in signature verification utilities
- **Testing Support**: Full-featured mock client for testing without API calls

## Installation

```bash
npm install @flowyr/sdk
```

## Quick Start

```typescript
import { Flowyr } from '@flowyr/sdk';

// Option 1: Use environment variable FLOWYR_API_KEY
const flowyr = new Flowyr();

// Option 2: Pass API key directly
const flowyr = new Flowyr('fl_your_api_key');

// Option 3: Advanced configuration
const flowyr = new Flowyr({
  apiKey: 'fl_your_api_key',
  baseUrl: 'https://api.flowyr.io',
  timeout: 30000,
  debug: false, // Enable debug logging
  retry: { maxRetries: 3, initialDelay: 1000 }, // Configure retries
});
```

## Usage Examples

### Creating Posts

```typescript
// Immediate post
const { data, error } = await flowyr.posts.create({
  connectionIds: ['con_123', 'con_456'],
  text: 'Hello world! 🚀',
  tags: ['announcement'],
});

if (error) {
  console.error('Failed to create post:', error.message);
} else {
  console.log('Post queued:', data.jobId);
}

// Scheduled post
await flowyr.posts.create({
  connectionIds: ['con_123'],
  text: 'Happy Monday team!',
  scheduledFor: '2025-12-08T09:00:00Z',
  priority: 3,
});

// Recurring post
await flowyr.posts.create({
  connectionIds: ['con_123'],
  text: 'Weekly update!',
  scheduledFor: '2025-12-08T09:00:00Z',
  isRecurring: true,
  frequency: 'weekly',
  recurrenceEndsAt: '2026-01-01T00:00:00Z',
});
```

### Managing Connections

```typescript
// List all connections
const { data: connections } = await flowyr.connections.list();

// Filter by platform
const { data: xConnections } = await flowyr.connections.list({
  platform: 'x',
  status: 'active',
});

// Get specific connection
const { data: connection } = await flowyr.connections.get('con_123');

// Test connection
const { data: testResult } = await flowyr.connections.test('con_123');
if (testResult?.success) {
  console.log('Connection is valid!');
}
```

### Working with Scheduler

```typescript
// List scheduled tasks
const { data: tasks } = await flowyr.scheduler.listTasks({
  status: 'PENDING',
  page: 1,
  limit: 20,
});

// Iterate through all pending tasks (auto-pagination)
for await (const task of flowyr.scheduler.listAllTasks({ status: 'PENDING' })) {
  console.log(task);
}

// Get specific task
const { data: task } = await flowyr.scheduler.getTask('task_abc123');

// Update task
await flowyr.scheduler.updateTask('task_abc123', {
  scheduledFor: '2025-12-10T10:00:00Z',
  priority: 5,
});

// Cancel task
await flowyr.scheduler.deleteTask('task_abc123');
```

### Uploading Media

```typescript
import { fileToDataUrl } from '@flowyr/sdk';

// Convert file to base64 data URL (browser)
const dataUrl = await fileToDataUrl(file);

// Upload single file
const { data, error } = await flowyr.media.upload({
  dataUrl,
  fileName: file.name,
});

// Upload multiple files
const { data: uploads } = await flowyr.media.uploadMultiple({
  mediaItems: [
    { dataUrl: dataUrl1, fileName: 'image1.jpg' },
    { dataUrl: dataUrl2, fileName: 'image2.jpg' },
  ],
});

// Use media URLs in posts
await flowyr.posts.create({
  connectionIds: ['con_123'],
  text: 'Check out these images!',
  mediaUrls: uploads.map(upload => upload.url),
});
```

### Auto-Pagination

```typescript
// Iterate through all posts (limit/offset pagination)
for await (const post of flowyr.posts.listAll({ status: 'success' })) {
  console.log(post);
}

// Fetch all as array (warning: loads all into memory)
const { data: allPosts } = await flowyr.posts.fetchAll({ status: 'success' });
console.log(`Found ${allPosts.length} successful posts`);
```

### Idempotency Keys

Prevent duplicate operations using idempotency keys. The SDK provides client-side caching with automatic cleanup:

```typescript
import { generateIdempotencyKey, createDeterministicKey } from '@flowyr/sdk';

// Auto-generated key (perfect for retry logic)
const key = generateIdempotencyKey('post');
await flowyr.posts.create({
  connectionIds: ['con_123'],
  text: 'Important announcement!',
  idempotencyKey: key,
});

// Calling again with the same key returns cached response (no API call)
await flowyr.posts.create({
  connectionIds: ['con_123'],
  text: 'Important announcement!',
  idempotencyKey: key, // Returns cached response
});

// Deterministic key based on content (prevents duplicate scheduled posts)
const deterministicKey = createDeterministicKey({
  connectionIds: ['con_123'],
  text: 'Daily standup meeting',
  scheduledFor: '2025-12-31T10:00:00Z',
});

// Custom key (enforce business rules)
const customKey = `announcement-${new Date().toISOString().split('T')[0]}`;
await flowyr.posts.create({
  connectionIds: ['con_123'],
  text: 'Daily update',
  idempotencyKey: customKey, // One announcement per day
});

// Configure client-side caching
const flowyr = new Flowyr({
  apiKey: 'fl_your_api_key',
  enableIdempotencyCache: true, // Default: true (60s TTL)
});
```

**When to use idempotency keys:**
- Retry logic (prevent duplicate posts on network errors)
- User interface (prevent double-click submissions)
- Scheduled posts (ensure no duplicates)
- Business rules (one post per day/week/etc)

See [examples/advanced/idempotency-keys.ts](./examples/advanced/idempotency-keys.ts) for more examples.

### Request Cancellation

Cancel in-flight requests using AbortController (perfect for React/Vue component cleanup):

```typescript
// Basic cancellation
const controller = new AbortController();

const promise = flowyr.posts.list(
  { limit: 50 },
  { signal: controller.signal }
);

// Cancel after 5 seconds
setTimeout(() => controller.abort(), 5000);

try {
  const result = await promise;
} catch (error) {
  if (error instanceof Error && error.name === 'AbortError') {
    console.log('Request canceled');
  }
}

// React component cleanup
useEffect(() => {
  const controller = new AbortController();

  flowyr.posts.list({}, { signal: controller.signal })
    .then(result => {
      if (!result.error) setPosts(result.data.posts);
    });

  // Cancel when component unmounts
  return () => controller.abort();
}, []);

// Cancel multiple requests together
const sharedController = new AbortController();

const requests = [
  flowyr.posts.list({ limit: 10 }, { signal: sharedController.signal }),
  flowyr.connections.list({ signal: sharedController.signal }),
  flowyr.scheduler.listTasks({ limit: 10 }, { signal: sharedController.signal }),
];

// Cancel all dashboard requests
sharedController.abort();
```

**Common use cases:**
- Component unmounting in React/Vue
- Search debouncing (cancel previous search)
- Form submission cancellation
- Custom timeouts
- Pagination navigation

See [examples/advanced/request-cancellation.ts](./examples/advanced/request-cancellation.ts) for more examples.

### Testing with MockFlowyr

Test your application without making real API calls using the built-in mock client:

```typescript
import { MockFlowyr } from '@flowyr/sdk';

// Create mock client
const mockClient = new MockFlowyr();

// Configure mock responses
mockClient.posts.create.mockResolvedValue({
  data: { jobId: 'test-123', message: 'Success' },
  error: null,
});

// Use in tests
const result = await mockClient.posts.create({
  connectionIds: ['con_123'],
  text: 'Test post',
});

// Verify calls
expect(mockClient.posts.create.calls.length).toBe(1);
expect(mockClient.posts.create.calls[0][0].text).toBe('Test post');

// Test error handling
mockClient.posts.create.mockResolvedValue({
  data: null,
  error: {
    message: 'Rate limit exceeded',
    statusCode: 429,
    name: 'rate_limit_error',
  },
});

// Custom implementation for validation logic
mockClient.posts.create.mockImplementation(async (options) => {
  if (!options.text) {
    return {
      data: null,
      error: {
        message: 'Text is required',
        statusCode: 400,
        name: 'validation_error',
      },
    };
  }
  return { data: { jobId: 'job-123', message: 'Success' }, error: null };
});

// Reset between tests
afterEach(() => {
  mockClient.resetAll();
});
```

**Mock features:**
- Jest-like API (`mockResolvedValue`, `mockRejectedValue`, `mockImplementation`)
- Call tracking for assertions
- Works with any test framework (Jest, Vitest, Mocha, etc.)
- No external dependencies
- Type-safe mocking
- All SDK methods available

See [examples/testing/mock-client-usage.ts](./examples/testing/mock-client-usage.ts) for complete examples.

### Managing Webhooks

```typescript
// Create webhook
const { data: webhook } = await flowyr.webhooks.create({
  url: 'https://myapp.com/webhooks/flowyr',
  events: ['post.success', 'post.failed', 'task.completed'],
  description: 'Production webhook',
});

console.log('Webhook secret:', webhook.secret);

// List webhooks
const { data: webhooks } = await flowyr.webhooks.list();

// Test webhook
await flowyr.webhooks.ping('wh_123');

// Update webhook
await flowyr.webhooks.update('wh_123', {
  events: ['post.success', 'task.completed'],
});
```

### Error Handling

```typescript
const { data, error, meta } = await flowyr.posts.create({
  connectionIds: ['invalid_id'],
  text: 'Test post',
});

if (error) {
  // Log request ID for support
  console.error(`Request ID: ${meta?.requestId}`);

  switch (error.name) {
    case 'api_error':
      console.error(`API error (${error.statusCode}): ${error.message}`);
      break;
    case 'rate_limit_error':
      console.error(`Rate limited. Retried ${meta?.retryCount} times`);
      break;
    case 'network_error':
      console.error('Network error:', error.message);
      break;
    case 'timeout_error':
      console.error('Request timed out');
      break;
  }
} else {
  console.log('Success:', data);
  console.log(`Rate limit remaining: ${meta?.rateLimitRemaining}`);
}
```

## Documentation

### API Reference

- **[Posts API](./docs/posts.md)** - Create and manage social media posts
- **[Connections API](./docs/connections.md)** - Manage social media platform connections
- **[Scheduler API](./docs/scheduler.md)** - Schedule and manage tasks
- **[Webhooks API](./docs/webhooks.md)** - Configure webhook notifications
- **[Variables API](./docs/variables.md)** - Manage workspace variables for dynamic content
- **[Jobs API](./docs/jobs.md)** - Track post job status and execution
- **[Advanced Features](./docs/advanced-features.md)** - Retry logic, rate limiting, debugging, webhook verification
- **[Error Handling](./docs/error-handling.md)** - Error handling patterns and types

### Resources

- `flowyr.posts` - Create and manage social media posts
- `flowyr.connections` - Manage social media connections
- `flowyr.scheduler` - Schedule and manage tasks
- `flowyr.webhooks` - Configure webhook notifications
- `flowyr.variables` - Manage workspace variables
- `flowyr.jobs` - Track job status and progress
- `flowyr.media` - Upload and manage media files

## TypeScript Support

The SDK is written in TypeScript and provides complete type definitions for all methods and responses:

```typescript
import type {
  CreatePostOptions,
  Connection,
  Task,
  Webhook
} from '@flowyr/sdk';
```

## Environment Variables

The SDK automatically detects the following environment variables:

- `FLOWYR_API_KEY` - Your Flowyr API key

## Development

```bash
# Install dependencies
npm install

# Build the SDK
npm run build

# Run tests
npm test

# Lint code
npm run lint

# Type check
npm run typecheck
```

## Requirements

- Node.js >= 18.0.0
- TypeScript >= 5.0 (for TypeScript projects)

## License

MIT

## Support

- [Documentation](https://docs.flowyr.com)
- [GitHub Issues](https://github.com/flowyr/flowyr-monorepo/issues)
- [Discord Community](https://discord.gg/flowyr)

## Contributing

We welcome contributions! Please see our [Contributing Guide](../CONTRIBUTING.md) for details.