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

@vite-hub/workflow

v0.0.4

Published

Workflow primitives and Vite integration for ViteHub.

Readme

@vite-hub/workflow

@vite-hub/workflow discovers named long-running work and exposes one provider-neutral API for starting and inspecting runs.

Use a Workflow when the application needs a run id, durable state, retries, cancellation, or resumable work. Use Queue when background delivery is enough and the application does not need to inspect a run.

Install

pnpm add @vite-hub/workflow

Then install the dependency required by the selected provider:

| Provider | Additional dependency | Durable state | | -------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------ | | Cloudflare Workflows | None in application code | Cloudflare owns run state. | | Vercel Workflow | workflow and @workflow/builders for native durable definitions | Native definitions survive function restarts; plain handlers run inline. | | OpenWorkflow | openworkflow; add postgres when using Postgres | Requires explicit SQLite or Postgres storage. |

Importing the provider-neutral package root does not load OpenWorkflow types. Worker lifecycle helpers live at @vite-hub/workflow/runtime/openworkflow-worker.

Define a workflow

// server/workflows/onboard-user.ts
import { defineWorkflow } from "@vite-hub/workflow";

export default defineWorkflow<{ email: string }>(async ({ id, payload, provider }) => {
  const idempotencyKey = `onboard-user:${payload.email}`;
  const user = await upsertUser(payload.email, { idempotencyKey });
  await sendWelcomeEmail(user.email, { idempotencyKey: `${idempotencyKey}:welcome-email` });

  return { id, provider };
});

Both external operations receive keys derived from stable input. Preserve that idempotency when adding side effects so retrying a completed operation does not duplicate it.

ViteHub discovers server/workflows/<name>.ts, folder workflows such as server/workflows/onboard-user/index.ts, and src/<name>.workflow.ts. The file name becomes the name passed to runtime helpers.

Configure the provider

// server/workflow-runtime.ts
import type {
  ResolvedWorkflowOptions,
  WorkflowDefinition,
  WorkflowDefinitionRegistry,
} from "@vite-hub/workflow";
import {
  setWorkflowRuntimeConfig,
  setWorkflowRuntimeRegistry,
} from "@vite-hub/workflow/runtime/state";
const postgresUrl = {
  kind: "env-variable",
  source: { kind: "env", name: "OPENWORKFLOW_POSTGRES_URL" },
} as const;

export const workflowConfig = {
  provider: "openworkflow",
  postgres: { url: postgresUrl },
} satisfies ResolvedWorkflowOptions;

const workflowRegistry = {
  "onboard-user": async () => ({
    default: (await import("./workflows/onboard-user.ts")).default as WorkflowDefinition,
  }),
} satisfies WorkflowDefinitionRegistry;

export function installWorkflowRuntime() {
  const url = process.env.OPENWORKFLOW_POSTGRES_URL;
  if (!url) throw new Error("OPENWORKFLOW_POSTGRES_URL is required");

  setWorkflowRuntimeConfig({
    ...workflowConfig,
    postgres: { ...workflowConfig.postgres, url },
  });
  setWorkflowRuntimeRegistry(workflowRegistry);
}
// vite.config.ts
import { hubWorkflow } from "@vite-hub/workflow/vite";
import { defineConfig } from "vite";
import { workflowConfig } from "./server/workflow-runtime.ts";

export default defineConfig({
  plugins: [hubWorkflow()],
  workflow: workflowConfig,
});

The Vite config, application process, and worker below share one provider config. The runtime installer also registers the same definitions in both processes. Set provider explicitly when the deployment target should not decide it. Otherwise ViteHub selects Cloudflare on Cloudflare hosting and Vercel on other supported hosts. Node and Docker select OpenWorkflow when its storage is configured. Netlify cannot infer a provider.

OpenWorkflow accepts one storage choice: postgres.url or sqlite.path. Hosted credentials belong in Server Env, not source code.

Run an OpenWorkflow worker

runWorkflow() only enqueues an OpenWorkflow run. Start one worker in a long-lived Node or Docker process:

// worker.ts
import { startOpenWorkflowWorker } from "@vite-hub/workflow/runtime/openworkflow-worker";
import { installWorkflowRuntime } from "./server/workflow-runtime.ts";

installWorkflowRuntime();
const worker = await startOpenWorkflowWorker();

Keep this process running while it should consume queued runs, and do not start a worker per request. startOpenWorkflowWorker() installs SIGINT and SIGTERM handlers. If the host owns shutdown, await worker.stop() from its shutdown hook.

Start and inspect a run

// server/api/onboard.post.ts
import { getWorkflowRun, runWorkflow } from "@vite-hub/workflow";
import { defineEventHandler, readBody } from "h3";
import { installWorkflowRuntime } from "../workflow-runtime.ts";

installWorkflowRuntime();

export default defineEventHandler(async (event) => {
  const payload = await readBody<{ email: string }>(event);
  const started = await runWorkflow("onboard-user", payload);

  return await getWorkflowRun("onboard-user", started.id);
});

runWorkflow() returns a normalized acknowledgement with an id, provider, and status. getWorkflowRun() returns normalized run and step state, including timestamps, attempts, results, and failures when the provider supplies them.

The other runtime helpers are:

  • deferWorkflow() starts through the deferred provider path when available.
  • cancelWorkflow() cancels a native Vercel run.
  • resumeWorkflowSignal() resumes a registered Vercel Workflow DevKit hook token.
  • createWorkflow() creates a named handle with run, defer, getRun, and cancel methods.

Unsupported provider operations fail with WORKFLOW_OPERATION_UNSUPPORTED; ViteHub does not pretend that an inline run was cancelled or resumed.

Make a Vercel workflow durable

A plain Vercel definition executes inline and does not survive a function restart. For durable execution, keep the same context-shaped handler and register a native Workflow DevKit entry:

pnpm add workflow @workflow/builders
import { defineWorkflow, type WorkflowExecutionContext } from "@vite-hub/workflow";

interface OnboardPayload {
  email: string;
}

async function upsertUserStep(email: string) {
  "use step";

  return await upsertUser(email, { idempotencyKey: `onboard-user:${email}` });
}

async function durableOnboard({ payload }: WorkflowExecutionContext<OnboardPayload>) {
  "use workflow";

  const user = await upsertUserStep(payload.email);
  return { userId: user.id };
}

async function inlineOnboard({ payload }: WorkflowExecutionContext<OnboardPayload>) {
  const user = await upsertUser(payload.email, { idempotencyKey: `onboard-user:${payload.email}` });
  return { userId: user.id };
}

export default defineWorkflow(inlineOnboard, { native: durableOnboard });

ViteHub transforms native when it generates Vercel output. Other providers keep using the normal handler. Put external side effects in idempotent use step functions because a durable step may be retried.

Handle stable failures

Throw ViteHubError when app callers need a stable, inspectable Workflow failure instead of parsing log output or provider-specific messages. ViteHub-owned failures use the package's fixed WorkflowErrorCode vocabulary.

import { ViteHubError } from "@vite-hub/runtime";

async function transcribe(recordingId: string) {
  try {
    return await transcribeRecording(recordingId);
  } catch (cause) {
    throw new ViteHubError("TRANSCRIPTION_FAILED", "Transcription failed.", {
      cause,
      details: { recordingId },
    });
  }
}

code and message remain available through error.toJSON(). Keep details JSON-safe and free of secrets; toJSON() omits cause, which remains available only on the in-memory error. ViteHub's built-in codes are typed as WorkflowErrorCode and use code-derived messages and code-specific details. Workflow Step retry behavior belongs in the Step's retry options rather than the error.

Production checklist

  • Confirm that the selected provider is durable enough for the work; inline Vercel handlers are not durable.
  • Make side-effecting steps idempotent so retries do not duplicate work.
  • Keep provider credentials and database URLs in Server Env.
  • Configure durable OpenWorkflow storage and run its migrations before serving traffic.
  • Verify start, inspection, failure, retry, cancellation, timeout, and restart behavior on the deployment host.

Documentation