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

@gridlock/channel-contract

v0.3.1

Published

Engine-generic channel-adapter contract. Type-only definitions for narrative channels: NPCChannel, PlayerAction, NarrativeEvent, ChannelAdapter, BroadcastAdapter, ChannelRepository. Game systems implement these interfaces over their chosen transport (What

Readme

@gridlock/channel-contract

Engine-generic, type-only contract for narrative channels. Game systems on the gridlock engine implement these interfaces over their chosen transport (WhatsApp, Discord, Telegram, iMessage, Slack, …); the dispatcher routes events through any conforming ChannelAdapter without knowing the transport implementation.

Lifted from @tic/channel-manager/src/types.ts under MS-33 #1608. The source package is now a re-export shim — TIC consumers see no API change.

What's in here

| Category | Exports | |---|---| | Tier + transport | ChannelTier, ChannelTransport, ChannelOriginTransport | | Provisioned-channel side | NPCChannel, NPCChannelStatus | | Player side | PlayerChannelProfile | | Outbound | NarrativeEvent, NarrativeContent | | Inbound | PlayerAction, PlayerActionContent, ChannelOrigin | | Adapters | ChannelAdapter, BroadcastAdapter, DeliveryResult, DeliveryMode | | Persistence | ChannelRepository, CaseChannel, CaseChannelRepository |

Zero runtime logic. Every export is a type, an interface, or a string-literal union.

What's not in here (and why)

  • Transport adapters (WhatsAppAdapter, DiscordAdapter, …) — these are product/transport-specific implementations. Gridlock owns the shape; the implementation lives where the deps live (the WhatsApp adapter pulls in Twilio, the Discord adapter pulls in discord.js, etc.).
  • Dispatcher — ChannelDispatcher stays in @tic/channel-manager for now. The dispatcher's routing rules contain TIC-specific assumptions (channel-manager observability projection, retry policy defaults) that haven't been audited yet.
  • Narrative event kinds — the typed payload shapes for dialogue.response, sage.analysis, case.broadcast, etc. live in @tic/channel-manager/src/narrativeEventKinds.ts because they encode product-specific concepts (SAGE, accusation, case files). The generic NarrativeEvent envelope lives here; the specific payloads are product-side.

Residual TIC-flavour (back-compat)

Three identifiers kept for back-compat:

  • NarrativeEvent.caseId — engine-generic concept ("scenario id"), name is a TIC holdover.
  • CaseChannel interface — engine-generic broadcast target; name is a TIC holdover.
  • CaseChannelRepository interface — same.

A future PR can introduce scenarioId / ScenarioChannel aliases and deprecate the TIC names. Renaming today would force a runtime translation layer that buys nothing for the engine extraction effort.

Usage from a third-party adapter

import {
  ChannelAdapter,
  ChannelTransport,
  DeliveryResult,
  NarrativeEvent,
  NPCChannel,
  PlayerChannelProfile,
} from '@gridlock/channel-contract';

export class TelegramAdapter implements ChannelAdapter {
  transport: ChannelTransport = 'discord' as ChannelTransport;
  // ...

  async send(
    event: NarrativeEvent,
    recipient: PlayerChannelProfile,
    source: NPCChannel,
  ): Promise<DeliveryResult> {
    // Talk to the Telegram Bot API, return delivery result.
    return { status: 'delivered', providerMessageId: 'tg-1', attempts: 1 };
  }
}

(Note: ChannelTransport is a fixed 'whatsapp' | 'discord' union today. A new entry for 'telegram' lands as part of the reference Telegram adapter PR, #1609.)

Acceptance suite (@gridlock/channel-contract/acceptance, #1610)

Adapter authors can run a ready-made acceptance suite against their implementation to prove it conforms to the contract:

import { runAdapterAcceptanceSuite } from '@gridlock/channel-contract/acceptance';
import { TelegramAdapter } from './TelegramAdapter';

runAdapterAcceptanceSuite({
  name: 'TelegramAdapter',
  createAdapter: () => new TelegramAdapter({ token: 'fake' }),
  successFixture: { event, recipient, source },          // happy path
  failureFixtures: [                                      // optional negatives
    { scenario: 'rate-limit', event, recipient, source, expectedErrorCode: 'RATE_LIMIT' },
    { scenario: 'unknown-recipient', event, recipient, source },
  ],
  inbound: {                                              // optional inbound parser
    rawPayload: { ... },
    parse: (raw) => parseInbound(raw),
    expected: { playerId: 'p1', targetNpcId: 'alice', contentKind: 'text' },
  },
});

Two entry points:

  • runAcceptanceChecks(config) returns Array<{ name, ok, error? }> for programmatic inspection.
  • runAdapterAcceptanceSuite(config) registers one node:test per check for nice CI output.

The suite covers: outbound delivery shape (status, providerMessageId, error, attempts), the no-throw-on-success invariant, failure-fixture scenarios with optional error-code pinning, and an optional inbound parser check. It calls only contract methods — the same suite works for any transport.

@tic/channel-manager wires the suite into tests/AdapterAcceptance.test.ts against WhatsAppAdapterStub + DiscordAdapterStub.

Tests

  • 19 type-shape conformance tests in tests/contract.test.ts — each constructs a literal of the contract type so a rename/remove fails to compile.
  • 12 acceptance-meta tests in tests/acceptance.test.ts — drive the suite against working + deliberately broken stubs to verify clear diagnostics ("broken adapter fails clearly" — #1610 acceptance).
pnpm --filter @gridlock/channel-contract test       # 31 / 31 pass
pnpm --filter @gridlock/channel-contract typecheck  # tsc --noEmit