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

@cool-ai/beach-format

v1.1.0

Published

Channel Formatter and Content Renderer primitives for batched-edge delivery in Beach applications.

Readme

@cool-ai/beach-format

Composer / Response Formatter primitive for Beach. Renders authoritative-data + narrative into a channel-shaped artifact for batched channels (email, WhatsApp, SMS, voice, push, PDF).

Home: cool-ai.org · Documentation: cool-ai.org/docs

Install

npm install @cool-ai/beach-format

Concern

Channels without a UI need to compose a standalone message from two inputs:

  1. Authoritative data — tool results that flowed through filter-and-distribute, annotated with composition tiers per field (anchored | validated | narrative).
  2. Narrative — the orchestrating LLM's respond() output, providing framing, tone, and context.

The composition rule is absolute: facts come from data; framing comes from narrative. Dates, statuses, amounts, names, email subjects, flight numbers — all from authoritative data. Connective prose comes from the narrative.

The Composer is where this rule lands at the rendering boundary. Anchored fields are token-substituted (LLM cannot edit them). Validated fields are post-checked against the data; failures fall back to anchored rendering. Narrative fields are LLM-authored end-to-end. The trust mechanism is structural, not prompted.

The parametric Composer<E, A>

import {
  EmailHtmlComposer,
  type EmailEnvelope,
} from '@cool-ai/beach-format';
import { annotateRecord } from '@cool-ai/beach-core';

const composer = new EmailHtmlComposer({
  llmRender: async ({ section, narrative, envelope }) => {
    // your AI SDK call — returns the prose-rendered HTML for the section
  },
});

const flight = annotateRecord(
  { flightNumber: 'BA572', departTime: '11:15', advisoryNote: 'Check in 60 min before' },
  {
    flightNumber: { composition: 'anchored' },
    departTime:   { composition: 'anchored' },
    advisoryNote: { composition: 'narrative' },
  },
);

const result = await composer.compose({
  sections: [{ sectionId: 'flight', sectionTitle: 'Flight', data: [flight] }],
  narrative: 'Two direct flights to Rome.',
  envelope: {
    channelClass: 'email-html',
    from: '[email protected]',
    to: ['[email protected]'],
    inboundSubject: 'Trip to Rome',
  },
});

if (result.status === 'rendered') {
  await emailChannel.send(result.artifact);
}

Composer<E extends ChannelEnvelope, A> is parametric on the channel envelope type. An EmailHtmlComposer extends Composer<EmailEnvelope, EmailHtmlArtifact> cannot read WhatsAppEnvelope fields — the type system rejects it at compile time. Cross-channel-read prohibition is enforced at the type boundary, not in prose.

Seven canonical channel formats

| Subclass | channelClass | Envelope | Artifact | |---|---|---|---| | EmailHtmlComposer | email-html | EmailEnvelope | { from, to, cc?, bcc?, subject, inReplyTo?, references?, html, plainText } | | EmailPlainComposer | email-plain | EmailEnvelope | { from, to, cc?, bcc?, subject, inReplyTo?, references?, body } | | WhatsAppComposer | whatsapp | WhatsAppEnvelope | { from, to, body, quotedMessageId?, interactiveOptions? } | | SmsComposer | sms | SmsEnvelope | { from, to, body } | | PushMobileComposer | push-mobile | PushMobileEnvelope | { to, title, body, deepLink?, category?, interruptionLevel?, badgeCount?, sound?, data? } | | VoiceComposer | voice | VoiceEnvelope | { plainText, ssml? } | | PdfPrintComposer | pdf-print | PdfPrintEnvelope | { html } |

Bespoke channels register via class MyComposer extends Composer<MyEnvelope, MyArtifact>. The seven canonical formats are capped — see architectural-constraints.md.

Three-tier composition annotation

| Tier | Behaviour | |---|---| | anchored | Channel-aware structural rendering, never LLM-generated. Primitives substitute character-for-character; structured objects render via the channel's structural rules (or per-data-part-type registrations). | | validated | LLM-rendered, then post-checked against the data. Validation failure → fall back to anchored rendering for that field; the rest of the message renders normally. | | narrative | LLM-rendered freely; no validation. |

Anchored fields are the trust mechanism — the LLM has no path to alter, paraphrase, round, or convert them. Anchored rendering is always structural.

Five canonical matrix rules

canonicalRules<E>() returns: pure narrative, empty everything, count-zero only, complex data no narrative, multi-lingual mismatch. Consumer-registered rules are evaluated first (in registration order); canonical defaults are evaluated last as fallbacks; the first non-null decision wins.

const composer = new EmailHtmlComposer({
  matrixRules: [
    (sections) => sections.length > 5 ? { mode: 'template' } : null,
  ],
});

Per-data-part-type rendering and validation overrides

Anchored object rendering and validator string-containment checks both support per-(partType, channelClass) overrides:

import { InMemoryAnchoredRendererRegistry, InMemoryValidatorOverrideRegistry } from '@cool-ai/beach-format';

const renderers = new InMemoryAnchoredRendererRegistry();
renderers.register('flight-leg', 'voice', (value) => {
  const v = value as { flightNumber: string; departTime: string };
  return `${v.flightNumber}, departing at ${v.departTime}`;
});

const validators = new InMemoryValidatorOverrideRegistry();
validators.register('place-name', { stringContainment: 'case-insensitive' });

const composer = new VoiceComposer({
  anchoredRenderers: renderers,
  validatorOverrides: validators,
});

Suppression + budget

Empty messages are suppressed by default — { status: 'suppressed', reason: 'no content' } is returned when both sections and narrative are empty. Heartbeat channels (where the user wants confirmation that the system ran successfully) override suppressEmptyMessage: false.

characterBudget triggers the deterministic shrink procedure: within-budget → narrative-truncated → template-fallback → over-budget. Anchored fields are preserved character-for-character; only narrative portions can be cut.

Architectural constraints

packages/format/src/composer/architectural-constraints.md records the invariants this primitive enforces — channel-format awareness scope, the canonical-format cap, the matrix-rule cap, tier-immutability, render-order guarantees, and the type-level cross-channel-read prohibition.

Related