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

@xano-sdk/workflows

v0.1.0

Published

Business processes on a record's status column, enforced by the server for the REST API, MCP agents and the in-app assistant alike: states and transitions with per-transition permissions, required fields, approval-gated moves and history; editable and ver

Readme

@xano-sdk/workflows

A business process on a record's status column, for a Xano SDK workspace, that the server enforces the same way for your REST API, MCP agents and the in-app assistant:

  • states and the moves between them;
  • who may make each move (your @xano-sdk/rbac permissions);
  • fields that must be filled in first;
  • moves that wait for someone to approve;
  • a history of every move: who, through what (the app, an agent, the assistant), from, to, and why.

The definition lives in your code as the default, and in the database as versions. Admins change it in the app (a node-canvas editor), publish a new version, and the server checks every version with the same rules your build checks the code with. A state that still has records can't be removed until you say where they go.

Plus an optional React frontend, @xano-sdk/workflows/react: <WorkflowEditor>, <WorkflowActions>, <WorkflowHistory> and <WorkflowBadge>.

npm install @xano/sdk @xano-sdk/rbac @xano-sdk/workflows
# the editor also needs:
npm install @xyflow/react elkjs

Use it

// xano/workflow.ts
import { c, col, expr, or, ref } from "@xano/sdk";
import { defineWorkflow } from "@xano-sdk/workflows";
import { note } from "./table/note.js";       // status: f.enum(["draft", "review", "published"])
import { user } from "./table/user.js";
import { rbac } from "./rbac.js";             // "notes.publish": ["admin"], "team.manage": ["admin"], …

export const notesFlow = defineWorkflow({
  name: "notes", table: note, field: "status", user, noun: "note",
  states: [
    { key: "draft", label: "Draft" },
    { key: "review", label: "In review", tone: "amber" },
    { key: "published", label: "Published", tone: "green", terminal: true },
  ],
  transitions: [
    { from: "draft", to: "review", label: "Send for review", requires: ["body"] },
    { from: "review", to: "draft", label: "Back to draft", permission: "notes.write" },
    { from: "review", to: "published", label: "Publish", approval: true },
  ],
  permissions: { rbac, manage: "team.manage", approve: "notes.publish" },
  // Who sees a record (and so may move it): owners, and people who read everything.
  visibility: {
    record: or(expr(ref("record.owner_id"), "=", ref("actor.id")), rbac.has(ref("actor.role"), "notes.read_all")),
    where: or(expr(col("owner_id"), "=", ref("actor.id")), rbac.has(ref("actor.role"), "notes.read_all")),
  },
  assistant: true,                                   // build the in-app assistant's tools too
  onChange: ({ recordId }) => [changed("note", recordId)],   // your realtime publish, if you have one
});
// xano/index.ts
notesFlow.register(app);   // tables, functions, endpoints, assistant tools, generated tests

defineWorkflow throws when the app builds if the definition is wrong: a move to a state that doesn't exist, a state nothing leads to, a move out of a final state, a permission or field that doesn't exist, approval with nobody to approve, or an enum column whose values aren't exactly the state keys.

The rules every move follows

All moves go through one function, workflow_<name>/move(record_id, to, actor_id, note?). Under a lock on the record it checks, against the live version:

  1. the person can see the record (visibility);
  2. a move from the record's current state to to exists;
  3. the person holds the move's permission ("Only admins can do this.");
  4. the move's required fields are filled ("Fill in Summary first.");
  5. if the move needs approval, it records a waiting request instead of moving. People who approve skip approval for their own moves in the app; an agent's or the assistant's move always waits, and the person it works for may approve it if they can approve.

Every outcome writes a history event. Approving re-checks the move: if the record moved on or the workflow changed since, the request goes stale and nothing moves.

Endpoints

All in the workflow_<name> API group, signed in with your user table. Logic lives in functions that take the person's id; the endpoints pass auth("id").

| Route | What | |---|---| | GET <name>/info | The contract, the live version, can_edit, can_approve | | GET <name>/definition | Live definition, shared draft, the editor's facts | | POST <name>/draft · DELETE <name>/draft | Save (with problems listed) or discard the draft | | POST <name>/publish | { spec, base, note?, migrations? } → the next version | | GET <name>/versions · GET <name>/versions/{number} | Published versions; one's definition (0 = code default) | | GET <name>/records/{record_id}/transitions | The moves this person could make, each allowed or with the reason | | POST <name>/records/{record_id}/move | { to, note? } → moved or pending | | GET <name>/records/{record_id}/history | Every move, newest first | | GET <name>/summary | How many records are in each state (only those the person can see) | | GET <name>/requests?view=to_decide\|mine | Waiting moves | | POST <name>/requests/{request_id}/decide | { decision: approve\|decline\|withdraw, note? } |

MCP agents and the assistant

// with @xano-sdk/agents: the same functions, marked as the agent's
export const workflowTools = notesFlow.agentTools().map((spec) => agent.tool(spec));
agent.serve({ tools: [...workflowTools, /* … */] });

// with @xano-sdk/chatbot (assistant: true)
registerChatbot(app, { authTable: user, tools: notesFlow.assistantTools, /* … */ });

Add WORKFLOW_PROMPT_RULE to the assistant's or server's instructions so the model says a move is waiting for approval instead of claiming it happened.

Approvals: why this package keeps its own

@xano-sdk/agents approvals are for an agent's action that a person OKs (source mcp or assistant, decided by the person or by role). A workflow's approval is different: anyone, a person in the app included, can ask; it's decided by a permission; and approving must re-check the move against the live definition and the record's state at that moment. So the waiting moves live in <name>_workflow_request, decided through <name>/requests/{id}/decide (and <WorkflowActions>). An app that wants one inbox lists both: GET approvals and GET <name>/requests.

The frontend (optional)

import "@xyflow/react/dist/style.css";   // once, for the editor's canvas
import { WorkflowProvider, WorkflowEditor, WorkflowActions, WorkflowHistory, WorkflowBadge, createWorkflowClient } from "@xano-sdk/workflows/react";

const notesFlow = createWorkflowClient({ apiBaseUrl: `${window.XANO_HOST}/api:notes-flow`, name: "notes", getToken: () => session.token });

<WorkflowProvider client={notesFlow} onRecordChanged={() => refetchNotes()}>
  <WorkflowBadge state={note.status} />          {/* labelled and coloured by the live definition */}
  <WorkflowActions record={note.id} />           {/* the moves, locked ones explained, approvals inline */}
  <WorkflowHistory record={note.id} />           {/* the timeline */}
  <WorkflowEditor noun="note" />                 {/* admins edit and publish; others view, read-only */}
</WorkflowProvider>
  • Editor: drag from a state's right edge to another state to add a move; select anything to edit it in the side panel (label, who can, required fields, approval; a state's name, colour, start and final). Problems are listed as you type, by the same rules the server runs; click one to jump to it. The draft autosaves and is shared; publishing asks where records in removed states go. Undo/redo, versions ("Use as draft"), "Tidy up" (elkjs layout), full keyboard (? lists the shortcuts), dark mode from a dark class on <html>, and a phone layout.
  • It's styled with shadcn/ui theme tokens, so it takes your theme. With Tailwind v4, let it see the classes: @source "../node_modules/@xano-sdk/workflows/dist";.
  • GET <name>/definition returns contract; a mismatch shows an update notice instead of guessing.

Tests

npm test (types, the factory's refusals, the shared rules, the editor model, the encoded bundle, the React parts in happy-dom) and npm run test:live, which deploys the fixture to a local Xano engine and runs the module's Xano tests: the server's validator against the TypeScript rules case by case, and workflow tests for every rule above. Pass tests: { row, requires?, owner? } to defineWorkflow and your app gets the same generated tests against its own table.

MIT