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 🙏

© 2024 – Pkg Stats / Ryan Hefner

hirardoc

v0.0.18

Published

Hierarchical normalised and offline first documents library

Downloads

59

Readme

hirardoc

Library for hierarchical offline first documents.

A document is represented by a hierarchy of normalized elements, with a single root element.

The basic shape of a hierarchical document is

interface INormalizedDocument {
    rootType: RootElementTypeName;
    rootId: Id;
    maps: {
        [ElementTypeName: string]: Map<Id, IElementTypeName>;
    }
} 

The library provides:

  1. low level operations on these types of documents (insert, change, delete, move)
  2. ability to diff between versions of a document
  3. perform information loss preventing three-way merges of the document, with hooks in the merging algorithm to take domain specific decisions in detecting and resolving conflicts

The library is a foundation for higher-level data structures that still want to have a unified way to represent changes and distribute these changes for synchronising data via merges and deltas.

The gist

Declaring your document structure

Declare the type of elements in your hierarchical documents:

interface IRoot extends IParentedId<'Root', null> {
    name: string;
    children: Id[]
}

interface INode extends IParentedId<'Node', 'Root'> {
    name: string;
    children: Id[];
}

interface ITestDocElementsMap {
    Root: Map<Id, IRoot>;
    Node: Map<Id, INode>;
}

Each document should come with its schema

const testDocSchema: IDocumentSchema<ITestDocElementsMap> = {
  documentType: 'TestDocSchema',
  rootType: 'Root',
  types: {
    Root: {
      children: [{__schemaType: 'Node', notNull: true}]
    },
    Node: {
      children: [
        {__schemaType: 'Node', notNull: true}]
    }
  }
};

Changing your document

Then either create a mutable version of a document, to perform a sequence of changes on it, or call a reducer with one or more operations on it to change it.

docReducer(emptyTestDocument(), [
        {
          __typename: HDocCommandType.INSERT_ELEMENT,
          position: ['children', 0],
          parentPath: [],
          element: {
            __typename: 'Node',
            _id: 'Node2',
            children: [],
            isChecked: false,
            text: 'secondNode'
          }
        },
        {
          __typename: HDocCommandType.INSERT_ELEMENT,
          position: ['children', 1],
          parentPath: [],
          element: {
            __typename: 'Node',
            _id: 'Node3',
            children: [],
            isChecked: false,
            text: 'Third node'
          }
        }
      ])
    );

Diffing a document

diff(baseDoc, laterDoc)

it returns a minimal array of document operation that will
transfrom baseDoc in laterDoc

Three-way merge between documents

const baseTree = docReducer(emptyTestDocument(), {
  __typename: HDocCommandType.INSERT_ELEMENT,
  position: ['children', 0],
  parentPath: [],
  element: {
    __typename: 'Node',
    _id: 'Node1',
    children: [],
    isChecked: false,
    text: 'firstNode'
  }
});
const myTree = docReducer(baseTree, [
  {
    __typename: HDocCommandType.INSERT_ELEMENT,
    position: ['children', 0],
    parentPath: [],
    element: {
      __typename: 'Node',
      _id: 'Node2',
      children: [],
      isChecked: false,
      text: 'secondNode'
    }
  }
]);
const theirTree = docReducer(baseTree, [
  {
    __typename: HDocCommandType.INSERT_ELEMENT,
    position: ['children', 0],
    parentPath: [],
    element: {
      __typename: 'Node',
      _id: 'Node3',
      children: [],
      isChecked: false,
      text: 'Third node'
    }
  },
  {
    __typename: HDocCommandType.DELETE_ELEMENT,
    path: ['children', 1]
  }
]);
const {mergedDoc, conflicts} = threeWayMerge(baseTree, myTree, theirTree);

mergedDoc contains the merged normalized document. The tree will still have the same elements as the documents in inp

The information in conflicts will tell you of any conflicts which either were not resolved or were resolved but are provided to show which options were available.

A non-trivial conflict object looks like this:

{ Node: new Map([ [ 'Node2', { infoConflicts: { text: { baseValue: 'secondNode', conflictValues: ['second node', 'SeconD node'], mergedValue: 'SeconD node', mergeStatus: MergeStatus.open } } } ], [ 'Node2_1', { positionConflicts: { clonedElements: ['NewSubtreeRootId'], status: MergeStatus.automerged } } ] ]), Root: new Map() };

There are two types of conflicts:

  1. Field value conflicts, where the same field was changed concurrently and it's not clear which the winner should be

  2. Positional conflicts, when the same element has been placed in different positions in the document hierarchy. These are the more interesting one the library deals with and where hooks are provided to affect how the merge works

Customizing the 3-way merge

To be documented. Using some code from node-diff3 to avoid package dependency.