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

@stateway/frontend-sdk

v0.4.0

Published

Stateway Frontend Gateway SDK — WebSocket client for browser frontends

Readme

@stateway/frontend-sdk

JavaScript/TypeScript client for Stateway — connect your frontend to BPMN process workflows in real-time.

Install

npm install @stateway/frontend-sdk

Zero runtime dependencies. Works natively in all modern browsers; pass webSocketImpl for Node.js.

Quick start

import { StatewayClient } from '@stateway/frontend-sdk';

const sw = new StatewayClient({
  endpoint: 'wss://ws.stateway.io',
  sessionToken: 'sws_live_...',
});

sw.onTaskFlow({
  onTask: (task) => renderForm(task),
  onComplete: () => showSuccess(),
});

That's it. onTaskFlow handles the entire lifecycle: receiving tasks, tracking process progress, and detecting completion.

The work.* event lifecycle

When a session connects, the server immediately sends a work state event. There are three states:

  connect
     │
     ▼
 work.loading          — server is computing the user's work state
     │
     ├─► work.snapshot  — user has pending tasks (WorkItem[]) + their open instances
     │
     └─► work.empty     — user has no pending tasks + their open instances

work.loading — emitted right after connect. Show a loading indicator.

work.snapshot — the user has at least one pending task. The event delivers:

  • tasks: WorkItem[] — the list of tasks waiting for the user
  • instances: InstanceSummary[] — all process instances associated with the user (including those without a pending task)

work.empty — the user has no pending tasks. The event delivers:

  • definitions: Array<{ key, canStart }> — which processes the user can start
  • instances: InstanceSummary[] — all process instances still running for the user

instances is the key to answering "does this user have open process instances right now?" — even when they have no active task. For example, a support ticket that is awaiting an agent reply still appears in instances.

Use instances.length === 0 (not the absence of tasks) to determine whether a process is truly complete from the user's perspective.

Events

For lower-level control, subscribe to events directly:

sw.on('connected', () => console.log('ready'));

sw.on('work.loading', () => {
  showSpinner();
});

sw.on('work.snapshot', (tasks, instances) => {
  // tasks: WorkItem[] — pending tasks for this user
  // instances: InstanceSummary[] — all open process instances
  renderTaskList(tasks);
  renderInstanceList(instances);
});

sw.on('work.empty', ({ definitions, instances }) => {
  // instances: InstanceSummary[] — running instances with no pending task for the user
  if (instances.length > 0) {
    showWaitingState(instances);   // e.g. "Your ticket is with the support team"
  } else {
    showNoWorkState(definitions);  // e.g. "No open requests. Start a new one?"
  }
});

sw.on('task.assigned', (task) => {
  // a new task was assigned to the current user
  console.log(task.name, task.variables);
});

sw.on('task.completed', ({ next }) => {
  // task was completed; process is now at `next.elementName`
});

sw.on('notification', ({ message, variables }) => {
  // process sent a notification message
});

sw.on('error', (err) => {
  console.error(err.code, err.message);
});

Actions

// start a new process instance
const { instanceId } = await sw.startProcess('loan-approval', {
  amount: 50000,
  applicant: '[email protected]',
});

// claim a task (marks it as in-progress by the current user)
await sw.claimTask(task.taskId);

// complete a task with output variables
await sw.completeTask(task.taskId, { approved: true, comment: 'looks good' });

// release a claimed task back to the pool
await sw.unclaimTask(task.taskId);

// send an event to a running instance (e.g. trigger a boundary event)
await sw.sendEvent(instanceId, 'payment_received', { amount: 50000 });

// evaluate a DMN decision table
const result = await sw.evaluateDecision('credit-score', { income: 80000 });

// rotate the session token without reconnecting
await sw.rotateToken('sws_live_newtoken...');

// close the connection
sw.disconnect();

onTaskFlow helper

onTaskFlow is a high-level helper that maps the full task lifecycle to simple callbacks. It returns a cleanup function.

const cleanup = sw.onTaskFlow({
  onTask(task) {
    // called when a task is available (snapshot or newly assigned)
    setCurrentTask(task);
  },
  onProcessing(elementName) {
    // called after task completion while process executes automatic steps
    setStatus(`Processing: ${elementName}...`);
  },
  onNotification(message, variables) {
    // called when the process sends a notification
    showToast(message);
  },
  onComplete() {
    // called when there are no more open process instances (instances.length === 0)
    setStatus('Done');
  },
});

// later, to stop listening:
cleanup();

Error handling

Actions reject with a StatewayError on failure. Check err.code for the error type:

try {
  await sw.claimTask(taskId);
} catch (err) {
  if (err.code === 'CLAIM_CONFLICT') {
    alert('Someone else claimed this task');
  }
}

| Code | Meaning | |---|---| | CLAIM_CONFLICT | Task was already claimed by another user | | CLAIM_REQUIRED | Task must be claimed before completion | | FORBIDDEN | Session does not have permission for this action | | TASK_NOT_FOUND | Task ID does not exist or belongs to another tenant | | SCOPE_VIOLATION | Action not allowed at the current process state | | SESSION_EXPIRED | Session token has expired — request a new one | | CONNECTION_LOST | Request was pending when the connection dropped | | TIMEOUT | Server did not respond within 30 seconds |

Session lifecycle

sw.on('session.expiring', ({ expiresIn }) => {
  // expiresIn: seconds until expiry — fetch a new token
  fetchNewToken().then((t) => sw.rotateToken(t));
});

sw.on('session.superseded', () => {
  // another tab/device opened the same session — this connection is closed
  showReconnectPrompt();
});

The client reconnects automatically after network interruptions (exponential backoff, up to 30 s). It does not reconnect after session.superseded or an explicit disconnect().

TypeScript

All events and action return types are fully inferred — no casts needed:

sw.on('work.snapshot', (tasks, instances) => {
  // tasks: WorkItem[]
  // instances: InstanceSummary[]
  tasks[0].taskId;                  // string
  tasks[0].variables;               // Record<string, unknown>
  instances[0].instanceId;          // string
  instances[0].status;              // 'running' | 'suspended'
});

Exported types: WorkItem, InstanceSummary, WorkEmptyState, TaskCompletedEvent, NotificationEvent, StatewayError, StatewayClientOptions, EventMap, ErrorCode.

Node.js

WebSocket is not available globally in Node.js 20. Inject the ws package:

import { WebSocket } from 'ws';
import { StatewayClient } from '@stateway/frontend-sdk';

const sw = new StatewayClient({
  endpoint: 'wss://ws.stateway.io',
  sessionToken: 'sws_live_...',
  webSocketImpl: WebSocket as unknown as typeof globalThis.WebSocket,
});

Issues

Found a bug or have a question? Open an issue at github.com/stateway-io/stateway-issues.

License

MIT