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

@walrus-streamkit/sdk

v0.1.4

Published

Backend SDK for video infrastructure on Walrus (blob storage) and Sui (ownership, payments, access) — upload, encrypt, publish, and resolve playback for VOD and pseudo-live. Runs on Node 18+ and Bun.

Downloads

646

Readme

@walrus-streamkit/sdk

Backend SDK for developer-first video infrastructure on Walrus (blob storage — the data plane) and Sui (ownership, payments, access — the control plane). Embed it in your own Node/Bun server to upload, encrypt, publish, and resolve playback for VOD and pseudo-live video.

This is the backend SDK: your server holds the signer and pays for storage. For viewer-side playback where the end-user's wallet pays for access and unlocks encrypted content, use @walrus-streamkit/client.

⚠️ Server-side only. Uses ffmpeg and node: built-ins (fs, child_process, crypto) — it does not run in a browser. Runs on Node 18+ and Bun.

Contents

Install

# Bun
bun add @walrus-streamkit/sdk

# Node — pick your package manager
npm install @walrus-streamkit/sdk
pnpm add @walrus-streamkit/sdk
yarn add @walrus-streamkit/sdk

The package ships both ESM and CommonJS builds with bundled type declarations, so it imports cleanly on Node and Bun:

import { WalrusStreamKit } from "@walrus-streamkit/sdk";          // ESM (Node ESM, Bun, bundlers)
const { WalrusStreamKit } = require("@walrus-streamkit/sdk");     // CommonJS (Node require)

Runtime requirements

  • Node 18+ or Bun 1.1+ — the SDK is runtime-agnostic. Every path (uploadFile(), upload(), getPlayback(), and upload-relay quilt encoding via WASM) runs on both; it relies only on node: built-ins (fs, child_process, crypto) that both runtimes provide. Use whichever your backend already runs.
  • ffmpeg on PATH — transcodes source media into HLS segments (uploadFile() and any transcoding pipeline). Install with your OS package manager (brew install ffmpeg, apt-get install ffmpeg, choco install ffmpeg), or point FFMPEG_PATH at a custom binary. This is a system dependency, not an npm/Bun one.
  • A funded Sui testnet/mainnet key when using the default upload-relay write mode — storing on Walrus costs gas + a storage tip.

Quickstart

Offline — no keys, no network (memory mode)

import { WalrusStreamKit } from "@walrus-streamkit/sdk";

const kit = new WalrusStreamKit({
  writeMode: "memory",
  gatewayBase: "http://localhost:3000",
  relayerUrl: "http://localhost:3000",
});

// `data` is one or more already-segmented .ts buffers (upload() does not transcode):
// one buffer → one segment. For a real file, use uploadFile() (transcodes into many).
const { videoId, segments } = await kit.upload({ data: tsBytes });
console.log(videoId, segments.length); // e.g. "up-1a2b3c4d", 1

memory keeps everything in RAM — ideal for tests and local dev. Nothing is durable.

Testnet — real Walrus (upload-relay mode)

import { WalrusStreamKit } from "@walrus-streamkit/sdk";
import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";

const kit = new WalrusStreamKit({
  writeMode: "upload-relay",                                  // the default
  network: "testnet",
  signer: Ed25519Keypair.fromSecretKey(process.env.SUI_KEY!), // your platform key — it pays
  relayerUrl: "http://localhost:8787",                        // your relayer
});

const res = await kit.uploadFile({ path: "./talk.mp4" });     // transcode → store on Walrus
const play = await kit.getPlayback(res.videoId);              // { kind: "public", manifestUrl }
// → feed play.manifestUrl to hls.js

Write modes

| Mode | Durable | Needs signer | Who pays | Use for | | --- | --- | --- | --- | --- | | memory | ❌ (RAM only) | No | — | tests, local dev, offline | | publisher | ✅ | No | the Walrus publisher (custodial) | quick custodial writes | | upload-relay (default) | ✅ | Yes | you — gas + a Walrus tip | production, non-custodial |

upload-relay quilt-encodes the bytes locally, your signer signs the Walrus registerBlob (bundling a tip), and the bytes ship to Walrus through your relayer's upload-proxy endpoint. Supported on testnet/mainnet only.

Configuration

new WalrusStreamKit(config?: StreamKitConfig)

Your config is shallow-merged over the testnet defaults (only keys you set override).

| Key | Type | Default (testnet) | | --- | --- | --- | | writeMode | "upload-relay" \| "publisher" \| "memory" | "upload-relay" | | signer | @mysten/sui Signer | — (required for upload-relay + publish) | | network | "testnet" \| "mainnet" \| "devnet" \| "localnet" | "testnet" | | packageId | string | 0xda93702b…f3e5 (deployed walrus_video v2) | | channelId | string | — (required for publish()) | | relayerUrl | string | http://localhost:8787 | | walrusPublisher | string | https://publisher.walrus-testnet.walrus.space | | walrusAggregator | string | https://aggregator.walrus-testnet.walrus.space | | walrusUploadRelay | string | https://upload-relay.testnet.walrus.space | | gatewayBase | string | http://localhost:8787 | | fetchImpl | typeof fetch | globalThis.fetch |

⚠️ Upload-relay host invariant: your walrusUploadRelay and the relayer's own WALRUS_UPLOAD_RELAY must point at the same host (testnet: upload-relay.testnet.walrus.space). Otherwise the tip pays the wrong operator and the relay rejects the upload.

Guides

Upload a file (recommended)

const res = await kit.uploadFile({ path: "./video.mp4", visibility: 0 });
// res: { videoId, manifestBlobId, integrityRoot, segments, sealKeyB64? }

uploadFile transcodes the input with ffmpeg, then streams the resulting segments into storage one at a time — bounded memory, so it handles multi-hour videos. Runs on Node and Bun (segments are read via node:fs).

Upload pre-segmented bytes

const res = await kit.upload({ data: [seg0, seg1] }); // each item is a complete .ts segment

upload() does not transcode — data is treated as already-segmented .ts parts. The whole payload sits in RAM, so use it for small clips or live segments, not large files.

Visibility & encryption

visibility selects the on-chain access kind:

| value | name | encrypted (Seal)? | | --- | --- | --- | | 0 | public | no | | 1 | allowlist | yes | | 2 | paid | yes | | 3 | subscription | yes |

For any visibility !== 0, each segment is AES-128 encrypted and the content key is Seal-wrapped; the wrapped key is returned as res.sealKeyB64. Register it with your relayer so it can serve the key — the viewer's wallet then unlocks it on-chain via seal_approve_*:

const res = await kit.uploadFile({ path: "./paid.mp4", visibility: 2 });

await fetch(`${relayerUrl}/v1/videos/${res.videoId}/register`, {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({
    manifestBlobId: res.manifestBlobId,
    segments: res.segments,
    visibility: 2,
    encrypted: true,
    sealKeyB64: res.sealKeyB64, // ← required for encrypted videos
  }),
});

Resolve playback

const pb = await kit.getPlayback(videoId, viewerAddress /* optional */);
// { kind: "public",    manifestUrl }
// { kind: "encrypted", manifestUrl, keyUrl, sealId, isOwner }

Feed manifestUrl to hls.js. For kind: "encrypted", the actual Seal decrypt + playback happens in the viewer client (@walrus-streamkit/client) — the user's wallet signs the unlock.

Pseudo-live → durable VOD

const live = kit.startLive({ segDurationSec: 2, windowSize: 6 });

await live.pushSegment(tsBytes);        // push .ts chunks as they're captured
const playlist = live.mediaPlaylist();  // current sliding-window HLS playlist (no #EXT-X-ENDLIST)
const vod = await live.end();           // finalize → full VOD manifest, reusing the same blobs

Use writeMode: "publisher" or "memory" for live — per-segment upload-relay writes are too slow for a live window.

Relayer metrics

const m = await kit.getMetrics();
// { cache: { hits, misses, sets, hitRatio, bytes? },
//   uploadQueue: { waiting, active, completed, failed, delayed },
//   requests: { byPath, total }, uptimeMs }

Reads operational metrics from your relayer's GET /v1/metrics.

Publish on-chain (optional)

const { videoAssetId, txDigest } = await kit.publish({
  videoId,
  manifestBlobId: res.manifestBlobId!,
  integrityRoot: res.integrityRoot!,
  durationMs: 600_000,
  channelId: "0x…",   // or set config.channelId
  visibility: 0,
});

Signs video::create / video::complete, minting a VideoAsset owned by your signer. Requires signer, a channelId, and network testnet/mainnet.

API reference

import { … } from "@walrus-streamkit/sdk"

| Export | Signature | Status | | --- | --- | --- | | WalrusStreamKit | new (config?: StreamKitConfig) | ✅ | | .upload | (input: UploadInput) => Promise<UploadResult> | ✅ | | .uploadFile | (input: UploadFileInput) => Promise<UploadResult> | ✅ | | .getPlayback | (videoId: string, viewer?: string) => Promise<PlaybackResult> | ✅ | | .publish | (input: PublishInput) => Promise<PublishResult> | ✅ | | .startLive | (opts?: { segDurationSec?; windowSize?; videoId? }) => LiveSession | ✅ | | .getMetrics | () => Promise<RelayerMetrics> | ✅ | | .getStatus | (videoId: string) => Promise<ProcessingStatus> | 🚧 not implemented | | .createPolicy | (input: PolicyInput) => Promise<PolicyResult> | 🚧 not implemented | | .addToAllowlist | (policyId: string, address: string) => Promise<{ digest }> | 🚧 not implemented | | .webhooks.register / .webhooks.list | webhook management | 🚧 not implemented | | LiveSession | pushSegment(ts) / mediaPlaylist() / end() | ✅ | | processUpload | low-level pipeline → ProcessedUpload | ✅ | | MemoryStorageAdapter, FfmpegTranscoder, createSealWrapper | advanced building blocks | ✅ | | resolveConfig, testnetDefaults, NotImplementedError | config + helpers | ✅ |

🚧 Methods marked not implemented throw NotImplementedError until their milestone lands.

Key types

UploadInput      { data: Uint8Array | Uint8Array[]; visibility?: 0|1|2|3; policyId?: string; durationMsHint?: number }
UploadFileInput  { path: string; visibility?: number }
UploadResult     { videoId: string; jobId?: string; manifestBlobId?: string; integrityRoot?: string; segments: string[]; sealKeyB64?: string }
PlaybackResult   { kind: "public"; manifestUrl: string }
               | { kind: "encrypted"; manifestUrl: string; keyUrl: string; sealId: string; isOwner: boolean }
PublishInput     { videoId; manifestBlobId; integrityRoot; durationMs; channelId?; visibility?; policyId? }

Caveats

  • Server-side only — ffmpeg + node: built-ins; not browser-compatible. Viewer playback of encrypted video lives in @walrus-streamkit/client.
  • Runtime: Node 18+ or Bun. Every path — uploadFile(), upload(), getPlayback(), and upload-relay quilt encoding (WASM) — runs on both. ffmpeg must be on PATH for transcoding.
  • upload-relay requires a signer and network testnet/mainnet, and its host must match the relayer's WALRUS_UPLOAD_RELAY.
  • sealKeyB64 is returned only for visibility !== 0; you must register it with the relayer or encrypted playback can't serve the key.
  • Live over upload-relay is impractical — use publisher or memory.
  • memory mode is not durable (RAM only; lost on restart).
  • 🚧 getStatus, createPolicy, addToAllowlist, and webhooks are not implemented yet.

See also