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/sdk

v0.27.2

Published

Typed named exports wrapping window.napplet for bundler-consuming napplet developers

Readme

@napplet/sdk

Named TypeScript exports for napplet developers using a bundler. Wraps window.napplet at call time.

Getting Started

Prerequisites

  • A NIP-5D runtime injects window.napplet before SDK methods are called
  • A shell host running a napplet protocol shell implementation

How It Works

  1. Import named exports from @napplet/sdk -- outbox, common, relay, inc, storage, keys, ble, count, lists
  2. Each SDK method delegates to its injected window.napplet.* counterpart at call time
  3. If window.napplet or a requested domain is unavailable when a method is called, a descriptive error is thrown

Installation

npm install @napplet/sdk

Quick Start

import { outbox, common, inc, storage, keys, media, notify, config, resource, ble, webrtc, link, count, lists } from '@napplet/sdk';

// Read kind 1 notes through outbox-aware routing
const { events } = await outbox.query(
  [{ kinds: [1], limit: 20 }],
  { timeoutMs: 3000 },
);
for (const result of events) console.log('Note:', result.event.content);

// Subscribe to live updates through the same outbox boundary
const sub = outbox.subscribe([{ kinds: [1], limit: 20 }], { timeoutMs: 3000 });
sub.on('event', (result) => console.log('New note:', result.event.content));

// Publish a signed note through the user's outbox/write relays
const published = await outbox.publish({
  kind: 1,
  content: 'Hello from my napplet!',
  tags: [],
  created_at: Math.floor(Date.now() / 1000),
});
if (!published.ok || !published.event) throw new Error(published.error ?? 'publish failed');

// Common social actions keep consent, event construction, signing, and relay routing in the shell
await common.react(published.event.id, '+');

// Inter-napplet messaging. The payload is this application's local choice.
inc.emit('napplet:note/open', { targetId: 'local-note-id' });
const incSub = inc.on('napplet:note/open', (event) => {
  console.log('Local note-open payload:', event.payload);
});

// Scoped storage
await storage.setItem('theme', 'dark');
const theme = await storage.getItem('theme'); // 'dark'

// Register keyboard action
const result = await keys.registerAction({
  id: 'editor.save', label: 'Save', defaultKey: 'Ctrl+S',
});

// Listen for bound key locally
const keySub = keys.onAction('editor.save', () => {
  console.log('Save triggered!');
});

// Create a media session
const { sessionId } = await media.createSession({
  owner: 'napplet',
  metadata: { title: 'My Song', artist: 'The Artist' },
});
media.reportState(sessionId, { status: 'playing', position: 42.5, duration: 240 });

// Send a notification
const { notificationId } = await notify.send({
  title: 'Task complete', body: 'Build succeeded', priority: 'normal',
});
notify.badge(1);

// Read per-napplet config (shell-validated + defaulted)
const values = await config.get();
console.log('Current theme:', values.theme);

// Subscribe to live config updates
const configSub = config.subscribe((v) => {
  applyTheme(v.theme);
});

// Deep-link settings UI
config.openSettings({ section: 'appearance' });

// Fetch external bytes via the shell (the iframe sandbox + strict CSP block direct fetch)
const avatarBlob = await resource.bytes('https://example.com/avatar.png');
const resourceItems = await resource.bytesMany([
  'https://example.com/avatar.png',
  'blossom:sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855',
]);
const handle = resource.bytesAsObjectURL('blossom:sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855');
imgEl.src = handle.url;
// handle.revoke() when done

// Open a shell-mediated WebRTC data session
const { session } = await webrtc.open({ scope: { type: 'direct', pubkey: 'abc123...' } });
await webrtc.send(session.id, { body: 'hello' });
// Open a shell-mediated BLE session and inspect exposed services
const { session: bleSession } = await ble.open({ acceptAllDevices: true });
const bleServices = await ble.services(bleSession.id);
// Open an external URL through the shell
await link.open('https://example.com/post/123', { label: 'Read post' });
// Add an item to a supported NIP-51 list through the runtime
await lists.add({ type: 'mute-list' }, [{ itemType: 'pubkey', value: 'abc123...' }]);

// Clean up
sub.close();
incSub.close();
keySub.close();
configSub.close();

API Reference

relay

Low-level relay operations through the shell's relay pool. Mirrors window.napplet.relay. Use this for explicit relay-local behavior such as group relays, diagnostics, and protocol tooling. For normal social reads/publishes, prefer outbox or a higher-level domain such as common, lists, count, or dm.

| Method | Returns | Description | |--------|---------|-------------| | subscribe(filters, onEvent, onEose, options?) | Subscription | Open a live relay subscription through the shell's relay pool; onEvent receives RelayEventResult | | publish(template, options?) | Promise<NostrEvent> | Send event template to the shell for signing and broadcast | | publishEncrypted(template, recipient, encryption?) | Promise<NostrEvent> | Send event template for encryption, signing, and broadcast | | query(filters) | Promise<RelayEventResult[]> | One-shot query: collect RelayEventResult records until EOSE, resolve |

outbox

Outbox-aware relay routing. Mirrors window.napplet.outbox. The napplet supplies filters, event ids, templates, and intent; the shell owns NIP-65 relay discovery, fallback, deduplication, signature validation, signing, and publish fanout.

| Method | Returns | Description | |--------|---------|-------------| | getEvent(eventId, options?) | Promise<OutboxEventResult> | Fetch one event through author/relay-aware routing | | query(filters, options?) | Promise<OutboxResult> | One-shot outbox-aware query returning RelayEventResult[] | | subscribe(filters, options?) | OutboxSubscription | Live outbox-aware stream with on('event') and close() | | publish(template, options?) | Promise<OutboxPublishResult> | Shell-sign and fan out through the user's write relays and directed inbox relays | | resolveRelays(target) | Promise<OutboxRelayPlan> | Diagnostic/advisory relay plan; prefer query/subscribe/publish for app behavior |

common

Common social actions. Mirrors window.napplet.common. Use it for NIP-19 helpers, profile lookup, follows, follow/unfollow, reactions, and reports so the shell owns consent, event construction, signing, publishing, and lookup policy.

inc

Inter-napplet communication between napplets. Mirrors window.napplet.inc.

Messages are sent as JSON envelope objects ({ type: 'inc.emit', topic, payload }) and received as ({ type: 'inc.event', topic, payload, sender }). Topics are opaque strings: a sender and subscriber use the same complete value. The package does not prescribe convention payload schemas, wildcard, prefix, or canonicalization behavior.

| Method | Returns | Description | |--------|---------|-------------| | emit(topic, payload?) | void | Broadcast an INC event to other napplets via the shell | | on(topic, callback) | { close(): void } | Subscribe to one-object IncEvent callbacks | | channel.open(target) | Promise<ChannelHandle> | Open a symmetric point-to-point channel | | channel.onOpened(callback) | { close(): void } | Receive inbound symmetric channel handles | | channel.list() | Promise<ChannelInfo[]> | List active channel snapshots | | channel.broadcast(payload?) | void | Broadcast to all open channel peers |

This non-normative SDK reference follows NAP-INC draft PR #89 at its adopted head. For outbound INC only, a queried convention URI is runtime shorthand for a stable topic plus a shallow text payload:

inc.emit('napplet:profile/open?pubkey=abc123');
// -> { type: 'inc.emit', topic: 'napplet:profile/open', payload: { pubkey: 'abc123' } }

inc.on('napplet:profile/open', (event) => {
  console.log(event.sender, event.payload);
});

The runtime percent-decodes the query text (+ remains +) before exact topic routing. A fragment, malformed percent encoding, repeated decoded name, or a query paired with an explicit payload throws before emission. Pass structured or non-text data through emit's explicit payload with a queryless topic. This is an INC emit input rule; subscriptions and shell routing do not parse queries or perform wildcard, prefix, or normalization matching.

Deprecated IFC compatibility exports are available as migration aliases: ifc, ifcEmit, ifcOn, IFC_DOMAIN, installIfcShim, and the Ifc* message types. They forward to the INC implementation and resolve to the canonical inc domain; new code should use inc, incEmit, incOn, INC_DOMAIN, installIncShim, and Inc* names.

intent

Archetype intent dispatch. Mirrors window.napplet.intent. Use invoke(request) or open(archetype, payload?, opts?).

import { intent } from '@napplet/sdk';

const result = await intent.open(
  'profile',
  { pubkey: 'abc123' },
  { convention: 'napplet:profile/open', behavior: { newWindow: true } },
);
if (!result.handled) throw new Error(result.error);
console.log(`Handled by ${result.handler} in ${result.windowId}`);

Results include required ok, archetype, action, and handled fields. IntentBehavior supports focus, newWindow, and reuse. This non-normative reference follows the living NAP-INTENT document.

storage

Sandboxed key-value storage. Mirrors window.napplet.storage. 512 KB quota per napplet.

| Method | Returns | Description | |--------|---------|-------------| | getItem(key) | Promise<string \| null> | Retrieve a stored value | | setItem(key, value) | Promise<void> | Store a key-value pair | | removeItem(key) | Promise<void> | Remove a stored key | | keys() | Promise<string[]> | List all stored keys | | instance.getItem/setItem/removeItem/keys | (same as above) | Per-instance storage scope — same surface, scoped to this napplet instance (sets scope: "instance" on the wire). See NAP-STORAGE. |

media

Ownership-aware media sessions. Napplet-owned sessions let your app play media and report state to the shell; shell-owned sessions provide a source so the shell fetches, plays, and reports state back.

| Method | Returns | Description | |--------|---------|-------------| | createSession(options) | Promise<{ sessionId?, owner?, error? }> | Create a napplet- or shell-owned media session | | updateSession(sessionId, metadata) | void | Update metadata for an existing session | | destroySession(sessionId) | void | Destroy a session | | reportState(sessionId, state) | void | Report playback state | | reportCapabilities(sessionId, actions) | void | Declare supported media actions | | sendCommand(sessionId, action, value?) | void | Request a control action from the current playback owner | | onCommand(sessionId, callback) | { close(): void } | Listen for shell media commands | | onState(sessionId, callback) | { close(): void } | Listen for shell-reported state on shell-owned sessions | | onCapabilities(sessionId, callback) | { close(): void } | Listen for shell-reported capabilities on shell-owned sessions | | onControls(sessionId, callback) | { close(): void } | Listen for the shell's supported control list |

notify

Shell-rendered notifications. Mirrors window.napplet.notify.

| Method | Returns | Description | |--------|---------|-------------| | send(notification) | Promise<{ notificationId }> | Send a notification to the shell | | dismiss(notificationId) | void | Dismiss a notification | | badge(count) | void | Set badge count (0 to clear) | | registerChannel(channel) | void | Register a notification channel | | requestPermission(channel?) | Promise<{ granted }> | Request permission to send notifications | | onAction(callback) | { close(): void } | Listen for action button clicks | | onClicked(callback) | { close(): void } | Listen for notification body clicks | | onDismissed(callback) | { close(): void } | Listen for dismissals | | onControls(callback) | { close(): void } | Listen for shell's notification capabilities |

config

Per-napplet declarative configuration (NAP-CONFIG). Mirrors window.napplet.config.

| Method | Returns | Description | |--------|---------|-------------| | get() | Promise<Record<string, unknown>> | One-shot snapshot of validated + defaulted config values | | subscribe(callback) | { close(): void } | Live push stream (initial snapshot + updates on change) | | openSettings(options?) | void | Open shell's settings UI, optionally deep-linked to x-napplet-section | | registerSchema(schema, version?) | Promise<void> | Runtime schema registration (escape hatch; prefer vite-plugin configSchema) | | onSchemaError(callback) | () => void | Listen for config.schemaError pushes (returns plain teardown fn) | | schema (accessor) | Record<string, unknown> \| null | Readonly current schema |

FromSchema type inference (NAP-CONFIG)

json-schema-to-ts is declared as an optional peerDependency of @napplet/nap (scoped to the @napplet/nap/config domain's FromSchema typing). Install it in your napplet to get FromSchema<typeof schema> typing for your config.subscribe callback -- the values parameter is inferred directly from your schema (enums, required fields, defaults all flow through). Authors who skip json-schema-to-ts pay no install cost and config.subscribe still works with the default Record<string, unknown> typing.

import { config } from '@napplet/sdk';
import type { FromSchema } from 'json-schema-to-ts';

const schema = {
  type: 'object',
  properties: {
    theme: { type: 'string', enum: ['light', 'dark'], default: 'dark' },
  },
  required: ['theme'],
} as const;

type MyConfig = FromSchema<typeof schema>;

const sub = config.subscribe((values: MyConfig) => {
  // values.theme is typed 'light' | 'dark'
});

Install the peer when you want typed callbacks:

npm install --save-dev json-schema-to-ts

resource

Sandboxed byte fetching (NAP-RESOURCE). Mirrors window.napplet.resource. Required because the iframe sandbox + strict CSP block direct fetch() / <img src=externalUrl> / XMLHttpRequest.

| Method | Returns | Description | |--------|---------|-------------| | info() | Promise<ResourceInfo> | Inspect advisory schemes and coarse policy limits. Not required before fetching. | | bytes(url, opts?) | Promise<Blob> | Fetch bytes through the shell. opts.signal accepts an AbortSignal. | | bytesMany(urls, opts?) | Promise<ResourceBytesItem[]> | Fetch many URLs through one envelope. Items preserve input order and length. | | bytesAsObjectURL(url) | { url: string; revoke: () => void } | Synchronous handle whose url resolves to a blob URL once the fetch completes. |

Canonical schemes: data: (in-shim), https: (shell-side under policy), blossom:sha256:<hex> (hash-verified), htree: (Hashtree-verified), nostr:<bech32> (single-hop NIP-19).

Bare helper aliases are also re-exported for consumers that prefer functional imports:

import { resourceInfo, resourceBytes, resourceBytesMany, resourceBytesAsObjectURL } from '@napplet/sdk';

const info = await resourceInfo();
const blob = await resourceBytes('https://example.com/avatar.png');
const items = await resourceBytesMany(['https://example.com/a.png']);
const handle = resourceBytesAsObjectURL('blossom:sha256:...');

keys

Keyboard forwarding and action keybindings. Mirrors window.napplet.keys.

| Method | Returns | Description | |--------|---------|-------------| | registerAction(action) | Promise<{ actionId, binding? }> | Declare a named action the shell can bind to a key | | unregisterAction(actionId) | void | Remove a previously registered action | | onAction(actionId, callback) | { close(): void } | Register a local handler for a bound key (zero-latency, not a wire message) |

identity

Read-only user identity queries (NAP-IDENTITY). Use the exported identity object, window.napplet.identity.* directly after runtime injection, or the bare-name helpers below.

| Method | Returns | Description | |--------|---------|-------------| | identity.getPublicKey() | Promise<string> | Shell-user pubkey, or "" when no user/signer is connected | | identity.onChanged(handler) | { close(): void } | Listen for shell-pushed identity changes; handler receives a pubkey or "" |

Bare helper aliases are also re-exported for consumers that prefer functional imports:

import { identity, identityGetPublicKey, identityOnChanged } from '@napplet/sdk';

const pubkey = await identity.getPublicKey();
const sub = identityOnChanged((nextPubkey) => {
  console.log(nextPubkey || 'signed out');
});

NAP-IDENTITY is strictly read-only. Signing remains delegated through outbox.publish() or higher-level action domains (common, lists, dm). Use relay.publish() only for explicit low-level relay escape hatches. Identity changes arrive through identity.changed rather than polling.

Runtime-Injected Domains

NIP-5D runtimes inject window.napplet before napplet code runs. Available NAP domains are present as properties; unavailable domains are absent. Gate optional behavior with property presence:

if (window.napplet?.outbox) { /* outbox API is available */ }
if (window.napplet?.relay) { /* low-level relay API is available */ }
if (window.napplet?.identity) { /* identity API is available */ }
if (window.napplet?.inc) { /* inter-napplet channel API is available */ }

Namespace Import

import * as napplet from '@napplet/sdk' produces an object structurally identical to window.napplet:

import * as napplet from '@napplet/sdk';

const { events } = await napplet.outbox.query([{ kinds: [1], limit: 20 }]);
napplet.storage.setItem('key', 'value');
napplet.config.subscribe((v) => console.log(v));

Types

All protocol types are re-exported from @napplet/core and the NAP packages:

import type {
  // Protocol types (from @napplet/core)
  NostrEvent,
  NostrFilter,
  Subscription,
  EventTemplate,
  NappletMessage,
  NapDomain,
  // NAP message types (re-exported from NAP packages)
  RelayNapMessage,
  IdentityNapMessage,
  StorageNapMessage,
  IncNapMessage,
  KeysNapMessage,
  BleNapMessage,
  ListsNapMessage,
  Action,
} from '@napplet/sdk';

Core Protocol Types

| Type | Description | |------|-------------| | NostrEvent | Standard Nostr event object | | NostrFilter | Relay subscription filter | | Subscription | Handle with close() method | | EventTemplate | Unsigned event template for shell-mediated publish domains such as outbox.publish() | | NappletMessage | Base JSON envelope type for all protocol messages | | NapDomain | String literal union of NAP domain names |

NAP Message Types

These are discriminated union types covering all messages in each NAP domain. Useful for writing typed message handlers in shell implementations or protocol-aware code.

| Type | NAP Package | Description | |------|-------------|-------------| | RelayNapMessage | @napplet/nap/relay | Discriminated union of all relay domain messages | | IdentityNapMessage | @napplet/nap/identity | Discriminated union of all identity domain messages | | StorageNapMessage | @napplet/nap/storage | Discriminated union of all storage domain messages | | IncNapMessage | @napplet/nap/inc | Discriminated union of all INC domain messages | | KeysNapMessage | @napplet/nap/keys | Discriminated union of all keys domain messages | | MediaNapMessage | @napplet/nap/media | Discriminated union of all media domain messages | | NotifyNapMessage | @napplet/nap/notify | Discriminated union of all notify domain messages | | ConfigNapMessage | @napplet/nap/config | Discriminated union of all config domain messages | | ResourceNapMessage | @napplet/nap/resource | Discriminated union of all resource domain messages | | BleNapMessage | @napplet/nap/ble | Discriminated union of all BLE domain messages | | CountNapMessage | @napplet/nap/count | Discriminated union of all count domain messages | | ListsNapMessage | @napplet/nap/lists | Discriminated union of all lists domain messages | | CommonNapMessage | @napplet/nap/common | Discriminated union of all common domain messages | | SerialNapMessage | @napplet/nap/serial | Discriminated union of all serial domain messages |

Individual message types (e.g., RelaySubscribeMessage, IdentityGetPublicKeyMessage) are also re-exported from @napplet/sdk for fine-grained typing.

NAP Domain Constants

Each NAP domain has a string constant re-exported from its package:

import { RELAY_DOMAIN, IDENTITY_DOMAIN, STORAGE_DOMAIN, INC_DOMAIN, THEME_DOMAIN, KEYS_DOMAIN, MEDIA_DOMAIN, NOTIFY_DOMAIN, CONFIG_DOMAIN, RESOURCE_DOMAIN, CVM_DOMAIN, OUTBOX_DOMAIN, UPLOAD_DOMAIN, INTENT_DOMAIN, BLE_DOMAIN, WEBRTC_DOMAIN, LINK_DOMAIN, COUNT_DOMAIN, LISTS_DOMAIN, COMMON_DOMAIN, SERIAL_DOMAIN, DM_DOMAIN } from '@napplet/sdk';
// Values: 'relay', 'identity', 'storage', 'inc', 'theme', 'keys', 'media', 'notify', 'config', 'resource', 'cvm', 'outbox', 'upload', 'intent', 'ble', 'webrtc', 'link', 'count', 'lists', 'common', 'serial', 'dm'

These constants are re-exported from the individual domain packages. Use property presence for type-safe conditional logic:

if (window.napplet?.relay) {
  // relay operations are available
}

if (window.napplet?.identity) {
  // identity queries are available
}

if (window.napplet?.config) {
  // NAP-CONFIG is available -- schema registration and subscribe()
}

if (window.napplet?.resource) {
  // resource.bytes(url) is available.
}

Runtime Guard

If window.napplet or a requested domain is unavailable when an SDK method is called, a clear error is thrown:

Error: window.napplet.relay is unavailable -- runtime did not inject this domain

This protects napplets from assuming optional domains are always present.

SDK vs Shim

| | @napplet/sdk | @napplet/shim | |---|---|---| | Import style | import { relay } from '@napplet/sdk' | import { installNappletGlobal } from '@napplet/shim' | | What it does | Named exports wrapping injected domains | Runtime-side global installer | | Dependencies | @napplet/core (types only) | None (types from @napplet/core) | | Side effects | None | Yes -- installs globals on a target window | | Required | Optional convenience for napplets | Runtime implementation detail |

Typical napplet usage: import the SDK for typed API access:

import { relay, inc, storage, keys, media, notify } from '@napplet/sdk';

If you are writing a vanilla napplet with no build step, use the injected window.napplet.* namespace directly -- the SDK is not required.

Protocol Reference

License

MIT