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

@awesome-flow/core

v0.15.0

Published

The core flow package. Provides building blocks for a flow-based canvas.

Readme

Awesome Flow

Visual interface components

Assembled editor and commands

FlowCanvas from @awesome-flow/core/canvas includes the context, renderer, Add node palette, fit/zoom controls and on-demand Arrange action. It needs a sized parent and the application's MantineProvider; it needs no portal targets or DnD provider.

import { FlowCanvas } from '@awesome-flow/core/canvas';

<div style={{ height: 600 }}>
  <FlowCanvas
    nodeTypes={nodeTypes}
    nodeTemplates={nodeTemplates}
    flowStorage={flowStorage}
    defaultPipelineExecution={settings.pipelineExecution}
    hybrid={{ overviewZoom: settings.skeletonZoom }}
    templates={{ presentation: 'responsive', extraTemplates }}
    controls={{ autoLayout: { rankdir: 'LR' }, fit: true, zoom: true }}
    layoutEngine={layoutEngine}
  />
</div>

Templates and controls are enabled by default on the assembled component. templates={false} hides Add node; controls={false} disables the entire built-in toolbar and its palette. Low-level FlowCanvasRender / FlowCanvasRenderIntersections accept the same options, opt-in. Their existing showControls delegates to the shared controls. Supply one toolbar per editor.

The palette follows the editor container width: a 320 px overlay on desktop, a bottom drawer below 640 px. presentation: 'panel' | 'drawer' overrides that choice. It searches registered, extra and saved templates; optional renderPreview(template) supplies presentation-only content. Button insertion works on touch and keyboard, with no preview node/handle/runtime registration. The desktop panel stays open for repeated additions; the drawer closes after adding. Opening/searching/closing is local UI state and does not modify the document.

Existing custom shells can compose FlowTemplatePalette and FlowCanvasControls under FlowCanvasContext, or call useFlowCommands() (exported from canvas and hooks):

  • insertTemplate(template, { position?, insetRight?, reveal?, generation? }) returns the new node, or undefined when vetoed/read-only/stale. Without an explicit graph position, it uses the current unobscured viewport center, dimensions, origin and snap grid, with bounded offsets for repeated adds. New nodes are root-level and selected; overview insertion reveals detail, subject to maxZoom. insetRight is an overlay width in screen pixels. generation guards delayed UI actions.
  • fitGraph(options?) fits canonical graph bounds, including unmounted hybrid nodes.
  • autoLayout({ options?, fitView?, fitViewOptions?, signal? }) returns Promise: true after applying and optional fit, false for cancellation/read-only/empty graph. Failures reject and publish { layout: 'error', error } through useFlowCommandState(). Concurrent requests share one operation.
  • cancelLayout() aborts the operation. Unmount, document replacement and intervening durable edits cancel it too. Late geometry measurements coalesce into a fresh worker attempt. Selection is preserved.

FlowCanvasContext and FlowCanvas accept beforeInsert(template) (false vetoes) and readOnly to disable mutating commands. The assembled component also disables RF dragging/connections/deletion. Hosts still control editing inside custom node inputs. A palette's onInsert overrides insertion; returning false keeps a drawer open. FlowTemplatesList.onInsert now governs its single Add button. Legacy FlowTemplatesList / FlowCanvasDropWrapper retain HTML5 DnD and still require FlowDndProvider.

Layout workers

The default layout engine is synchronous Dagre. For large graphs, supply the worker adapter below. This preference is independent of pipeline execution. A Promise around main-thread Dagre does not offload its calculation.

// layout.worker.ts — an entry owned and bundled by the consuming application
import { installFlowLayoutWorker } from '@awesome-flow/core/layout-engine';
installFlowLayoutWorker(self);
// Keep the engine stable outside render.
import { createWorkerLayoutEngine } from '@awesome-flow/core/layout-engine';
export const layoutEngine = createWorkerLayoutEngine(
  () => new Worker(new URL('./layout.worker.ts', import.meta.url), { type: 'module' })
);

Alternatively supply a FlowLayoutEngine for another layout service/algorithm. Its input contains only topology and geometry, including canonical parents and group state, never application data or runtime computed values. Results contain positions and group dimensions; current data/selection are merged on the main thread and committed once. Worker requests terminate on completion, error or abort. Graph preparation, result application and fit still happen on the main thread.

Noise, Planet Noise and Group Test use this worker adapter and the assembled editor. ECS uses the same native controls/palette, accepts layoutEngine, and no longer requires #portal-navbar.

Canvas rendering

FlowCanvasRender, FlowCanvasRenderIntersections and FlowEcsCanvas use hybrid rendering by default. The complete graph stays in FlowStore and GraphRuntime. React Flow handles the camera and gestures; visible/active nodes get rich DOM controls, while Canvas placeholders cover overview zoom and progressive mounting. Hidden and unmounted nodes continue to execute their pipelines.

flashNodeOnLogicChanges animates runtime version changes observed after the node view mounts. Returning from a skeleton does not replay earlier updates; existing error messages remain visible. Versions also cover runtime cleanup/invalidation, so the flash is a state-update indicator, not an execution counter.

Set hybrid={false} to use full React Flow rendering, or pass hybrid={{ catalog, fallbackSize, farOverview, overviewZoom }} to customize the hybrid policy. An explicit visibilityWindow={{ ... }} chooses that alternative unless hybrid options are supplied. Host flow.nodes/flow.edges overrides, global nonzero nodeOrigin and global nodeExtent retain the ordinary renderer. Hybrid defaults to minZoom: 0.0001; flow.minZoom overrides it.

overviewZoom is the zoom ratio below which passive nodes become skeletons (default 0.6, or 60%). Rich controls return above overviewZoom * 1.2 to prevent flicker; 0.3 therefore switches below 30% and restores detail above 36%. Values must be greater than zero and at most one. Lowering it also delays far-mode eviction when necessary. Selected/focused controls remain available. Updating the threshold does not recreate the graph or its spatial indexes.

HybridOptionsProvider from @awesome-flow/core/canvas supplies defaults to descendant production canvases: <HybridOptionsProvider value={{ overviewZoom: 0.3 }}>...</HybridOptionsProvider>. An explicit canvas option overrides its provider default. Keep catalogs/options stable between renders.

showMinimap uses a cached overview of the complete canonical graph in hybrid mode. Fit controls, deletion and group drop targets also use canonical state. React Flow's getNodes()/getEdges() return only the presentation subset: use FlowStore for document queries, layout, serialization and programmatic edits. Unmeasured nodes use temporary dimension estimates (320 × 160 by default); optional template silhouettes improve placeholder fidelity. Placeholders do not display live computed text. Per-instance measurements, explicit dimensions and numeric/pixel CSS dimensions take precedence over template estimates. Geometry-only proxies preserve edge endpoints when rich controls unmount.

Auto-layout uses those canonical dimension estimates for nodes that have not mounted, so it can lay out and fit the complete graph without waiting for DOM measurements. Estimates are not written to measured; rerunning layout after nodes have been measured uses their actual dimensions.

Edge labels

FlowEdgeLabel owns the React Flow label portal, edge-relative positioning, pointer-event guards, and the canvas Z layer. Consumers provide only the label content:

import { FlowEdgeLabel } from '@awesome-flow/core/canvas';

<FlowEdgeLabel labelX={labelX} labelY={labelY}>
  <StatusBadge>Ready</StatusBadge>
</FlowEdgeLabel>;

Labels are non-interactive by default. Set interactive when the child contains controls; the component then enables pointer events and applies React Flow's nodrag and nopan guards. The label layer remains above edge paths and below nodes.

Per-flow execution and rendering settings

Hosts can supply their own settings directly; no Sandbox settings or browser storage is required:

<FlowCanvasContext
  nodeTemplates={nodeTemplates}
  defaultPipelineExecution={settings.pipelineExecution} // 'worker' (default) or 'main'
  hybrid={{ overviewZoom: settings.skeletonZoomPercent / 100 }}
>
  <FlowCanvasRender nodeTypes={nodeTypes} />
</FlowCanvasContext>

overviewZoom defaults to 0.6 and accepts 0 < zoom <= 1. Rich nodes return above 1.2 × overviewZoom to avoid flicker near the threshold. Selected/active controls can remain rich. Settings update an existing flow without replacing its document or runtime. Provider defaults (PipelineExecutionProvider and HybridOptionsProvider) still apply when a prop is omitted; FlowCanvasRender hybrid={{ overviewZoom: ... }} overrides the flow's presentation defaults.

Execution preference applies to createNodePipeline pipelines that supply both a main kernel and a worker factory. Explicit pipeline execution choices remain authoritative, ordinary pipelines run on the main thread, and a supplied runtime retains its host-owned execution policy. The library does not automatically move arbitrary JavaScript closures into workers.

FlowEcsCanvas also accepts defaultPipelineExecution and hybrid={{ overviewZoom }}.

Auto layout

<FlowAutoLayout refreshLayout={requested} /> lays out the full logical graph, including nodes represented only by skeletons. Set requested back to false before requesting another layout. An initial true requests layout too. Optional options are Dagre graph options (default { rankdir: 'LR' }); fitView={false} suppresses the subsequent fit, and fitViewOptions configures the fit animation (default duration 500 ms). Options are sampled when a request starts; recreating an options object during a host rerender does not trigger another layout.

Nested groups are sized from the inside out. Collapsed groups keep their compact dimensions and receive updated expanded bounds for a later expansion. Unmounted node estimates stay out of durable node measurements. Waiting layout/fit requests are cancelled on unmount or document replacement. Dagre calculation is currently synchronous on the main thread; pipeline worker preferences do not move auto layout into a worker.

Persisting multiple flow documents

@awesome-flow/core/documents defines a persistence-neutral document boundary for applications that manage more than one graph:

import {
  createFlowSnapshot,
  parseFlowSnapshot,
  type FlowDocument,
  type FlowDocumentRepository
} from '@awesome-flow/core/documents';

createFlowSnapshot clones nodes, edges, and groups into a versioned JSON-safe graph while removing transient canvas fields. parseFlowSnapshot validates persisted input before it is installed. remapFlowSnapshotIds creates collision-safe document duplicates while preserving group membership and edge topology. Applications own the concrete repository, metadata, routing, and authorization; Awesome Flow does not depend on a database.

Canvas consumers can capture or install the current graph through getFlowSnapshot() and replaceFlowSnapshot() on the flow store. Replacement is one logical action: transient interaction state is cleared and GraphRuntime rebuilds all pipeline contexts, including when two documents reuse node IDs.

The snapshot intentionally excludes viewport, selection, measurements, computed outputs, and execution history. Store viewport as optional editor metadata on FlowDocument; store immutable versions and run records separately when the host application supports activation and execution.

Reusing templates and host-authored graphs

NodeFactory accepts an optional second argument (kind: 'node' | 'handle' | 'template') => string. Hosts can inject stable identities for deterministic recipe generation and tests. The default generator is unchanged; callers injecting identities must keep them unique. Both paths use the existing template and handle-delta machinery, including added, removed and configured handles.

FlowTemplatesList accepts an optional onInsert(template) callback. When present, each draggable preview also has a labelled Insert button for keyboard/click authoring. The host owns placement and snapshot insertion. Omitting the callback keeps the original draggable-only palette. Existing toolbar actions have accessible names; this does not replace Save as template with a second workflow.

Persist custom NodeTemplate[] through the existing store templates / setTemplates boundary if the application needs reuse after reload. Applications own storage, validation, migration, save status and conflict policy. Built-in factory defaults should not be copied into that custom catalog.

Connection contexts

Handle dataType values remain simple string or numeric compatibility keys. A target handle can use dataId with groupBy: 'dataId' when several same-type connections have distinct roles. Pipelines receive complete upstream NodeContext references in context.incoming; the runtime does not extract or wrap a separate port value.

Set optional: true on an override or auxiliary handle to de-emphasize it while unused. The handle remains mounted and connectable, returning to full emphasis when its node is selected or hovered, or when it has a connection. This is presentation metadata only and does not alter compatibility or pipeline grouping.

The selected-node toolbar can duplicate any non-group node and save any configured node as a reusable template. FlowCanvasRenderIntersections composes consumer-provided drag callbacks with its group-intersection callbacks. FlowCanvasRender owns deletion and composes the change/connect/deletion callbacks, so higher-level editors can add interaction such as connection-driven quick insertion without disabling grouping behavior.

React Flow's ancestor-aware stacking keeps children above selected groups. Drag/select groups through their headers and resize through their resize handles. Empty group interiors allow edge labels and pane gestures through; consumers do not need custom zIndex overrides.

Groups with maxConnections: 1 resolve to one context, while multi-connection groups resolve to an array. Use getConnectionContexts(context.incoming?.[key]) to normalize either representation to a list while retaining access to the upstream node ID, persisted data, computed output, and its own connection contexts.

Application-owned evaluation

GraphRuntime in @awesome-flow/core/runtime remains usable without a canvas. For persisted graphs, call validateGraph(entries, edges, getFactory) first. It checks IDs, factories, persisted handles (including custom-template deltas), directions, data types, cardinality and cycles. The validator is explicit so existing logical graphs without port declarations remain supported. Runtime traversal also diagnoses cycles, and getNodeStatus(id) distinguishes ready, running, failed, blocked and invalid. Minimum-count diagnostics have severity: 'incomplete'; a preview editor can continue evaluating other branches while showing the missing inputs. Maximum-count/topology defects remain errors. Missing domain inputs are still owned by the consuming pipeline/application, not inferred from a null output.

Removing nodes via syncNodeList also detaches their incident edges, cleans removed pipelines, and refreshes affected surviving contexts. Callers do not need a separate edge-removal event to clear dependent results. Existing contexts retain their identity. A node-list sync with an unchanged ID set does not redispatch.

Connection arrays follow the declared edge-list order, including after syncEdges inserts, reorders or rewires an edge. Preserve that order in host semantic cache keys: ordered operations such as Divide/Power may change even when edge IDs and endpoints are otherwise unchanged. Context objects and dataId/groupBy lookup contracts remain intact.

NodeEntry.input and runtime.updateInputs(new Map([[nodeId, snapshot]])) supply transient external inputs as context.input; they do not modify context.data or persisted snapshots. Application nodes validate their own input shape. Connection contexts, dataId, and groupBy are unchanged.

runtime.subscribe(ids => ...) observes propagation without replacing the legacy version-bump callback. Wait for runtime.idle and inspect statuses before publishing a generation. dispose() suppresses further publication/queued dispatch and cleans in-flight work after it settles; it does not abort arbitrary external promises or undo side effects. For superseding asynchronous previews, use isolated generations and discard the abandoned session in the host.

Pass runtime={applicationRuntime} to FlowCanvasContext to observe that session. In this mode the provider does not install a graph or dispatch edits independently. useUpdateNodeData still updates editor configuration; the host must observe the editor snapshot, resolve inputs, and publish its next runtime. The default canvas-owned mode consumes canonical store commits and batches adjacent runtime changes in a microtask. Do not let both host and editor own evaluation.

getConnectionTargets(template, dataType) in @awesome-flow/core/utils returns every compatible input role with its template index and display label. Use that index on the instantiated node's handles to connect the user's chosen role. getHandleLabel honors explicit labels before generating a fallback.

Unified store and persistence

The canvas owns one FlowStore. Nodes, edges, groups and templates remain in that store, together with transient interaction state and indexed connection queries. Computed pipeline outputs and node-version subscriptions belong to GraphRuntime, outside Zustand.

Use useFlowStore(selector) for editor state and useNodeContext() / useNodeVersion() for runtime output. Prefer narrow selectors. selectNodeConnections and selectHandleConnections return stable edge-ID arrays; handle identity includes node ID, direction and handle ID.

StoreContext exposes flowStore and flowPersistence. flowStore.subscribeCommits(listener) reports applied changes; subscribeDocument(listener) reports durable revisions and document loads. Both return an unsubscribe function. Position changes are durable; selection, dragging, measured dimensions and resizing flags are transient. Explicit width/height changes remain durable.

Enable built-in persistence with flowStorage={{ persist: true, storeName: documentId, persistence: { mode: 'debounce', delay: 450 } }}. Supply persistStorage for a custom Zustand PersistStorage adapter. The controller projects and validates only when writing; its allowlist is nodes, edges, groups, templates, plus registered durableKeys from extension slices. Computed outputs, selection, handles and indexes are never persisted.

useFlowPersistence() exposes reactive phase, dirty, failure, and awaitable ready, flush(), retry(), revalidate(). Projection failures (including non-finite numbers) latch with an actionable data path; correct the document and explicitly call revalidate(). Storage failures can be retried. Neither failure removes the last good stored document. The context owns activation and teardown; headless users own FlowPersistenceController.activate() and await dispose().

See the migration guide for removed APIs, extension registration, runtime ordering and application-owned saves.

Undo, redo, and selection actions

Every FlowStore has bounded document history (100 steps by default). Canvas controls show Undo/Redo; Ctrl/⌘ Z, Ctrl/⌘ Shift Z, and Ctrl Y operate on the focused editor. Text inputs retain native text undo. Use one stable, unique storage key per document to restore the graph and both history stacks from IndexedDB:

<FlowCanvas
  key={documentId}
  nodeTemplates={nodeTemplates}
  nodeTypes={nodeTypes}
  flowStorage={{
    persist: true,
    storeName: `flow:${documentId}`,
    history: { limit: 100, mergeDelay: 500 },
    persistence: { mode: 'debounce', delay: 300 }
  }}
/>

Without persistence, history lasts for the store's lifetime. flowStorage.history: false disables recording; controls.history: false hides only the buttons. useFlowHistory() exposes undo(), redo(), clear(), stopMerging(), batch(fn), and paired begin()/end() for application gestures. Headless callers use flowStore.history. useFlowHistoryState() exposes canUndo, canRedo, undoCount, and redoCount through a separate subscription, without extra canvas-store publications.

History records durable node/edge fields, groups, templates and registered durable extension fields. Selection, camera state, measurements, dragging/resizing flags and runtime outputs are excluded. Undo preserves current transient state on surviving elements; restored elements are initially unselected. Pointer gestures and synchronous setter sequences form one step; repeated data edits to the same nodes merge within mergeDelay. Use batch(fn) for explicit multi-setter commands and stopMerging() to separate unrelated synchronous commands. Layout is one step. Snapshot/host loads clear history; hydration restores only the history belonging to the installed checkpoint. Commands must run outside store observers (schedule a microtask if needed).

The persistence envelope adds history beside state and version; custom storage adapters must retain that envelope. Graph snapshots themselves remain unchanged. History with custom partialize/merge projections or a migrated storage version is not restored because it may refer to different graph data. Projection failures keep the last good checkpoint and use the existing persistence failure channel. If invalid values remain only in old undo entries, call history.clear() before revalidate(). Call await persistence.flush() before navigation when a completed write is required; browser termination cannot guarantee completion of asynchronous IndexedDB work. Context teardown owns timer/listener disposal.

The canvas renders one Selection actions toolbar, with a bounding outline for multiple nodes. It uses canonical geometry, including selected nodes outside the hybrid render window. Duplicate copies selected groups with their descendants and internal connections; Edit components applies to all selected nodes; Delete honors onBeforeDelete and deletion callbacks. Single selection also offers the template action. The controls keep touch-sized targets and stay within the canvas. selectionToolbar={false} disables the shared toolbar for hosts providing their own selection UI; readOnly hides mutating selection actions.