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

@napplet/core

v0.22.0

Published

Shared protocol types, constants, and message definitions for the napplet protocol

Readme

@napplet/core

JSON envelope types and NAP dispatch infrastructure for the napplet ecosystem.

Getting Started

Package Overview

This package is the single source of truth for all protocol-level definitions in the napplet ecosystem. All other @napplet/* packages import their envelope types, dispatch infrastructure, and protocol constants from here.

Zero dependencies. No DOM or browser APIs. Works in any JavaScript runtime.

Installation

npm install @napplet/core

Quick Start

import {
  type NappletMessage, type NapDomain, type NapProtocolId, type ShellSupports,
  type NapHandler, type NapDispatch,
  NAP_DOMAINS, SHELL_BRIDGE_URI, PROTOCOL_VERSION,
  createDispatch, registerNap, dispatch, getRegisteredDomains,
  ALL_CAPABILITIES, TOPICS,
} from '@napplet/core';

API Reference

Envelope Types

The JSON envelope wire format is the primary API introduced in NIP-5D v4. All messages between napplet and shell use a type field as a discriminant in domain.action format.

NappletMessage

Base interface for all messages exchanged between napplet and shell.

interface NappletMessage {
  /** Message type discriminant in "domain.action" format */
  type: string;
}

Concrete message types extend this interface with domain-specific payload fields:

// Example concrete message type:
interface RelaySubscribe extends NappletMessage {
  type: 'relay.subscribe';
  id: string;
  subId: string;
  filters: NostrFilter[];
}

The type field domain prefix (relay, identity, storage, inc, theme, keys, media, notify, config, resource, cvm, outbox, upload, intent, ble, webrtc, link, lists, serial, common) routes messages to the correct NAP handler via dispatch().

NapDomain

String literal union of the NAP capability domains.

type NapDomain = 'relay' | 'identity' | 'storage' | 'inc' | 'theme' | 'keys' | 'media' | 'notify' | 'config' | 'resource' | 'cvm' | 'outbox' | 'upload' | 'intent' | 'ble' | 'webrtc' | 'link' | 'lists' | 'serial' | 'common';

| Domain | Scope | |-----------|------------------------------------------| | relay | Relay proxy (subscribe, publish, query) | | identity | Read-only user identity queries | | storage | Scoped key-value storage proxy | | inc | Inter-napplet communication (dispatch + channel) | | theme | Theme tokens and appearance settings | | keys | Keyboard forwarding and action keybindings| | media | Ownership-aware media session control | | notify | Shell-rendered notifications | | config | Per-napplet declarative configuration (JSON Schema-driven) | | resource | Byte-fetching primitive (URL to Blob) | | cvm | Native ContextVM / MCP-over-Nostr bridge | | outbox | Outbox-aware relay routing | | upload | Shell-mediated file/blob upload | | intent | Archetype intent dispatch | | ble | Runtime-mediated Bluetooth LE/GATT sessions | | webrtc | Runtime-mediated WebRTC data sessions | | link | Shell-mediated external link opening | | lists | Runtime-mediated NIP-51 list mutations | | serial | Runtime-mediated serial device access | | common | Shell-mediated common social actions |

NAP_DOMAINS

Runtime constant array of all NAP domain strings. Useful for iteration and validation.

const NAP_DOMAINS: readonly NapDomain[] = ['relay', 'identity', 'storage', 'inc', 'theme', 'keys', 'media', 'notify', 'config', 'resource', 'cvm', 'outbox', 'upload', 'intent', 'ble', 'webrtc', 'link', 'lists', 'serial', 'common'];

for (const domain of NAP_DOMAINS) {
  console.log(`Checking support for: ${domain}`);
}

ShellSupports

Interface for the shell capability query API.

interface ShellSupports {
  supports(capability: NapDomain | string, protocol?: `NAP-${number}`): boolean;
}

Napplets call window.napplet.shell.supports(domain) to check whether the shell declared support for a NAP domain before using that domain's API. For numbered NAP-NN message protocols, pass the protocol identifier as the optional second argument:

window.napplet.shell.supports('inc', 'NAP-01');

NappletShell — NAP-SHELL (foundational handshake)

Type for the window.napplet.shell namespace. shell is the foundational, mandatory NAP domain — the one capability that cannot be discovered via supports() (and is not a member of NAP_DOMAINS). The shim posts shell.ready (no payload); the runtime replies once with shell.init carrying the environment { capabilities: { domains, protocols }, services }; the shim caches it so supports(domain, protocol?) answers synchronously and locally thereafter (false before init and for any unknown domain/protocol).

interface NappletShell {
  supports(domain: string, protocol?: string): boolean;
  readonly services: readonly string[];
  ready(): Promise<ShellEnvironment>;   // resolves once the environment is delivered
  onReady(handler: (env: ShellEnvironment) => void): Subscription;
}

interface ShellEnvironment {
  capabilities: ShellCapabilities;
  services: string[];
}

interface ShellCapabilities {
  domains: string[];
  protocols: Record<string, string[]>;
}
// Synchronous, local capability queries (no wire round-trip):
window.napplet.shell.supports('relay');        // true if the runtime offers relay
window.napplet.shell.supports('inc', 'NAP-2'); // true if it also speaks NAP-2

// Gate startup on the environment:
const env = await window.napplet.shell.ready();
const sub = window.napplet.shell.onReady((e) => start(e));

The @napplet/nap/shell subpath re-exports these types alongside DOMAIN.


NAP Dispatch Infrastructure

The dispatch system allows NAP modules to self-register at import time. Inbound messages are routed to the correct NAP handler based on the domain prefix extracted from message.type (the part before the first .). The exported helper names (registerNap, NapHandler, NapDispatch) are retained for package compatibility.

createDispatch()

Factory that returns an isolated { registerNap, dispatch, getRegisteredDomains } backed by its own Map<string, NapHandler>. Use for testing or multi-instance scenarios.

function createDispatch(): NapDispatch;
import { createDispatch } from '@napplet/core';

const { registerNap, dispatch } = createDispatch();
registerNap('relay', handleRelayMessage);
dispatch({ type: 'relay.subscribe' }); // true

registerNap(domain, handler)

Register a handler for a NAP domain on the module-level singleton registry. NAP modules call this at import time.

const registerNap: (domain: string, handler: NapHandler) => void;
import { registerNap } from '@napplet/core';

registerNap('relay', (msg) => {
  // handles all relay.* messages
  console.log('relay message:', msg.type);
});

Throws if the domain is already registered.

dispatch(message)

Dispatch a message to the handler matching its domain prefix. Returns true if a handler was found and called.

const dispatch: (message: NappletMessage) => boolean;
import { dispatch } from '@napplet/core';

dispatch({ type: 'relay.subscribe' });  // true (if relay handler registered)
dispatch({ type: 'unknown.action' });   // false
dispatch({ type: 'malformed' });         // false (no dot)

The domain is extracted by splitting message.type on the first .. A type with no . or an empty domain prefix returns false without throwing.

getRegisteredDomains()

Return all currently registered domain strings from the singleton registry.

const getRegisteredDomains: () => string[];
import { getRegisteredDomains } from '@napplet/core';

getRegisteredDomains(); // ['relay', 'identity', 'storage']

NapHandler

Callback type for NAP message handlers.

type NapHandler = (message: NappletMessage) => void;

NapDispatch

Interface returned by createDispatch().

interface NapDispatch {
  registerNap: (domain: string, handler: NapHandler) => void;
  dispatch: (message: NappletMessage) => boolean;
  getRegisteredDomains: () => string[];
}

Protocol Types

Types shared by all napplet packages for Nostr event structures and the capability system.

NostrEvent

Standard Nostr event structure (used by relay NAP and identity NAP).

interface NostrEvent {
  id: string;
  pubkey: string;
  created_at: number;
  kind: number;
  tags: string[][];
  content: string;
  sig: string;
}

NostrFilter

Subscription filter (used by relay NAP for relay.subscribe and relay.query).

interface NostrFilter {
  ids?: string[];
  authors?: string[];
  kinds?: number[];
  since?: number;
  until?: number;
  limit?: number;
  [key: `#${string}`]: string[] | undefined; // tag filters, e.g. '#t', '#e'
}

Capability

String union type listing all 10 protocol capability strings.

type Capability =
  | 'relay:read' | 'relay:write'
  | 'cache:read' | 'cache:write'
  | 'hotkey:forward'
  | 'sign:event' | 'sign:nip04' | 'sign:nip44'
  | 'state:read' | 'state:write';

Shell implementations use bitfield constants (CAP_*) for fast runtime checks. Capability strings are the human-readable protocol-level representation.

ALL_CAPABILITIES

readonly Capability[] containing all 10 capability strings.

for (const cap of ALL_CAPABILITIES) {
  console.log(cap); // 'relay:read', 'relay:write', ...
}

Subscription

Handle returned by relay.subscribe() and inc.on().

interface Subscription {
  close(): void;
}

EventTemplate

Unsigned event template passed to relay.publish().

interface EventTemplate {
  kind: number;
  content: string;
  tags: string[][];
  created_at: number;
}

Protocol Constants

| Constant | Value | Description | |----------|-------|-------------| | PROTOCOL_VERSION | '4.0.0' | Current napplet-shell protocol version (JSON envelope era, NIP-5D v4) | | SHELL_BRIDGE_URI | 'napplet://shell' | URI identifying the shell bridge in relay tags | | REPLAY_WINDOW_SECONDS | 30 | Maximum event age (seconds) for replay protection |


Topic Constants

The TOPICS object contains string constants for INC topic-based routing. These are legacy constants from the pre-envelope era — with JSON envelope messages, topic strings are passed directly in inc.emit and inc.subscribe payloads.

import { TOPICS } from '@napplet/core';

TOPICS.STATE_GET                // 'shell:state-get'
TOPICS.SHELL_CONFIG_GET         // 'shell:config-get'
TOPICS.WM_FOCUSED_WINDOW_CHANGED // 'wm:focused-window-changed'
// ... see source for full list

Note: With JSON envelope wire format (v0.16.0+), state operations use storage.* messages directly rather than INC topic routing. These constants are retained for backward compatibility with shell runtime implementations.


Types

import type {
  NappletMessage, NapDomain, NamespacedCapability, NapProtocolId, ShellSupports,
  NappletShell, ShellEnvironment, ShellCapabilities, ShellReadyMessage, ShellInitMessage,
  NapHandler, NapDispatch,
  NostrEvent, NostrFilter, Capability,
  Subscription, EventTemplate, NappletGlobal,
} from '@napplet/core';

| Type | Description | |------|-------------| | NappletMessage | Base interface for all JSON envelope messages | | NapDomain | Union of the active NAP domain strings | | NamespacedCapability | Union of NapDomain \| nap:* \| perm:* for supports() | | NapProtocolId | Numbered NAP protocol id such as NAP-01 for the optional second supports() argument | | ShellSupports | Interface with supports() capability query method | | NappletShell | NAP-SHELL type for window.napplet.shell (supports, services, ready, onReady) | | ShellEnvironment | The shell.init environment: { capabilities, services } | | ShellCapabilities | The capability set { domains, protocols } answering supports() | | ShellReadyMessage / ShellInitMessage | NAP-SHELL wire message types | | NapHandler | Callback type for domain handlers | | NapDispatch | Interface returned by createDispatch() | | NostrEvent | Nostr event structure | | NostrFilter | Subscription filter for relay NAP | | Capability | Human-readable capability string union | | Subscription | Handle with close() returned by subscribe/on | | EventTemplate | Unsigned event template for publishing |

Boundary Helpers (clone-safety)

NAP shims cross the napplet ⇄ shell boundary by structured-cloning a JSON envelope through postMessage. Framework reactive values — Svelte 5 $state, Vue reactive, Solid stores — are Proxy objects that are not structured- cloneable, so a naive postMessage throws DataCloneError, which gets silently swallowed in async paths (the envelope never crosses the boundary). These helpers make that loud or transparent.

| Export | Description | |--------|-------------| | sendEnvelope(target, message, targetOrigin?) | The single boundary chokepoint shims post through. Per the active mode it posts as-is, snapshot-recovers a non-cloneable arg, or throws a loud, synchronous, actionable error. | | toCloneableSnapshot(value) | Deep snapshot stripping reactive proxies into plain objects/arrays while preserving binary (Uint8Array/ArrayBuffer), Date, RegExp, Map, Set, and cycles. Lossless for binary (unlike JSON); functions/symbols throw. | | setCloneMode(mode) / getCloneMode() | 'auto' (default: post as-is, snapshot-and-retry on DataCloneError, warn once), 'strict' (throw, never recover), or 'snapshot' (eagerly snapshot every envelope). | | clearCloneWarnings() | Reset the once-per-type auto-recovery warnings. |

import { setCloneMode, toCloneableSnapshot } from '@napplet/core';

// Default 'auto' handles reactive state transparently on the failure path.
// Or normalize explicitly:
napplet.outbox.subscribe(toCloneableSnapshot(filters), { relays });
// Or globally:
setCloneMode('snapshot');

These are SDK plumbing only — identical plain envelopes reach the wire, so they add no protocol surface.

Integration Note

@napplet/core is consumed by all packages in the napplet ecosystem for envelope types and NAP dispatch.

  • In this repo: @napplet/shim, @napplet/sdk, and @napplet/vite-plugin import NappletMessage, NapDomain, ShellSupports, and all shared protocol types from @napplet/core.
  • @napplet/nap domain modules (@napplet/nap/relay, @napplet/nap/identity, @napplet/nap/storage, @napplet/nap/inc, @napplet/nap/keys, @napplet/nap/media, @napplet/nap/notify, @napplet/nap/config, @napplet/nap/link, @napplet/nap/lists, and other active domain subpaths): extend NappletMessage for their domain-specific message types and call registerNap at import time.

Protocol Reference

  • NIP-5D -- Napplet-shell protocol specification

License

MIT