npm package: @lytics/slack-client
Description: TypeScript client for posting rich Slack messages with Block Kit formatting, retry logic, and fluent API
Latest version: 1.1.2

# @lytics/slack-client

TypeScript client library for posting messages to Slack webhooks. Provides type-safe methods with rich Block Kit formatting, fluent API, retry logic, and dry-run support.

## Features

- 🔐 **Type-safe API**: Full TypeScript support with Slack Block Kit types
- 🎨 **Rich formatting**: Slack Block Kit message builder with fluent API
- ✨ **Fluent interface**: Chainable methods for building complex messages
- 🔄 **Retry logic**: Automatic retries with exponential backoff
- 🛡️ **Error handling**: Custom error types for different failure scenarios
- 🧪 **Dry-run mode**: Test messages without actually posting
- ⚡ **Zero dependencies**: Uses Node.js native `fetch` API

## Installation

```bash
npm install @lytics/slack-client
# or
pnpm add @lytics/slack-client
# or
yarn add @lytics/slack-client
```

## Usage

### Basic Example

```typescript
import { SlackClient } from '@lytics/slack-client';

const client = new SlackClient({
  webhook_url: process.env.SLACK_WEBHOOK_URL!,
});

// Post a PR summary
await client.postPRSummary(categorizedPRs, 10);
```

### PR Summary with Categories

```typescript
import { SlackClient, type CategorizedPRs } from '@lytics/slack-client';

const categorizedPRs: CategorizedPRs = {
  seeking_reviews: [
    {
      number: 123,
      title: 'Add new feature',
      url: 'https://github.com/org/repo/pull/123',
      author: 'user1',
      approvals: 0,
      days_open: 2,
      repo: 'org/repo',
    },
  ],
  ready_to_merge: [
    {
      number: 124,
      title: 'Fix bug',
      url: 'https://github.com/org/repo/pull/124',
      author: 'user2',
      approvals: 2,
      days_open: 1,
      repo: 'org/repo',
    },
  ],
  stale: [],
};

const client = new SlackClient({
  webhook_url: process.env.SLACK_WEBHOOK_URL!,
});

await client.postPRSummary(categorizedPRs, 2);
```

### Custom Messages with Fluent API

The `MessageBuilder` provides a fluent API for building custom Slack messages:

```typescript
import { SlackClient, MessageBuilder } from '@lytics/slack-client';

const client = new SlackClient({
  webhook_url: process.env.SLACK_WEBHOOK_URL!,
});

// Build a custom message using fluent API
const message = new MessageBuilder()
  .setHeader('🚀 Deployment Complete')
  .setSummary('Deployed to *production* at 12:00 PM')
  .addSection('✅ Services Updated', '3 services deployed successfully', [
    '*api-gateway*: v1.2.2 → v1.2.3',
    '*auth-service*: v2.1.0 → v2.1.1',
    '*database*: applied 5 migrations',
  ])
  .setFooter('_Deployed by @engineer • <https://ci.com/build/456|View Build>_')
  .build();

await client.postMessage(message);
```

### Advanced: Custom Messages with Block Kit

For full control, you can also build messages manually using Block Kit:

```typescript
import { SlackClient, type SlackMessage } from '@lytics/slack-client';

const client = new SlackClient({
  webhook_url: process.env.SLACK_WEBHOOK_URL!,
});

const message: SlackMessage = {
  text: 'Custom notification',
  blocks: [
    {
      type: 'header',
      text: {
        type: 'plain_text',
        text: '🚀 Deployment Complete',
        emoji: true,
      },
    },
    {
      type: 'section',
      text: {
        type: 'mrkdwn',
        text: 'The deployment to *production* was successful!',
      },
    },
  ],
};

await client.postMessage(message);
```

### Dry-Run Mode

```typescript
const client = new SlackClient(
  {
    webhook_url: process.env.SLACK_WEBHOOK_URL!,
  },
  true // Enable dry-run mode
);

// This will log the message instead of posting
await client.postPRSummary(categorizedPRs, 10);
// Output: [DRY RUN] Would post to Slack: {...}

// Toggle dry-run mode
client.setDryRun(false); // Disable dry-run
client.setDryRun(true);  // Enable dry-run
```

### Error Handling

```typescript
import {
  SlackClient,
  SlackWebhookError,
  NetworkError,
  ConfigurationError,
} from '@lytics/slack-client';

try {
  const client = new SlackClient({
    webhook_url: process.env.SLACK_WEBHOOK_URL!,
  });
  
  await client.postPRSummary(categorizedPRs, 10);
} catch (error) {
  if (error instanceof SlackWebhookError) {
    console.error(`Slack API error: ${error.status} - ${error.response}`);
  } else if (error instanceof NetworkError) {
    console.error('Network failure:', error.message);
  } else if (error instanceof ConfigurationError) {
    console.error('Invalid configuration:', error.message);
  }
}
```

### Custom Retry Configuration

```typescript
const client = new SlackClient({
  webhook_url: process.env.SLACK_WEBHOOK_URL!,
});

// Configure retry attempts and initial delay
client.setRetryConfig(5, 2000); // 5 attempts, 2000ms initial delay

// Retries will use exponential backoff: 2s, 4s, 8s, 16s, 32s
await client.postMessage({ text: 'Important message' });
```

## API Reference

### `SlackClient`

#### Constructor

```typescript
constructor(config: SlackClientConfig, dryRun?: boolean)
```

**Parameters:**
- `config.webhook_url` (string, required): Slack webhook URL
- `config.default_channel` (string, optional): Default channel for messages
- `dryRun` (boolean, optional): Enable dry-run mode (default: false)

#### Methods

##### `postPRSummary(categorizedPRs, totalCount, channel?)`

Post a formatted PR summary with three-tier categorization.

```typescript
async postPRSummary(
  categorizedPRs: CategorizedPRs,
  totalCount: number,
  channel?: string
): Promise<void>
```

**Parameters:**
- `categorizedPRs`: PRs organized by category (seeking reviews, ready to merge, stale)
- `totalCount`: Total number of open PRs
- `channel`: Optional channel override

**Throws:** `SlackWebhookError`, `NetworkError`

##### `postMessage(message)`

Post a custom Slack message with blocks.

```typescript
async postMessage(message: SlackMessage): Promise<void>
```

**Parameters:**
- `message`: Slack message with text and/or blocks

**Throws:** `SlackWebhookError`, `NetworkError`

##### `setDryRun(enabled)`

Enable or disable dry-run mode.

```typescript
setDryRun(enabled: boolean): void
```

##### `isDryRun()`

Check if dry-run mode is enabled.

```typescript
isDryRun(): boolean
```

##### `setRetryConfig(attempts, initialDelay)`

Configure retry behavior.

```typescript
setRetryConfig(attempts: number, initialDelay: number): void
```

**Parameters:**
- `attempts`: Number of retry attempts (default: 3)
- `initialDelay`: Initial delay in milliseconds (default: 1000)

### `MessageBuilder`

Utility class for building Slack messages with fluent API or static helpers.

#### Fluent API Methods (Instance Methods)

##### `setHeader(text, emoji?)`

Set the header block.

```typescript
setHeader(text: string, emoji?: boolean): this
```

**Parameters:**
- `text`: Header text (plain text)
- `emoji`: Enable emoji rendering (default: true)

##### `setSummary(text)`

Set the summary context block (appears below header). Automatically adds a divider.

```typescript
setSummary(text: string): this
```

**Parameters:**
- `text`: Summary text (supports markdown)

##### `addSection(title, description, items)`

Add a section with title, description, and items. Sections with empty items are automatically skipped. Automatically adds a divider.

```typescript
addSection(title: string, description: string, items: string[]): this
```

**Parameters:**
- `title`: Section title
- `description`: Section description
- `items`: Array of item strings (supports markdown)

##### `setFooter(text)`

Set the footer context block.

```typescript
setFooter(text: string): this
```

**Parameters:**
- `text`: Footer text (supports markdown)

##### `setFallbackText(text)`

Set custom fallback text for notifications.

```typescript
setFallbackText(text: string): this
```

**Parameters:**
- `text`: Fallback text (plain text)

##### `build()`

Build the final Slack message.

```typescript
build(): SlackMessage
```

**Returns:** Formatted Slack message ready to send

#### Static Helper Methods

##### `buildPRSummary(categorizedPRs, totalCount, categoryFilter?)`

Build a complete PR summary message with Block Kit formatting.

```typescript
static buildPRSummary(
  categorizedPRs: CategorizedPRs,
  totalCount: number,
  categoryFilter?: CategoryFilter
): SlackMessage
```

##### `buildPlainTextSummary(categorizedPRs, totalCount, categoryFilter?)`

Build a plain text version of the PR summary (for logging/dry-run).

```typescript
static buildPlainTextSummary(
  categorizedPRs: CategorizedPRs,
  totalCount: number,
  categoryFilter?: CategoryFilter
): string
```

## Types

### `CategorizedPRs`

```typescript
interface CategorizedPRs {
  seeking_reviews: PRSummary[];
  ready_to_merge: PRSummary[];
  stale: PRSummary[];
}
```

### `PRSummary`

```typescript
interface PRSummary {
  number: number;
  title: string;
  url: string;
  author: string;
  approvals: number;
  days_open: number;
  repo: string;
}
```

### `SlackMessage`

```typescript
interface SlackMessage {
  text?: string;
  blocks?: SlackBlock[];
  channel?: string;
}
```

## Error Types

All errors extend `SlackClientError`:

- **`ConfigurationError`**: Invalid configuration (missing or invalid webhook URL)
- **`SlackWebhookError`**: Slack API errors (includes status code and response)
- **`NetworkError`**: Network failures (connection issues, timeouts)

## Retry Behavior

The client automatically retries failed requests with exponential backoff:

- **Default**: 3 attempts with 1000ms initial delay
- **Backoff**: Delay doubles each retry (1s → 2s → 4s)
- **Configurable**: Use `setRetryConfig()` to customize

## Testing

Run tests:

```bash
pnpm test
```

Run tests in watch mode:

```bash
pnpm test:watch
```

## Development

Build the package:

```bash
pnpm build
```

Type check:

```bash
pnpm typecheck
```

Lint:

```bash
pnpm lint
```

## License

MIT