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/vidu-s2-editing

v0.3.0

Published

Strongly-typed SDK for the ViduS2Editing model on Reactor

Readme

@reactor-models/vidu-s2-editing

Typed JavaScript + React SDK for the ViduS2Editing model on Reactor. Version v0.3.0.


Get started

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

npx create-reactor-app my-app --model=vidu-s2-editing
pnpm dlx create-reactor-app my-app --model=vidu-s2-editing

Install

npm install @reactor-models/vidu-s2-editing
pnpm add @reactor-models/vidu-s2-editing

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

import { ViduS2EditingModel } from "@reactor-models/vidu-s2-editing";
import { ViduS2EditingProvider, useViduS2Editing } from "@reactor-models/vidu-s2-editing";

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 { ViduS2EditingProvider } from "@reactor-models/vidu-s2-editing";
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 (
    <ViduS2EditingProvider jwtToken={token} connectOptions={{ autoConnect: true }}>
      <ReactorView className="w-full aspect-video" />
    </ViduS2EditingProvider>
  );
}

Connect

JavaScript

import { ViduS2EditingModel } from "@reactor-models/vidu-s2-editing";

const viduS2Editing = new ViduS2EditingModel();
await viduS2Editing.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 { ViduS2EditingProvider, useViduS2Editing } from "@reactor-models/vidu-s2-editing";

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 } = useViduS2Editing();
  return <span>Status: {status}</span>;
}

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

Events

Client-to-model commands. The typed surface is ViduS2EditingModel (one method per event) in plain JS, and useViduS2Editing() 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).

endEdit

End the edit. Valid from starting through live. Replies edit_ended once everything is released, about 5 s later, after session_state broadcasts ending then ended; start_edit works again. Emits command_error with NOT_LIVE when no edit is active.

Returns: edit_ended — { type: "edit_ended", end_reason: "", duration_seconds: 0 } (or undefined when the send fails).

No parameters.

JavaScript

import { ViduS2EditingModel } from "@reactor-models/vidu-s2-editing";

const viduS2Editing = new ViduS2EditingModel();
await viduS2Editing.connect(jwt);

const reply = await viduS2Editing.endEdit();

if (reply) {
  console.log("edit_ended", reply.end_reason, reply.duration_seconds);
}

React

"use client";
import { useViduS2Editing } from "@reactor-models/vidu-s2-editing";

function Example() {
  const { endEdit } = useViduS2Editing();

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

getState

Return the authoritative snapshot. Valid any time. Replies session_state; never fails.

Returns: session_state — { type: "session_state", phase: "", end_reason: null, last_error: null, editing_type: null, control_ready: false, reference_set: false, edit_started_at: null, video_receiving: false, edit_max_seconds: null, camera_forwarding: false, last_frame_age_ms: null, edit_elapsed_seconds: null } (or undefined when the send fails).

No parameters.

JavaScript

import { ViduS2EditingModel } from "@reactor-models/vidu-s2-editing";

const viduS2Editing = new ViduS2EditingModel();
await viduS2Editing.connect(jwt);

const reply = await viduS2Editing.getState();

if (reply) {
  console.log(
    "session_state",
    reply.phase,
    reply.end_reason,
    reply.last_error,
    reply.editing_type,
    reply.control_ready,
    reply.reference_set,
    reply.edit_started_at,
    reply.video_receiving,
    reply.edit_max_seconds,
    reply.camera_forwarding,
    reply.last_frame_age_ms,
    reply.edit_elapsed_seconds,
  );
}

React

"use client";
import { useViduS2Editing } from "@reactor-models/vidu-s2-editing";

function Example() {
  const { getState } = useViduS2Editing();

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

startEdit

Start editing your camera track with one reference image and a scenario. Valid in idle, ended, and failed. Returns nothing; session_state broadcasts walk starting, warming_up, then live, about 4 s in all, and edited video appears on main_video a few seconds after camera_forwarding turns true. Emits command_error with BUSY while an edit is active, and INVALID_INPUT when the reference is missing, given twice, not an image, over 10 MB, or not an http(s) URL. A failure while starting moves the phase to failed: UPSTREAM_REJECTED when the service cannot use the image, another UPSTREAM_* code, WARMUP_TIMEOUT, MEDIA_JOIN_FAILED, or a passed-through service code.

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

| Parameter | Type | Description | |---|---|---| | editing_type | "style_transfer" \| "virtual_tryon" \| "subject_replacement" \| "background_replacement" | What the image supplies: style_transfer (the look of the whole scene, the default), virtual_tryon (a garment you wear), subject_replacement (a character that replaces you), or background_replacement (the scene behind you). Change it later with switch_reference. (default null) | | reference_image | FileRef \| null | The reference image, as an upload: PNG, JPG, or WEBP, under 10 MB. Give this or reference_image_url, not both. (default null) | | reference_image_url | string \| null | A public http(s) URL of the reference image, instead of reference_image. The editor fetches it when the edit starts. (maxLength 2048, default null) |

JavaScript

import { ViduS2EditingModel } from "@reactor-models/vidu-s2-editing";

const viduS2Editing = new ViduS2EditingModel();
await viduS2Editing.connect(jwt);

const fileRef = await viduS2Editing.uploadFile(blob);
await viduS2Editing.startEdit({ reference_image: fileRef, editing_type: "style_transfer", reference_image_url: null });

React

"use client";
import { useViduS2Editing } from "@reactor-models/vidu-s2-editing";

function Example() {
  const { startEdit, uploadFile } = useViduS2Editing();

  async function handlePick(file: File) {
    const ref = await uploadFile(file);
    await startEdit({ reference_image: ref, editing_type: "style_transfer", reference_image_url: null });
  }

  return <input type="file" onChange={(e) => handlePick(e.target.files![0])} />;
}

switchReference

Change the reference image, the scenario, or both, without restarting the edit; what you omit stays as it is. Valid while live. Replies reference_switched once the switch is sent, and the picture changes within a few seconds. There is no later confirmation: if the editor cannot apply the switch, a command_error for switch_reference follows about 4 s later with a passed-through SWITCH_PROMPT_*, RENDER_*, or LIVE_NOT_ACTIVE code, the previous reference stays in effect, and the edit keeps running. Each call switches exactly once; do not resend on silence. Emits command_error with NOT_LIVE outside live, and INVALID_INPUT when nothing to change is given or the new reference fails the same checks as in start_edit.

Returns: reference_switched — { type: "reference_switched", editing_type: "", reference_changed: true } (or undefined when the send fails).

| Parameter | Type | Description | |---|---|---| | editing_type | "style_transfer" \| "virtual_tryon" \| "subject_replacement" \| "background_replacement" | A new scenario, with the same values as in start_edit. Omit to keep the current one; session_state.editing_type shows the one in effect. (default null) | | reference_image | FileRef \| null | A new reference image, as an upload: PNG, JPG, or WEBP, under 10 MB. Give this or reference_image_url, not both; omit both to keep the current image. (default null) | | reference_image_url | string \| null | A public http(s) URL of a new reference image, instead of reference_image. (maxLength 2048, default null) |

JavaScript

import { ViduS2EditingModel } from "@reactor-models/vidu-s2-editing";

const viduS2Editing = new ViduS2EditingModel();
await viduS2Editing.connect(jwt);

const fileRef = await viduS2Editing.uploadFile(blob);
const reply = await viduS2Editing.switchReference({ reference_image: fileRef, editing_type: "style_transfer", reference_image_url: null });

if (reply) {
  console.log("reference_switched", reply.editing_type, reply.reference_changed);
}

React

"use client";
import { useViduS2Editing } from "@reactor-models/vidu-s2-editing";

function Example() {
  const { switchReference, uploadFile } = useViduS2Editing();

  async function handlePick(file: File) {
    const ref = await uploadFile(file);
    await switchReference({ reference_image: ref, editing_type: "style_transfer", reference_image_url: null });
  }

  return <input type="file" onChange={(e) => handlePick(e.target.files![0])} />;
}

Messages

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

edit_ended

Emitted as the reply to end_edit once the edit is released.

Listener: onEditEnded · React hook: useViduS2EditingEditEnded

| Field | Type | Description | |---|---|---| | end_reason | string | Same values as session_state.end_reason. | | duration_seconds | number | Seconds the edit was live, 0 if it never was. |

JavaScript

import { ViduS2EditingModel } from "@reactor-models/vidu-s2-editing";

const viduS2Editing = new ViduS2EditingModel();
viduS2Editing.onEditEnded((msg) => {
  console.log("edit_ended", msg.end_reason, msg.duration_seconds);
});
await viduS2Editing.connect(jwt);

React

import { useViduS2EditingEditEnded } from "@reactor-models/vidu-s2-editing";

// Inside a React component wrapped by <ViduS2EditingProvider>:
useViduS2EditingEditEnded((msg) => {
  console.log("edit_ended", msg.end_reason, msg.duration_seconds);
});

command_error

Emitted when a command is rejected or an edit fails; the command had no effect.

Broadcast, and stored as session_state.last_error. origin says whose fault it is; code is stable and safe to branch on.

Listener: onCommandError · React hook: useViduS2EditingCommandError

| Field | Type | Description | |---|---|---| | code | string | Stable code, e.g. NOT_LIVE, UPSTREAM_CAPACITY. | | origin | string | request (the input was invalid), state (not allowed in this phase), upstream (the generation service refused or failed), or platform (a failure on our side). | | reason | string | A readable sentence. | | command | string | The command that failed, or session when nothing the client sent caused it. | | trace_id | string \| null | Correlation id to quote when reporting the failure. (default null) | | retryable | boolean | Whether the same command may succeed if sent again later. (default false) | | upstream_code | string \| null | Error code from the generation service, when it gave one. (default null) | | upstream_status | number \| null | HTTP status from the generation service, when origin is upstream. (default null) |

JavaScript

import { ViduS2EditingModel } from "@reactor-models/vidu-s2-editing";

const viduS2Editing = new ViduS2EditingModel();
viduS2Editing.onCommandError((msg) => {
  console.log(
    "command_error",
    msg.code,
    msg.origin,
    msg.reason,
    msg.command,
    msg.trace_id,
    msg.retryable,
    msg.upstream_code,
    msg.upstream_status,
  );
});
await viduS2Editing.connect(jwt);

React

import { useViduS2EditingCommandError } from "@reactor-models/vidu-s2-editing";

// Inside a React component wrapped by <ViduS2EditingProvider>:
useViduS2EditingCommandError((msg) => {
  console.log(
    "command_error",
    msg.code,
    msg.origin,
    msg.reason,
    msg.command,
    msg.trace_id,
    msg.retryable,
    msg.upstream_code,
    msg.upstream_status,
  );
});

session_state

Emitted on connect, whenever anything in it changes, and as the reply to get_state: the authoritative snapshot.

Broadcast to every client. Mirror it; derive nothing from other messages.

Listener: onSessionState · React hook: useViduS2EditingSessionState

| Field | Type | Description | |---|---|---| | phase | string | Where the session is: idle, starting, warming_up, live, ending, ended, or failed. start_edit works from idle, ended, and failed. | | end_reason | string \| null | Why the last edit ended: ended_by_client, max_duration, content_policy, upstream_quota, upstream_interrupted, media_lost, or bridge_failed. Null while an edit runs. (default null) | | last_error | Record<string, unknown> \| null | The most recent command_error payload, or null. Cleared when an edit starts. (default null) | | editing_type | string \| null | The scenario in effect: style_transfer, virtual_tryon, subject_replacement, or background_replacement. Null before the first start_edit. (default null) | | control_ready | boolean | switch_reference is accepted. (default false) | | reference_set | boolean | Whether a reference image is in effect for the current edit. (default false) | | edit_started_at | number \| null | Unix time, in seconds, at which the edit became live. (default null) | | video_receiving | boolean | Edited video is arriving on main_video. (default false) | | edit_max_seconds | number \| null | Longest the edit may run before it ends on its own. (default null) | | camera_forwarding | boolean | Your camera track is reaching the editor. Nothing is edited until it is. (default false) | | last_frame_age_ms | number \| null | Milliseconds since the last edited frame. Grows when video stalls. (default null) | | edit_elapsed_seconds | number \| null | Seconds since the edit became live. (default null) |

JavaScript

import { ViduS2EditingModel } from "@reactor-models/vidu-s2-editing";

const viduS2Editing = new ViduS2EditingModel();
viduS2Editing.onSessionState((msg) => {
  console.log(
    "session_state",
    msg.phase,
    msg.end_reason,
    msg.last_error,
    msg.editing_type,
    msg.control_ready,
    msg.reference_set,
    msg.edit_started_at,
    msg.video_receiving,
    msg.edit_max_seconds,
    msg.camera_forwarding,
    msg.last_frame_age_ms,
    msg.edit_elapsed_seconds,
  );
});
await viduS2Editing.connect(jwt);

React

import { useViduS2EditingSessionState } from "@reactor-models/vidu-s2-editing";

// Inside a React component wrapped by <ViduS2EditingProvider>:
useViduS2EditingSessionState((msg) => {
  console.log(
    "session_state",
    msg.phase,
    msg.end_reason,
    msg.last_error,
    msg.editing_type,
    msg.control_ready,
    msg.reference_set,
    msg.edit_started_at,
    msg.video_receiving,
    msg.edit_max_seconds,
    msg.camera_forwarding,
    msg.last_frame_age_ms,
    msg.edit_elapsed_seconds,
  );
});

reference_switched

Emitted as the reply to switch_reference once the switch is sent to the editor.

The picture changes within a few seconds. A switch the editor cannot apply arrives afterwards, about 4 s later, as a command_error for switch_reference, and the previous reference and scenario stay in effect.

Listener: onReferenceSwitched · React hook: useViduS2EditingReferenceSwitched

| Field | Type | Description | |---|---|---| | editing_type | string | The scenario now requested. | | reference_changed | boolean | Whether a new reference image was sent. |

JavaScript

import { ViduS2EditingModel } from "@reactor-models/vidu-s2-editing";

const viduS2Editing = new ViduS2EditingModel();
viduS2Editing.onReferenceSwitched((msg) => {
  console.log("reference_switched", msg.editing_type, msg.reference_changed);
});
await viduS2Editing.connect(jwt);

React

import { useViduS2EditingReferenceSwitched } from "@reactor-models/vidu-s2-editing";

// Inside a React component wrapped by <ViduS2EditingProvider>:
useViduS2EditingReferenceSwitched((msg) => {
  console.log("reference_switched", msg.editing_type, msg.reference_changed);
});

Tracks

Named media channels between your app and the ViduS2Editing model. Use the typed helpers below — ViduS2EditingModel.publish<Track> / on<Track> in plain JS, and useViduS2EditingTrack or the per-track <ViduS2Editing<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 { ViduS2EditingModel } from "@reactor-models/vidu-s2-editing";

const viduS2Editing = new ViduS2EditingModel();
await viduS2Editing.connect(jwt);

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

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

React

"use client";
import { ViduS2EditingCameraView } from "@reactor-models/vidu-s2-editing";

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

main_video

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

JavaScript

import { ViduS2EditingModel } from "@reactor-models/vidu-s2-editing";

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

React

"use client";
import { ViduS2EditingMainVideoView } from "@reactor-models/vidu-s2-editing";

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