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

@lyonbot/linked-data

v1.0.0

Published

Load and edit linked data easily

Readme

@lyonbot/linked-data

Load edit linked (graph-like) data easily

[ Homepage | GitHub | NPM ]

Usage

npm i @lyonbot/linked-data

Create Nodes

Let's say we have such nested data:

const cardData = {
  type: 'card',
  theme: 'black',
  children: [
    { type: 'paragraph', children: ['Welcome'] },
    { id: 'openBtn', type: 'button', children: ['Open'] },
  ],
};

And we know its pattern (schemas)

const schemas = {
  Component: {
    type: 'object',
    key: 'id', // if exists, take `id` property as unique key
    properties: {
      children: 'ComponentArray', // Array also has its own schema (see below)
    },

  ComponentArray: {
    type: 'array',
    items: 'Component', // actual items can be non-object. we don't strictly validate the type
  },
};

We can convert the data it into lots of connected nodes, following the schema relations.

const linkedData = new LinkedData({ schemas });
const cardNode = linkedData.import(cardData, 'Component');

The linkedData.import() will follow "Component" schema, explode cardData to lots of DataNodes, make links between them, and return the entry DataNode -- the cardNode above.

Note:

Schema does NOT validate value type. A DataNode with "object" schema, can still storage anything -- array, string, number, etc.

If actual type of node.value mismatches, the schema will be ignored temporarily, until value is set to correct type.

In this example, some "Component" nodes store string values only. That's okay.

Every DataNode has a unique key. If you import twice without "overwrite" option, you will get new DataNodes. The new nodes will have different keys, although their contents are same as the old nodes'.

Read, Edit, Link & Unlink

DataNode provides Proxy-powered node.value. You can read, write, splice array, push elements with it freely.

You don't have to care about the links -- they are automatically converted into Proxy again. Just consume and mutate the value.

const card = cardNode.value;

// you can read and write value to "card"

card.theme = 'light';
card.children.push('another text');

const button = card.children.find(child => child.id === 'openBtn');
button.children.push({ type: 'icon', icon: 'caret-right' });

// card is modified now

Because we've prepared schemas for each DataNode, the *.children.push above, will automatically:

  1. create new "Component" DataNode
  2. modify the array, add new link

If you want to manually create a link, use anotherNode.ref:

// create a DataNode with no Schema
const passwordNode = linkedData.import('dolphins');

// make a link
cardNode.value.password = passwordNode.ref;

// read
console.log(cardNode.value.password); // => "dolphins"

// always synced
passwordNode.value = 'nE7jA%5m';
console.log(cardNode.value.password); // => "nE7jA%5m"

// unlink
cardNode.value.password = 'dead string';
console.log(cardNode.value.password); // => "dead string"
console.log(passwordNode.value); // => "nE7jA%5m" -- not affected

Track Mutations, Undo & Redo

This feature is tree-shakable

With ModificationObserver magic, all you need is:

import { ModificationObserver } from '@lyonbot/linked-data';

// ... skip ...

const observer = new ModificationObserver(() => {
  // find out what's changed
  const records = observer.takeRecords();
  console.log(`You just add / edit ${records.length} nodes!`);

  // you can storage records to somewhere else.
  // how to undo/redo? see the first figure above

  // stop observing
  observer.disconnect();
});

// start observing
observer.observeLinkedData(linkedData);

// ----------------------------------------
// now start to modify node.value
// ...

const card = cardNode.value;

card.theme = 'light';
card.children.push('another text');

const button = card.children.find(child => child.id === 'openBtn');
button.children.push({ type: 'icon', icon: 'caret-right' });

In the records, you may get following deduced procedure:

With the records, you can easily implement undo & redo:

import { applyPatches } from '@lyonbot/linked-data';

// undo:
records.forEach(record => {
  record.node.value = applyPatches(record.node.value, record.revertPatches);
});

// then, redo:
records.forEach(record => {
  record.node.value = applyPatches(record.node.value, record.patches);
});

Theories

Mutable & Immutable

In the separated Node list, every node is independent. If you modify a Node, the other Nodes referring it will NOT be mutated -- they only have a reference, not a value.

Therefore, the nested data containing lots of Nodes, is close to "mutable" philosophy.

We only cares about when a Node's value changes. You can maintain in mutable way or immutable way -- it doesn't matter, as long as you can notify us that value is changed.

💡 We suggest that maintain Node value in immutable way. It allows external libraries to utilize Object.is(x, y) and low-costly distinguish whether value is really changed, where value can be the whole Node or some property from Node.

  • To be aggressive, if we treat every object/array as Node regardless of their semantic purposes, we will get Vue or Mobx -- every non-primitive value can be "observed".

  • Web Component's attributes are always primitive data, which makes the comparison simple and low-cost.

Dependency Graph

Every Node can be referred.

It's easy to find out a Node's dependents with linked-data because we collects necessary info while generating the separated Node list.

The dependency graph may be circular.

Identifier

Every Node needs an identifier.

💡 Identifiers shall be permanent, readonly, final to a Node.

💡 In a certain context, identifiers shall be unique.

We shall always store it within Node's value. If a input Node has no identifier, we shall generated one, in current context.

You can see lots of generated, unnamed_-prefixed identifiers in the example above.

  • Vue doesn't need one because

    1. Each object instance has a memory address in JavaScript engine. We can use memory address as the identifier because Identifier's properties apply to memory addresses.

    2. Vue doesn't hydrate two nested data.

  • MongoDB generates _id for each document.

When you need Schemas

Schemas are optional.

Schema does NOT validate value type. A DataNode with "object" schema, can still storage anything -- array, string, number, etc.

If actual type of node.value mismatches, the schema will be ignored temporarily, until value is set to correct type.

What can a schema play a role in? You can define some rules by writing schemas:

  • key:

    • when importing, how to read Identifier from raw JSON object
    • when exporting, how to write Identifier to the exported JSON object
  • properties for objects, or items for arrays

    • when importing, convert some properties it into a Node Reference.
    • when exporting, convert some "referring" properties into node.
    • when writing values (not reference) into certain properties, automatically create new Node and new reference.

However the other properties CAN be a Node Reference too -- user can make links anywhere.