npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@teever/ez-hook-effect

v0.5.1

Published

A Discord webhook library built with Effect - type-safe, composable, and resilient

Readme

@teever/ez-hook-effect

A Discord webhook library built with Effect - type-safe, composable, and resilient.

Features

  • Type-safe - Runtime validation with Effect schemas
  • Composable - Functional programming patterns with Effect
  • Error Handling - Comprehensive error tracking with structured validation issues
  • Retry Logic - Built-in exponential backoff with jitter
  • Dependency Injection - Layer-based architecture
  • Validated - All Discord webhook constraints enforced
  • One Dependency - Only requires Effect
  • Environment Support - Configure via environment variables
  • CRUD Operations - Send, get, modify, delete, and validate webhooks

Installation

bunx jsr add @teever/ez-hook-effect
# or
npx jsr add @teever/ez-hook-effect

Quick Start

import { Effect, pipe } from 'effect'
import { makeDefaultLayer, sendWebhook, Webhook } from '@teever/ez-hook-effect'

// Configure your webhook
const WEBHOOK_URL = 'https://discord.com/api/webhooks/YOUR_ID/YOUR_TOKEN'

// Create service layer
const AppLayer = makeDefaultLayer(WEBHOOK_URL)

// Send a simple message
const program = pipe(
  Webhook.make,
  Webhook.setContent('Hello from Effect!'),
  Webhook.setUsername('My Bot'),
  Webhook.build,
  Effect.flatMap(sendWebhook)
)

// Run the program
Effect.runPromise(program.pipe(Effect.provide(AppLayer)))

Pipe-First API (Recommended)

Prefer composing with pipe for ergonomic, Effect-style data assembly. This library uses a single, pipe-first API (no OO builders).

import { Effect, pipe } from 'effect'
import { Webhook, Embed, sendWebhook } from '@teever/ez-hook-effect'

const program = pipe(
  Embed.make,
  Embed.setTitle('System Status'),
  Embed.setDescription('All systems operational'),
  Embed.setColor(0x00ff00),
  Embed.addField('CPU', '45%', true),
  Embed.setTimestamp(),
  Embed.build,
  Effect.flatMap((embed) =>
    pipe(
      Webhook.make,
      Webhook.setContent('📊 Status Update'),
      Webhook.setUsername('Ez-Hook Bot'),
      Webhook.addEmbed(embed),
      Webhook.build
    )
  ),
  Effect.flatMap(sendWebhook)
)

Building Rich Embeds

const embedProgram = pipe(
  Embed.make,
  Embed.setTitle('Server Status'),
  Embed.setDescription('All systems operational'),
  Embed.setColor(0x00FF00),
  Embed.addField('CPU', '45%', true),
  Embed.addField('Memory', '2.1GB', true),
  Embed.addField('Uptime', '15 days', true),
  Embed.build,
  Effect.flatMap((embed) =>
    pipe(
      Webhook.make,
      Webhook.setContent('Daily Report'),
      Webhook.addEmbed(embed),
      Webhook.build
    )
  ),
  Effect.flatMap(sendWebhook)
)

Error Handling

Retries for rate limits (429) and transient errors (5xx) are handled automatically using configurable exponential backoff. sendWebhook fails the Effect on any non-204 response, so errors always surface to your error handling paths.

import { Effect, pipe } from 'effect'
import { Webhook, sendWebhook, ValidationError, HttpError, NetworkError, RateLimitError, WebhookError } from '@teever/ez-hook-effect'

const program = pipe(
  Webhook.make,
  Webhook.setContent('Hello!'),
  Webhook.build,
  Effect.flatMap(sendWebhook),
  Effect.catchTag('ValidationError', (error) =>
    Effect.logError(`Validation failed: ${error.message}`)
  ),
  Effect.catchTag('RateLimitError', (error) =>
    Effect.logError(`Rate limited: retry after ${error.retryAfter}ms`)
  ),
  Effect.catchTag('HttpError', (error) =>
    Effect.logError(`Request failed: ${error.message}`)
  ),
  Effect.catchTag('NetworkError', (error) =>
    Effect.logError(`Network error: ${error.message}`)
  ),
  Effect.catchTag('WebhookError', (error) =>
    Effect.logError(`Webhook error: ${error.message}`)
  )
)

Structured Validation Errors

Validation errors include detailed issue information:

Effect.catchTag('ValidationError', (error) =>
  Effect.logError(`Validation failed:\n${error.format()}`)
)

Error Helpers

import { Effect, pipe } from 'effect'
import { Webhook, sendWebhook, formatWebhookError, webhookErrorToLogObject } from '@teever/ez-hook-effect'

const program = pipe(
  Webhook.make,
  Webhook.setContent('Hello!'),
  Webhook.build,
  Effect.flatMap(sendWebhook),
  Effect.catchAll((error) =>
    Effect.logError(formatWebhookError(error)).pipe(
      Effect.tap(() => Effect.log(JSON.stringify(webhookErrorToLogObject(error))))
    )
  )
)

Raw Response

When you need status/body, use sendWebhookRaw:

import { Effect, pipe } from 'effect'
import { sendWebhookRaw, Webhook } from '@teever/ez-hook-effect'

const program = pipe(
  Webhook.make,
  Webhook.setContent('Hello!'),
  Webhook.build,
  Effect.flatMap(sendWebhookRaw),
  Effect.tap((res) => Effect.log(`Status: ${res.status}`))
)

Validate Only

import { Effect, pipe } from 'effect'
import { Webhook } from '@teever/ez-hook-effect'

const program = pipe(
  Webhook.validate({ content: 'Hello!' }),
  Effect.tap(() => Effect.log('Valid payload'))
)

Configuration Options

Programmatic Configuration

const AppLayer = WebhookServiceLive.pipe(
  Layer.provide(makeConfigLayer(WEBHOOK_URL, {
    maxRetries: 5,        // Maximum retry attempts
    baseDelayMs: 1000,    // Base delay for exponential backoff
    maxDelayMs: 60000,    // Maximum delay between retries
    enableJitter: true    // Add randomness to retry delays
  })),
  Layer.provide(HttpClientLive)
)

One-Liner Configuration

import { makeDefaultLayer } from '@teever/ez-hook-effect'

const AppLayer = makeDefaultLayer(WEBHOOK_URL, {
  maxRetries: 5,
  baseDelayMs: 1000,
})

Environment Variables

import { ConfigFromEnv, HttpClientLive, WebhookServiceLive } from '@teever/ez-hook-effect'

// Reads from DISCORD_WEBHOOK_URL, WEBHOOK_MAX_RETRIES, etc.
const AppLayer = WebhookServiceLive.pipe(
  Layer.provide(ConfigFromEnv),
  Layer.provide(HttpClientLive)
)

# Available environment variables:
# DISCORD_WEBHOOK_URL     - Webhook URL (required)
# WEBHOOK_MAX_RETRIES     - Maximum retry attempts (default: 3)
# WEBHOOK_BASE_DELAY_MS   - Base delay in ms (default: 1000)
# WEBHOOK_MAX_DELAY_MS    - Maximum delay in ms (default: 60000)
# WEBHOOK_ENABLE_JITTER   - Enable jitter (default: true)

Parse Webhook URL

Extract webhook ID and token from a URL:

import { parseWebhookUrl } from '@teever/ez-hook-effect'

const result = await Effect.runPromise(
  parseWebhookUrl('https://discord.com/api/webhooks/123456789/token')
)
// result: { id: '123456789', token: 'token' }

Webhook Operations

Beyond sending messages, the library supports full webhook CRUD:

import { Effect, Layer, pipe } from 'effect'
import { getWebhook, modifyWebhook, deleteWebhook, validateWebhook, WebhookService, WebhookServiceLive, makeConfigLayer, HttpClientLive } from '@teever/ez-hook-effect'

const AppLayer = WebhookServiceLive.pipe(
  Layer.provide(makeConfigLayer(WEBHOOK_URL)),
  Layer.provide(HttpClientLive)
)

const program = Effect.gen(function* () {
  const service = yield* WebhookService

  // Check if webhook is valid and accessible
  const isValid = yield* service.validateWebhook()

  // Get webhook information
  const info = yield* service.getWebhook()

  // Modify webhook settings
  const modified = yield* service.modifyWebhook({
    name: 'New Name',
  })

  // Delete the webhook
  const deleted = yield* service.deleteWebhook()
})

Effect.runPromise(Effect.provide(program, AppLayer))

Testing Utilities

import { Effect, Layer, pipe } from 'effect'
import { makeTestHttpClient, WebhookServiceLive, makeConfigLayer } from '@teever/ez-hook-effect'

const TestHttpClient = makeTestHttpClient((_req) =>
  Effect.succeed({
    status: 204,
    statusText: 'No Content',
    headers: {},
    body: null,
    text: '',
  })
)

const AppLayer = WebhookServiceLive.pipe(
  Layer.provide(makeConfigLayer('https://discord.com/api/webhooks/123/abc')),
  Layer.provide(TestHttpClient)
)

Development

bun run lint          # Biome lint + format
bun run typecheck     # TypeScript type checking

Testing

The library includes comprehensive tests for all features:

bun test              # Run all tests
bun test:watch        # Watch mode
bun test:coverage     # Coverage report

Building

bun run build         # Build the library
bun run build:standalone  # Create standalone executable

Examples

Check out the examples/ directory:

  • 01-basic-webhook.ts - Send a simple message
  • 02-rich-embeds.ts - Build embeds with fields, colors, metadata
  • 03-error-handling.ts - Validation errors and recovery patterns
  • 04-multiple-embeds.ts - Add several embeds to one webhook
  • 05-service-layer.ts - Dependency injection with layers and CRUD operations

License

MIT