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

@slackwire/core

v0.1.2

Published

Slack client, template engine, and render pipeline

Readme

@slackwire/core

The engine behind slackwire: the render pipeline, the typed template engine, and the Slack client. It loads JSON templates from a catalog, validates a payload against the template schema, interpolates typed placeholders into Block Kit with per-kind escaping, enforces Slack's hard limits, and delivers the result through a thin SlackClient wrapper over @slack/web-api. Pure render functions do no I/O; Slack calls, the name resolver, and config all live in clearly separated modules.

Version 0.1.0. Requires Node.js 20 or newer.

Install

pnpm add @slackwire/core

Minimal usage

Render a template, then post the result.

import { render, SlackClient, loadConfig } from '@slackwire/core';
import { readFileSync, readdirSync } from 'node:fs';

const fsAdapter = {
  readFile: (p: string) => readFileSync(p, 'utf8'),
  listDirs: (p: string) =>
    readdirSync(p, { withFileTypes: true })
      .filter(d => d.isDirectory())
      .map(d => d.name),
};

// every key in the template's schema.json is required (see "Limits and gotchas")
const result = render(
  { catalogPath: './templates', name: 'ci-cd', version: '1.0.0' },
  {
    title: 'CI passed: healthcart-v2 #2451',
    ref: 'feature/checkout-fix',
    short_sha: 'a1b9f2c',
    description: 'Fix Stitch amount overflow',
    author: 'Naledi',
    icon_url: 'https://placehold.co/72x72/2eb67d/ffffff/png?text=PASS',
    icon_alt: 'passed',
    steps_text: 'Install -> Lint -> Test -> Build -> Deploy',
    progress_bar: '5 of 5 - deployed to staging',
    runner: 'ci-3',
    test_count: '142',
    coverage: '84.2%',
    finished_at: { epoch: 1750000000, format: '{time}', fallback: 'now' },
    primary_label: 'Open staging',
    primary_url: 'https://staging.example.com/healthcart-v2',
    logs_url: 'https://ci.example.com/healthcart-v2/2451/logs',
  },
  { fs: fsAdapter },
);

const cfg = loadConfig();                 // reads SLACK_TOKEN / _BASE64 / _FILE
const client = new SlackClient(cfg.token);
const { ts, channel } = await client.post({
  channel: 'C0XXXXXXXXX',
  blocks: result.blocks,
  text: result.text,
});

You can also skip templates and post raw blocks directly with client.post({ channel, blocks, text }).

Public API surface

Everything below is re-exported from the package root (packages/core/src/index.ts).

render(templateRef, payload, options): RenderResult

Loads the template bundle, validates the payload against the schema, interpolates typed placeholders, assembles partials, derives fallback text, and checks Slack limits.

interface TemplateRef { catalogPath: string; name: string; version: string; }

interface RenderOptions {
  fs: FsAdapter;                          // { readFile, listDirs }
  dryRun?: boolean;
  partials?: Record<string, object[]>;
  themeToken?: string;
  attribution?: boolean;                  // default false
}

interface RenderResult { blocks: object[]; attachments: object[]; text: string; }

Placeholder kinds (PlaceholderKind)

Declared per field in schema.json. Skeleton tokens use {{kind:key}}. Each kind is escaped for its position, which is why < > & and values like Name <email> render safely.

| Kind | Behavior | |---|---| | text_plain | Plain text. | | text_mrkdwn | Slack mrkdwn, special characters escaped. | | link_url | URL for a link. | | link_text | Display text for a link, \| and > escaped. | | date | Object { epoch, format, fallback }, rendered to a <!date^...> token. | | user_mention | Wrapped as <@id>. | | channel_mention | Wrapped as <#id>. | | code | Inline code. | | code_block | Triple-backtick code block, passed through literally. | | color | Hex color used for the accent attachment. | | image_url | Image URL. | | button | Button value. |

SlackClient

Thin wrapper around @slack/web-api. Every SDK error is mapped to a typed error (see below), and the token is redacted from error messages.

new SlackClient(token)
SlackClient.withProxy(token, proxyUrl)

client.post(args: PostArgs): Promise<PostResult>           // { channel, ts }
client.update(args: UpdateArgs): Promise<PostResult>
client.delete(channel, ts): Promise<void>
client.schedule(args): Promise<{ scheduledMessageId: string }>
client.react(channel, ts, name): Promise<void>
client.uploadV2(args): Promise<void>                       // @slack/web-api FilesUploadV2Arguments
client.search(query, options?): Promise<SlackMessage[]>
client.history(args: HistoryArgs): Promise<SlackMessage[]> // surfaces blocks + attachments + metadata

post and update accept optional metadata ({ event_type, event_payload }), which is how idempotent re-finds tag messages.

loadConfig(): SlackConfig

Resolves the token in order SLACK_TOKEN -> SLACK_TOKEN_BASE64 (base64-decoded) -> SLACK_TOKEN_FILE (trimmed file contents), throwing ConfigError if none resolve. Reports tokenType ('user' for xoxp-, otherwise 'bot') and attribution (true only when SLACK_ATTRIBUTION=true).

Resolver

Resolves channel and user names to Slack IDs by paginating the API and caching the map.

new Resolver(client, cache, teamId)
resolver.resolveChannel(name): Promise<string | undefined>
resolver.resolveUser(name): Promise<string | undefined>

findOrCreate(args): Promise<FindOrCreateResult>

Idempotent post-or-update for CI. Re-finds an existing card by a dedupe key stored in Slack message metadata (conversations.history for bot tokens, search.messages for user tokens), then updates it instead of posting a duplicate.

Validation and fallback helpers

validateStructural(output): void   // block structure; image alt_text; mrkdwn token balance
validateLimits(output): void       // 50 blocks, section 3000, header 150, button 75, ~38000 soft total
deriveFallback(blocks, attachments?): string
escape(value, kind): string        // per-kind escaping primitive

Parity harness

normalize, parityDiff, and discoverParityCases power the byte-parity eval suite. See docs/parity-evals.md.

Error classes

| Class | CLI exit code | Meaning | |---|---|---| | SchemaError | 2 | Payload failed JSON Schema validation. | | StructuralError | 2 | Block structure invalid (includes an unknown $use partial). | | LimitError | 2 | Slack hard limit exceeded. | | SlackApiError | 3 | Slack platform error. | | NetworkError | 4 | HTTP / request-level failure. | | RateLimitError | 5 | Rate limited; carries retryAfter seconds. | | ConfigError | (no CLI mapping) | Missing token at config load time. |

Limits and gotchas

  • 50 blocks per message; section text 3000 chars; header 150; button label 75; image blocks require alt_text. A roughly 38000-char soft total payload limit is checked against the serialized block array. Violations throw LimitError.
  • validateStructural walks only top-level blocks and shallow-walks attachments. It does not validate attachments[].blocks, so house-style colored cards (whose block tree lives in the attachment) are not length- or alt_text-checked by the engine. Respect Slack limits manually for those.
  • The template JSON Schema marks every key required; there is no optional-field support yet.
  • attribution is false by default. Pass attribution: true (or set SLACK_ATTRIBUTION=true) to render the accent attachment, which Slack may footer with "Added by <app>".
  • Tokens are redacted from all error messages; SLACK_TOKEN_BASE64 is decoded at runtime and never logged.