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

@reactor-models/sana-streaming

v2.0.1

Published

Strongly-typed SDK for the SanaStreaming model on Reactor

Readme

@reactor-models/sana-streaming

Typed JavaScript + React SDK for the SanaStreaming model on Reactor. Version v2.0.1.


Get started

Scaffold a starter app for SanaStreaming with create-reactor-app:

npx create-reactor-app my-app --model=sana-streaming
pnpm dlx create-reactor-app my-app --model=sana-streaming

Install

npm install @reactor-models/sana-streaming
pnpm add @reactor-models/sana-streaming

The package exports a plain-JavaScript client and a set of React bindings. Import whichever you need from @reactor-models/sana-streaming:

import { SanaStreamingModel } from "@reactor-models/sana-streaming";
import { SanaStreamingProvider, useSanaStreaming } from "@reactor-models/sana-streaming";

React 18 or later is required when using the provider and hooks. The token-loading examples below use React 19's use(); on React 18, fetch the JWT in a useEffect and pass it to the provider once it resolves.


Authenticate

Reactor uses short-lived JWTs for session auth. You hold your API key on your server, mint a token on demand, and the client never sees the raw key. Tokens are valid for 6 hours — if one leaks, it expires on its own.

Mint a JWT with POST https://api.reactor.inc/tokens and the Reactor-API-Key header; the response JSON is { "jwt": "..." }.

JavaScript (Next.js route handler)

// app/api/reactor/token/route.ts
import { NextResponse } from "next/server";

export async function POST() {
  const res = await fetch("https://api.reactor.inc/tokens", {
    method: "POST",
    headers: { "Reactor-API-Key": process.env.REACTOR_API_KEY! },
  });
  const { jwt } = await res.json();
  return NextResponse.json({ jwt });
}

React (provider)

Call the /api/reactor/token route above from a client component and pass the result to the provider:

"use client";

import { use } from "react";
import { SanaStreamingProvider } from "@reactor-models/sana-streaming";
import { ReactorView } from "@reactor-team/js-sdk";

async function getToken() {
  const r = await fetch("/api/reactor/token", { method: "POST" });
  const { jwt } = await r.json();
  return jwt;
}

const tokenPromise = getToken();

export default function App() {
  const token = use(tokenPromise);
  return (
    <SanaStreamingProvider jwtToken={token} connectOptions={{ autoConnect: true }}>
      <ReactorView className="w-full aspect-video" />
    </SanaStreamingProvider>
  );
}

Connect

JavaScript

import { SanaStreamingModel } from "@reactor-models/sana-streaming";

const sanaStreaming = new SanaStreamingModel();
await sanaStreaming.connect(jwt);

React

The provider takes the JWT as a prop; fetch it from the same /api/reactor/token route the Authenticate example mints:

"use client";

import { use } from "react";
import { SanaStreamingProvider, useSanaStreaming } from "@reactor-models/sana-streaming";

async function getToken() {
  const r = await fetch("/api/reactor/token", { method: "POST" });
  const { jwt } = await r.json();
  return jwt;
}

const tokenPromise = getToken();

function Controller() {
  const { status } = useSanaStreaming();
  return <span>Status: {status}</span>;
}

export default function App() {
  const token = use(tokenPromise);
  return (
    <SanaStreamingProvider jwtToken={token}>
      <Controller />
    </SanaStreamingProvider>
  );
}

Events

Client-to-model commands. The typed surface is SanaStreamingModel (one method per event) in plain JS, and useSanaStreaming() in React — every field name below matches the parameter name the method accepts. Every awaited call resolves once the model's handler has completed; a command whose handler returns a message resolves with that reply (see each event's "Returns" line).

pause

Pause editing after the current chunk; frames stop until resume. Valid only while generating. Emits generation_paused + state, or command_error if not generating or already paused.

Returns: generation_paused{ type: "generation_paused", chunk_index: 0 } (or undefined when the send fails).

No parameters.

JavaScript

import { SanaStreamingModel } from "@reactor-models/sana-streaming";

const sanaStreaming = new SanaStreamingModel();
await sanaStreaming.connect(jwt);

const reply = await sanaStreaming.pause();

if (reply) {
  console.log("generation_paused", reply.chunk_index);
}

React

"use client";
import { useSanaStreaming } from "@reactor-models/sana-streaming";

function Example() {
  const { pause } = useSanaStreaming();

  return <button onClick={() => pause()}>pause</button>;
}

reset

Abort the current run, clear the prompt, and return to the waiting state. Valid at any time. Emits generation_reset + state; re-arm with set_prompt / start.

Returns: generation_reset{ type: "generation_reset", reason: "" } (or undefined when the send fails).

No parameters.

JavaScript

import { SanaStreamingModel } from "@reactor-models/sana-streaming";

const sanaStreaming = new SanaStreamingModel();
await sanaStreaming.connect(jwt);

const reply = await sanaStreaming.reset();

if (reply) {
  console.log("generation_reset", reply.reason);
}

React

"use client";
import { useSanaStreaming } from "@reactor-models/sana-streaming";

function Example() {
  const { reset } = useSanaStreaming();

  return <button onClick={() => reset()}>reset</button>;
}

start

Begin streaming on main_video, transforming the live camera track (webcam). A prompt is OPTIONAL — with none set the output stays close to the camera input. Emits generation_started + state, or command_error. No effect while already generating.

Returns: nothing — the awaited call resolves undefined once the model's handler has run.

No parameters.

JavaScript

import { SanaStreamingModel } from "@reactor-models/sana-streaming";

const sanaStreaming = new SanaStreamingModel();
await sanaStreaming.connect(jwt);

await sanaStreaming.start();

React

"use client";
import { useSanaStreaming } from "@reactor-models/sana-streaming";

function Example() {
  const { start } = useSanaStreaming();

  return <button onClick={() => start()}>start</button>;
}

resume

Resume editing after a pause, continuing from where it stopped. Valid only while paused. Emits generation_resumed + state, or command_error if not paused.

Returns: generation_resumed{ type: "generation_resumed", chunk_index: 0 } (or undefined when the send fails).

No parameters.

JavaScript

import { SanaStreamingModel } from "@reactor-models/sana-streaming";

const sanaStreaming = new SanaStreamingModel();
await sanaStreaming.connect(jwt);

const reply = await sanaStreaming.resume();

if (reply) {
  console.log("generation_resumed", reply.chunk_index);
}

React

"use client";
import { useSanaStreaming } from "@reactor-models/sana-streaming";

function Example() {
  const { resume } = useSanaStreaming();

  return <button onClick={() => resume()}>resume</button>;
}

setSeed

Set the random seed that controls the edit's randomness, so the same source, prompt, and seed reproduce the same result. Valid at any time, but read once when generation begins — change it then reset to re-seed an in-progress run. Emits state.

Returns: nothing — the awaited call resolves undefined once the model's handler has run.

| Parameter | Type | Description | |---|---|---| | seed | number | Non-negative noise seed. (min 0, default 0) |

JavaScript

import { SanaStreamingModel } from "@reactor-models/sana-streaming";

const sanaStreaming = new SanaStreamingModel();
await sanaStreaming.connect(jwt);

await sanaStreaming.setSeed({ seed: 0 });

React

"use client";
import { useSanaStreaming } from "@reactor-models/sana-streaming";

function Example() {
  const { setSeed } = useSanaStreaming();

  return <button onClick={() => setSeed({ seed: 0 })}>setSeed</button>;
}

setPrompt

Set the natural-language instruction that steers the edit. Optional and valid at any time — with no prompt the output stays close to the source video; set or change it (including while streaming) to steer the edit live, visible about one chunk later on a not-yet-shown chunk. Emits prompt_accepted + state, or command_error (the previous prompt is kept) if the prompt cannot be processed.

Returns: prompt_accepted{ type: "prompt_accepted", prompt: "A sunset over the ocean" } (or undefined when the send fails).

| Parameter | Type | Description | |---|---|---| | prompt | string | Natural-language edit instruction. (default "") |

JavaScript

import { SanaStreamingModel } from "@reactor-models/sana-streaming";

const sanaStreaming = new SanaStreamingModel();
await sanaStreaming.connect(jwt);

const reply = await sanaStreaming.setPrompt({ prompt: "A sunset over the ocean" });

if (reply) {
  console.log("prompt_accepted", reply.prompt);
}

React

"use client";
import { useSanaStreaming } from "@reactor-models/sana-streaming";

function Example() {
  const { setPrompt } = useSanaStreaming();

  return <button onClick={() => setPrompt({ prompt: "A sunset over the ocean" })}>setPrompt</button>;
}

setAnchorInterval

Periodically re-ground the edit on the source video every chunks chunks (0 = never). Over a long edit the output can slowly drift away from the source; re-grounding pulls it back, at the cost of a brief visible refresh on the following chunk. Valid at any time, including while streaming (it takes effect at the next chunk boundary). Each re-ground emits anchored.

Returns: nothing — the awaited call resolves undefined once the model's handler has run.

| Parameter | Type | Description | |---|---|---| | chunks | number | Chunks between anchors; 0 disables anchoring. (min 0, default 20) |

JavaScript

import { SanaStreamingModel } from "@reactor-models/sana-streaming";

const sanaStreaming = new SanaStreamingModel();
await sanaStreaming.connect(jwt);

await sanaStreaming.setAnchorInterval({ chunks: 20 });

React

"use client";
import { useSanaStreaming } from "@reactor-models/sana-streaming";

function Example() {
  const { setAnchorInterval } = useSanaStreaming();

  return <button onClick={() => setAnchorInterval({ chunks: 20 })}>setAnchorInterval</button>;
}

Messages

Model-to-client messages. Register a typed listener with on… on SanaStreamingModel, or a useSanaStreaming… hook in React, to receive only the messages you care about.

state

Emitted as a full snapshot of the session's observable state.

Sent on connect, after every accepted state-changing command, and at each chunk boundary, so a client can render its UI from one message instead of accumulating individual events.

Listener: onState · React hook: useSanaStreamingState

| Field | Type | Description | |---|---|---| | seed | number | Current value of the noise seed. | | paused | boolean | True while generation is paused via pause. | | running | boolean | True while the chunk loop is actively producing frames. | | started | boolean | True once start has been accepted; reset to false by reset. | | has_prompt | boolean | True once an edit prompt has been set. | | current_chunk | number | Zero-based index of the last completed chunk; 0 before the first. | | current_prompt | string \| null | The prompt currently steering the edit, or null. | | anchor_interval | number | Re-ground generation every this many chunks (0 = never). Set via set_anchor_interval. |

JavaScript

import { SanaStreamingModel } from "@reactor-models/sana-streaming";

const sanaStreaming = new SanaStreamingModel();
sanaStreaming.onState((msg) => {
  console.log(
    "state",
    msg.seed,
    msg.paused,
    msg.running,
    msg.started,
    msg.has_prompt,
    msg.current_chunk,
    msg.current_prompt,
    msg.anchor_interval,
  );
});
await sanaStreaming.connect(jwt);

React

import { useSanaStreamingState } from "@reactor-models/sana-streaming";

// Inside a React component wrapped by <SanaStreamingProvider>:
useSanaStreamingState((msg) => {
  console.log(
    "state",
    msg.seed,
    msg.paused,
    msg.running,
    msg.started,
    msg.has_prompt,
    msg.current_chunk,
    msg.current_prompt,
    msg.anchor_interval,
  );
});

anchored

Emitted each time the edit re-grounds on the source video to pull back drift. Fires every anchor_interval chunks; the following chunk may show a brief visible refresh.

Listener: onAnchored · React hook: useSanaStreamingAnchored

| Field | Type | Description | |---|---|---| | chunk_index | number | Zero-based index of the first chunk generated after this re-ground. |

JavaScript

import { SanaStreamingModel } from "@reactor-models/sana-streaming";

const sanaStreaming = new SanaStreamingModel();
sanaStreaming.onAnchored((msg) => {
  console.log("anchored", msg.chunk_index);
});
await sanaStreaming.connect(jwt);

React

import { useSanaStreamingAnchored } from "@reactor-models/sana-streaming";

// Inside a React component wrapped by <SanaStreamingProvider>:
useSanaStreamingAnchored((msg) => {
  console.log("anchored", msg.chunk_index);
});

command_error

Emitted when a command is rejected; the session's prior state is unchanged.

Listener: onCommandError · React hook: useSanaStreamingCommandError

| Field | Type | Description | |---|---|---| | reason | string | Why the command was rejected. | | command | string | Name of the command that was rejected. |

JavaScript

import { SanaStreamingModel } from "@reactor-models/sana-streaming";

const sanaStreaming = new SanaStreamingModel();
sanaStreaming.onCommandError((msg) => {
  console.log("command_error", msg.reason, msg.command);
});
await sanaStreaming.connect(jwt);

React

import { useSanaStreamingCommandError } from "@reactor-models/sana-streaming";

// Inside a React component wrapped by <SanaStreamingProvider>:
useSanaStreamingCommandError((msg) => {
  console.log("command_error", msg.reason, msg.command);
});

chunk_complete

Emitted once per completed chunk of main_video.

Listener: onChunkComplete · React hook: useSanaStreamingChunkComplete

| Field | Type | Description | |---|---|---| | chunk_index | number | Zero-based index of the chunk that just completed. | | active_prompt | string | The prompt that drove this chunk; empty means the output stayed close to the source. | | frames_emitted | number | Edited pixel frames emitted by this chunk. |

JavaScript

import { SanaStreamingModel } from "@reactor-models/sana-streaming";

const sanaStreaming = new SanaStreamingModel();
sanaStreaming.onChunkComplete((msg) => {
  console.log(
    "chunk_complete",
    msg.chunk_index,
    msg.active_prompt,
    msg.frames_emitted,
  );
});
await sanaStreaming.connect(jwt);

React

import { useSanaStreamingChunkComplete } from "@reactor-models/sana-streaming";

// Inside a React component wrapped by <SanaStreamingProvider>:
useSanaStreamingChunkComplete((msg) => {
  console.log(
    "chunk_complete",
    msg.chunk_index,
    msg.active_prompt,
    msg.frames_emitted,
  );
});

prompt_accepted

Emitted when set_prompt accepts a new edit prompt.

Listener: onPromptAccepted · React hook: useSanaStreamingPromptAccepted

| Field | Type | Description | |---|---|---| | prompt | string | The prompt text that was accepted. |

JavaScript

import { SanaStreamingModel } from "@reactor-models/sana-streaming";

const sanaStreaming = new SanaStreamingModel();
sanaStreaming.onPromptAccepted((msg) => {
  console.log("prompt_accepted", msg.prompt);
});
await sanaStreaming.connect(jwt);

React

import { useSanaStreamingPromptAccepted } from "@reactor-models/sana-streaming";

// Inside a React component wrapped by <SanaStreamingProvider>:
useSanaStreamingPromptAccepted((msg) => {
  console.log("prompt_accepted", msg.prompt);
});

generation_reset

Emitted when reset aborts the run and clears the prompt and source video.

Listener: onGenerationReset · React hook: useSanaStreamingGenerationReset

| Field | Type | Description | |---|---|---| | reason | string | Short human-readable reason the reset was issued. |

JavaScript

import { SanaStreamingModel } from "@reactor-models/sana-streaming";

const sanaStreaming = new SanaStreamingModel();
sanaStreaming.onGenerationReset((msg) => {
  console.log("generation_reset", msg.reason);
});
await sanaStreaming.connect(jwt);

React

import { useSanaStreamingGenerationReset } from "@reactor-models/sana-streaming";

// Inside a React component wrapped by <SanaStreamingProvider>:
useSanaStreamingGenerationReset((msg) => {
  console.log("generation_reset", msg.reason);
});

generation_paused

Emitted when pause succeeds and frames stop until resume.

Listener: onGenerationPaused · React hook: useSanaStreamingGenerationPaused

| Field | Type | Description | |---|---|---| | chunk_index | number | Index of the last completed chunk before pausing. |

JavaScript

import { SanaStreamingModel } from "@reactor-models/sana-streaming";

const sanaStreaming = new SanaStreamingModel();
sanaStreaming.onGenerationPaused((msg) => {
  console.log("generation_paused", msg.chunk_index);
});
await sanaStreaming.connect(jwt);

React

import { useSanaStreamingGenerationPaused } from "@reactor-models/sana-streaming";

// Inside a React component wrapped by <SanaStreamingProvider>:
useSanaStreamingGenerationPaused((msg) => {
  console.log("generation_paused", msg.chunk_index);
});

generation_resumed

Emitted when resume succeeds and frames continue from where they stopped.

Listener: onGenerationResumed · React hook: useSanaStreamingGenerationResumed

| Field | Type | Description | |---|---|---| | chunk_index | number | Index of the last completed chunk before resuming. |

JavaScript

import { SanaStreamingModel } from "@reactor-models/sana-streaming";

const sanaStreaming = new SanaStreamingModel();
sanaStreaming.onGenerationResumed((msg) => {
  console.log("generation_resumed", msg.chunk_index);
});
await sanaStreaming.connect(jwt);

React

import { useSanaStreamingGenerationResumed } from "@reactor-models/sana-streaming";

// Inside a React component wrapped by <SanaStreamingProvider>:
useSanaStreamingGenerationResumed((msg) => {
  console.log("generation_resumed", msg.chunk_index);
});

generation_started

Emitted once when start succeeds and frames begin streaming.

Listener: onGenerationStarted · React hook: useSanaStreamingGenerationStarted

| Field | Type | Description | |---|---|---| | prompt | string | The prompt active at the start of generation (may be empty). | | chunk_num | number | Total chunks the run will produce (0 — not known up front). | | frame_num | number | Approximate total edited pixel frames (0 — not known up front). |

JavaScript

import { SanaStreamingModel } from "@reactor-models/sana-streaming";

const sanaStreaming = new SanaStreamingModel();
sanaStreaming.onGenerationStarted((msg) => {
  console.log(
    "generation_started",
    msg.prompt,
    msg.chunk_num,
    msg.frame_num,
  );
});
await sanaStreaming.connect(jwt);

React

import { useSanaStreamingGenerationStarted } from "@reactor-models/sana-streaming";

// Inside a React component wrapped by <SanaStreamingProvider>:
useSanaStreamingGenerationStarted((msg) => {
  console.log(
    "generation_started",
    msg.prompt,
    msg.chunk_num,
    msg.frame_num,
  );
});

generation_complete

Emitted when the run ends (via reset or disconnect).

Listener: onGenerationComplete · React hook: useSanaStreamingGenerationComplete

| Field | Type | Description | |---|---|---| | total_chunks | number | Total chunks produced by the run. |

JavaScript

import { SanaStreamingModel } from "@reactor-models/sana-streaming";

const sanaStreaming = new SanaStreamingModel();
sanaStreaming.onGenerationComplete((msg) => {
  console.log("generation_complete", msg.total_chunks);
});
await sanaStreaming.connect(jwt);

React

import { useSanaStreamingGenerationComplete } from "@reactor-models/sana-streaming";

// Inside a React component wrapped by <SanaStreamingProvider>:
useSanaStreamingGenerationComplete((msg) => {
  console.log("generation_complete", msg.total_chunks);
});

Tracks

Named media channels between your app and the SanaStreaming model. Use the typed helpers below — SanaStreamingModel.publish<Track> / on<Track> in plain JS, and useSanaStreamingTrack or the per-track <SanaStreaming<Track>View> components in React — so track names are checked at compile time.

camera

A video channel you push media into (for example, the user's webcam).

JavaScript

import { SanaStreamingModel } from "@reactor-models/sana-streaming";

const sanaStreaming = new SanaStreamingModel();
await sanaStreaming.connect(jwt);

const media = await navigator.mediaDevices.getUserMedia({ video: true });
const videoTrack = media.getVideoTracks()[0];
await sanaStreaming.publishCamera(videoTrack);

// later, to stop sending:
await sanaStreaming.unpublishCamera();

React

"use client";
import { SanaStreamingCameraView } from "@reactor-models/sana-streaming";

// Inside a component wrapped by <SanaStreamingProvider>:
export function Example() {
  return <SanaStreamingCameraView />;
}

main_video

A video channel you subscribe to — the model publishes this for your app to render.

JavaScript

import { SanaStreamingModel } from "@reactor-models/sana-streaming";

const sanaStreaming = new SanaStreamingModel();
sanaStreaming.onMainVideo((track, stream) => {
  // attach to a <video> element, pipe to a canvas, etc.
  videoEl.srcObject = stream;
});
await sanaStreaming.connect(jwt);

React

"use client";
import { SanaStreamingMainVideoView } from "@reactor-models/sana-streaming";

// Inside a component wrapped by <SanaStreamingProvider>:
export function Example() {
  return <SanaStreamingMainVideoView className="w-full aspect-video" />;
}