@apipass-dev/workflow-canvas
v0.1.0
Published
Apipass-first workflow canvas components, nodes, and runtime powered by @xyflow/react and @apipass-dev/apipass-sdk.
Maintainers
Readme
@apipass-dev/workflow-canvas
Apipass-first workflow canvas components, nodes, and runtime powered by
@xyflow/react and @apipass-dev/apipass-sdk.
This package is intentionally not a generic React Flow wrapper. It provides a reusable workflow canvas plus built-in Apipass generation nodes so teams can build node-based AI products while keeping @apipass-dev/apipass-sdk visible in the integration.
Install
npm install @apipass-dev/apipass-sdk @apipass-dev/workflow-canvasYour app also needs React. @xyflow/react is an internal dependency of this package and does not need to be installed directly by the app.
The package stylesheet includes React Flow base CSS plus the Apipass node and canvas styles.
Basic Usage
"use client";
import {
ApipassWorkflowProvider,
WorkflowCanvas,
createAdapterWorkflowRuntime,
defaultApipassEdgeTypes,
defaultApipassNodeTypes,
type ApipassWorkflowRuntimeAdapter,
} from "@apipass-dev/workflow-canvas";
import "@apipass-dev/workflow-canvas/style.css";
const adapter: ApipassWorkflowRuntimeAdapter = {
async generateImage(input) {
const response = await fetch("/api/workflows/run-node", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
kind: input.kind,
nodeId: input.nodeId,
nodeType: input.node.type,
prompt: input.prompt,
request: input.request,
}),
signal: input.signal,
});
if (!response.ok) throw new Error("Workflow API route failed.");
return response.json();
},
};
const runtime = createAdapterWorkflowRuntime({ adapter });
export function Editor() {
return (
<ApipassWorkflowProvider
runtime={runtime}
actions={{
updateNodeData: (nodeId, patch) => {
// Update your local state, Redux store, server document, etc.
},
regenerateNode: async (nodeId) => {
// Call runtime.runNode or your app-specific runner.
},
}}
>
<WorkflowCanvas
nodes={nodes}
edges={edges}
nodeTypes={defaultApipassNodeTypes}
edgeTypes={defaultApipassEdgeTypes}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
onDropNode={(type, position, initialData) => {
// Add a node to your state.
}}
backgroundProps={{
gap: 24,
color: "var(--canvas-dot)",
}}
controlsProps={{
showInteractive: false,
}}
miniMapProps={{
nodeColor: (node) => node.type === "mediaOutput" ? "#3f3f46" : "#71717a",
maskColor: "rgba(0,0,0,0.08)",
}}
/>
</ApipassWorkflowProvider>
);
}Keep private Apipass API keys on the server. In a Next.js app, your API route can read process.env.APIPASS_API_KEY, call @apipass-dev/apipass-sdk, and return only the workflow node result to the browser. Production routes should use your app's auth and rate limits.
Kit Usage
Use createApipassWorkflowKit when an app wants the default Apipass node map, edge map, add-menu metadata, initial data factory, and connection validator as one object:
import {
ApipassWorkflowEditor,
createApipassWorkflowKit,
} from "@apipass-dev/workflow-canvas";
const kit = createApipassWorkflowKit({
runtime,
nodeOverrides: {
customNode: CustomNode,
},
initialDataOverrides: {
generateImage: { resolution: "2K" },
},
});
export function Editor() {
return (
<ApipassWorkflowEditor
kit={kit}
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
onDropNode={(type, position, initialData) => {
addNode({
type,
position,
data: kit.createNodeData(type, initialData),
});
}}
workflowProviderProps={{
runtime: kit.runtime,
actions: {
updateNodeData,
regenerateNode,
},
}}
/>
);
}ApipassWorkflowEditor is intentionally a thin provider-plus-canvas component. It does not own persistence, auth, billing, routes, workflow lists, or product shell UI.
Runtime Adapters
Apps that execute workflow nodes through product-specific API routes can provide an adapter instead of exposing private API keys in the browser:
import {
createAdapterWorkflowRuntime,
type ApipassWorkflowRuntimeAdapter,
} from "@apipass-dev/workflow-canvas";
const adapter: ApipassWorkflowRuntimeAdapter = {
async generateImage(input) {
const response = await fetch("/api/workflows/generate-image", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(input.request),
signal: input.signal,
});
if (!response.ok) throw new Error("Workflow API route failed.");
const data = await response.json();
return {
nodeId: input.nodeId,
type: input.node.type,
status: "complete",
data: {
status: "complete",
outputImage: data.imageUrl,
},
};
},
};
export const runtime = createAdapterWorkflowRuntime({ adapter });The SDK-backed createApipassWorkflowRuntime({ client }) remains available for server-side execution, tests, and trusted environments. Do not pass production API keys through NEXT_PUBLIC_* variables or instantiate the SDK with private keys in browser code.
What Is Included
WorkflowCanvas: a controlled@xyflow/reactcanvas with Apipass-friendly defaults.ApipassWorkflowProvider: context for the SDK client, runtime, upload adapter, and node actions.createApipassWorkflowKit: default node/edge maps, connection validation, add-menu metadata, and initial node data factories.ApipassWorkflowEditor: a thin provider-plus-canvas editor primitive for controlled workflow state.- Built-in Apipass nodes: prompt, image/audio/video input, image/video/audio generation, LLM generation, output, and gallery.
- Canvas defaults and passthrough props:
defaultApipassCanvasProps,backgroundProps,controlsProps,miniMapProps, andreactFlowProps. - React Flow facade exports from
@apipass-dev/workflow-canvas/react-flowfor apps that should not import@xyflow/reactdirectly. createApipassWorkflowRuntime: graph execution helpers that call@apipass-dev/apipass-sdk.createAdapterWorkflowRuntime: graph execution through app-provided generation/upload handlers.- Workflow schema helpers: normalize, serialize, deserialize, and migrate workflow documents.
React Flow Facade
Apps can keep @xyflow/react behind this package boundary by importing low-level canvas APIs from the facade entry:
import {
BaseEdge,
EdgeLabelRenderer,
Handle,
NodeResizer,
Position,
addEdge,
applyEdgeChanges,
applyNodeChanges,
getBezierPath,
getSmoothStepPath,
useReactFlow,
useUpdateNodeInternals,
} from "@apipass-dev/workflow-canvas/react-flow";This is useful when your app owns custom nodes, custom edges, or a custom store, but you still want @apipass-dev/workflow-canvas to be the only package that talks to React Flow directly.
Design Boundary
The package owns:
- canvas rendering
- node and edge UI
- handle validation
- workflow document shape
- Apipass model request builders
- Apipass workflow runtime
Your app owns:
- auth
- persistence
- billing
- routing
- API-key custody
- product shell
- template/library management
Built-In Node Types
| Type | Purpose |
| --- | --- |
| prompt | Stores prompt text and emits text. |
| imageInput | Stores or uploads an image and emits image. |
| videoInput | Stores or uploads a video and emits video. |
| audioInput | Stores or uploads audio and emits audio. |
| nanoBanana / generateImage | Starts an Apipass image generation job and emits image. |
| generateVideo | Starts an Apipass video generation job and emits video. |
| generateAudio | Starts an Apipass audio generation job and emits audio. |
| llmGenerate | Calls Apipass chat completions and emits text. |
| mediaOutput | Displays the first connected media result. |
| outputGallery | Displays connected image outputs as a gallery. |
Runnable Example
The repository includes a minimal Next.js example app:
cd examples/nextjs-basic
npm install
npm run devOpen the local URL printed by Next.js. The canvas renders without an Apipass key. To run the built-in Apipass generation node, set:
cp .env.example .env.local
# edit .env.local and set APIPASS_API_KEYAPIPASS_API_KEY is read only by the example API route under src/app/api/workflows/run-node/route.ts; it is not exposed to the browser bundle.
The example depends on this package through file:../... Its .npmrc sets install-links=true so npm installs the local package like a packed npm dependency instead of a symlink; this keeps peer dependency resolution close to the published package. After changing library source, run npm run build in the package root, then run npm install again inside the example.
The published npm package does not include examples/. package.json uses a files allowlist containing only dist, README.md, and LICENSE.
Build
npm install
npm run typecheck
npm run buildPublish
npm login
npm publish --access public