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

@fyit/crouton-flow

v0.1.0

Published

Vue Flow integration for nuxt-crouton - graph/DAG visualization for collections

Readme

nuxt-crouton-flow

Vue Flow integration layer for Nuxt Crouton - graph/DAG visualization with real-time multiplayer collaboration.

Features

  • Graph Visualization: Render collection data as interactive node graphs
  • Automatic Layout: Dagre-based tree/DAG layout for hierarchical data
  • Real-time Sync: Multiplayer collaboration via Yjs CRDTs
  • Presence Awareness: See other users' cursors and selections
  • Position Persistence: Drag-and-drop with automatic position saving
  • Custom Nodes: Use collection-specific node components

Installation

pnpm add @fyit/crouton-flow

Add to your nuxt.config.ts:

export default defineNuxtConfig({
  extends: [
    '@fyit/crouton',
    '@fyit/crouton-flow'
  ]
})

Basic Usage

<template>
  <CroutonFlow
    :rows="decisions"
    collection="decisions"
    parent-field="parentId"
    position-field="position"
  />
</template>

<script setup lang="ts">
const { data: decisions } = await useCollectionQuery('decisions')
</script>

Real-time Multiplayer Sync

Enable multiplayer collaboration with the sync prop:

<template>
  <CroutonFlow
    collection="decisions"
    sync
    :flow-id="projectId"
  />
</template>

<script setup lang="ts">
const route = useRoute()
const projectId = route.params.projectId as string
</script>

How It Works

When sync is enabled:

  1. Yjs CRDT: All node state is managed by a Yjs document
  2. Durable Objects: A Cloudflare Durable Object handles WebSocket connections
  3. Real-time Sync: Changes are broadcast to all connected clients
  4. Dual Persistence: State is saved both as Yjs blob and individual rows

Prerequisites

  1. D1 Database: Run the migration to create the yjs_flow_states table:
-- server/database/migrations/0001_yjs_flow_states.sql
CREATE TABLE IF NOT EXISTS yjs_flow_states (
  flow_id TEXT PRIMARY KEY,
  collection_name TEXT NOT NULL,
  state BLOB NOT NULL,
  version INTEGER DEFAULT 1,
  created_at INTEGER DEFAULT (unixepoch()),
  updated_at INTEGER DEFAULT (unixepoch())
);
  1. Wrangler Config: Configure Durable Objects in wrangler.toml:
[[durable_objects.bindings]]
name = "FLOW_ROOMS"
class_name = "FlowRoom"

[[migrations]]
tag = "v1"
new_classes = ["FlowRoom"]
  1. Authentication: Users must be authenticated via useUserSession() for presence features.

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | rows | Record<string, unknown>[] | - | Collection rows (not needed with sync) | | collection | string | required | Collection name | | parentField | string | 'parentId' | Field for parent relationships | | positionField | string | 'position' | Field for node positions | | labelField | string | 'title' | Field for node labels | | sync | boolean | false | Enable real-time sync | | flowId | string | - | Flow ID (required with sync) | | controls | boolean | true | Show zoom controls | | minimap | boolean | false | Show minimap | | background | boolean | true | Show background pattern | | draggable | boolean | true | Enable node dragging |

Events

| Event | Payload | Description | |-------|---------|-------------| | nodeClick | (nodeId, data) | Node clicked | | nodeDblClick | (nodeId, data) | Node double-clicked | | nodeMove | (nodeId, position) | Node position changed | | edgeClick | (edgeId) | Edge clicked |

Custom Node Components

Create a [Collection]Node.vue component to customize node rendering:

<!-- app/components/DecisionsNode.vue -->
<script setup lang="ts">
defineProps<{
  data: Record<string, unknown>
  selected: boolean
  dragging: boolean
  label?: string
}>()
</script>

<template>
  <div class="custom-node" :class="{ selected, dragging }">
    <h3>{{ data.title }}</h3>
    <p>{{ data.status }}</p>
  </div>
</template>

Composables

useFlowSync

Direct access to the sync state for advanced use cases:

const {
  nodes,           // Readonly ref of all nodes
  connected,       // WebSocket connected
  synced,          // Initial sync complete
  error,           // Connection error
  users,           // Online users
  createNode,      // Create a new node
  updateNode,      // Update node data
  updatePosition,  // Update node position
  deleteNode,      // Delete a node
  selectNode,      // Broadcast node selection
  updateCursor,    // Broadcast cursor position
} = useFlowSync({
  flowId: 'my-flow',
  collection: 'decisions'
})

useFlowPresence

Utilities for presence UI:

const { otherUsers, getUsersSelectingNode, getNodePresenceStyle } = useFlowPresence({
  users: computed(() => syncState.users),
  currentUserId: currentUser.id
})

Architecture

┌─────────────────────────────────────────────────────────────┐
│                        Clients                              │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐                  │
│  │ Client A │  │ Client B │  │ Client C │                  │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘                  │
│       │             │             │                         │
│       └─────────────┼─────────────┘                         │
│                     │ WebSocket                             │
│                     ▼                                       │
│  ┌─────────────────────────────────────────────────────┐   │
│  │           Cloudflare Durable Object                 │   │
│  │                 (FlowRoom)                          │   │
│  │  - Manages Yjs Y.Doc per flow                       │   │
│  │  - Handles WebSocket connections                    │   │
│  │  - Merges updates from all clients                  │   │
│  │  - Persists to D1 on changes                        │   │
│  └──────────────────────┬──────────────────────────────┘   │
│                         │                                   │
│                         ▼                                   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                 D1 (SQLite)                         │   │
│  │  yjs_flow_states    │  collection tables            │   │
│  │  (fast reload)      │  (queryable records)          │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

License

MIT