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

@permaweb/protocol-tag-utils

v0.0.2

Published

A utility for extracting and parsing tags associated with ANS-115 Data-Protocols

Downloads

1,203

Readme

protocol-tag-utils

A tiny, zero-dependency, set of utilities for interacting with Data-Protocol tags. Browser, Node, Bun, and Deno compatible.

Why

It is fundamental to note that, in ANS-104, tags are ordered.

ANS-115 specifies how to utilize Data-Protocols to compose application level specification using ANS-104 tags.

However, ambiguity arises when a piece of data implements many Data-Protocol which specify the same tag name:

const tags = [
  { name: "Data-Protocol", value: "ao" },
  // ...
  { name: "Data-Protocol", value: "zone" },
  // Which tags goes with which Data-Protocol?
  { name: "Type", value: "Process" },
  { name: "Type", value: "Profile" },
  { name: "Variant", value: "ao.TN.1" },
  { name: "Variant", value: "0.0.2" },
];

This ambuguity can lead to unexpected behavior in workarounds in subsequent implementations.

Solution

By enforcing one additional simple convention, this ambiguity is massively curtailed:

Tags are "associated" with the most recent Data-Protocol tag. **A corollary is that "unassociated" tags, not belonging to a Data-Protocol, appear at the beginning, before the first Data-Protocol tag.

const tags = [
  { name: "Unassociated", value: "Tag" },
  { name: "Another", value: "Unassociated" },
  // these are associated with 'ao' Data-Protocol
  { name: "Data-Protocol", value: "ao" },
  { name: "Type", value: "Process" },
  { name: "Variant", value: "ao.TN.1" },
  // these are asscociated with 'zone' Data-Protocol
  { name: "Data-Protocol", value: "zone" },
  { name: "Type", value: "Profile" },
  { name: "Variant", value: "0.0.2" },
];

This module provides utilties for interacting with tags, using the above convention.

Usage

import {
  concat,
  concatUnassoc,
  create,
  findAll,
  findAllByName,
  findByName,
  parse,
  parseAll,
  parseAllUnassoc,
  parseUnassoc,
  proto,
  removeAll,
  removeAllByName,
  update,
} from "@permaweb/protocol-tag-utils";

const tags = [
  { name: "Data-Protocol", value: "ao" },
  { name: "Type", value: "Process" },
  { name: "Variant", value: "ao.TN.1" },
  { name: "Data-Protocol", value: "zone" },
  { name: "Type", value: "Profile" },
  { name: "Variant", value: "0.0.2" },
];

// use a top-level export, passing protocol first
const aoType = findByName("ao", "Type", tags);

// OR use the proto helper to get an API per Data-Protocol
const ao = proto("ao");
const zone = proto("zone");

// No longer need to pass protocol every time
const aoType = ao.value("Type", tags);
const zoneTypes = zone.values("Type", tags);

Passing protocol first, over and over, might get verbose. Alternatively, you can use the proto helper.

findAll

Extract the tags associated with the provided Data-Protocol.

If the Data-Protocol tag is NOT found, then an empty array is returned

import { findAll } from "@permaweb/protocol-tag-utils";

const tags = [/*...*/];

// [{ name, value }, ...]
const aoTags = findAll("ao", tags);

findAllByName

Extract the tags, with the name, associated with the provided Data-Protocol.

import { findAllByName } from '@permaweb/protocol-tag-utils'

const tags = [/*...*/]

// [{ name, value }, ...]
const zoneTypes = findAllByName('zone', 'Type' tags)

findByName

Extract the FIRST tag, with the name, associated with the provided Data-Protocol.

import { findByName } from '@permaweb/protocol-tag-utils'

const tags = [/*...*/]

// { name, value }
const aoType = findAllByName('ao', 'Type' tags)

create

Associate an array of tags associated with the Data-Protocol. The Data-Protocol tag will be prepended to the front of the array.

import { create } from "@permaweb/protocol-tag-utils";

const pTags = [{ name: "Foo", value: "Bar" }];

/**
[
  { name: 'Data-Protocol', value: 'ao' },
  { name: 'Foo', value: 'Bar' }
]
 */
const aoTags = create("ao", pTags);

update

Replace the tags, associated with the Data-Protocol, with the provided tags.

If there are no associated tags for the Data-Protocol, then the new section is concatenated to the end of all tags.

NO deduplication is performed on the associated tags.

import { update } from "@permaweb/protocol-tag-utils";

const tags = [
  { name: "Data-Protocol", value: "ao" },
  { name: "Type", value: "Process" },
  { name: "Variant", value: "ao.TN.1" },
  { name: "Data-Protocol", value: "zone" },
  { name: "Type", value: "Profile" },
  { name: "Variant", value: "0.0.2" },
];

const pTags = [{ name: "Foo", value: "Bar" }, { name: "Cool", value: "Beans" }];

// ao subsection is replaced
update("ao", pTags, tags);

/**
 *[
    { name: "Data-Protocol", value: "ao" },
    { name: "Foo", value: "Bar" },
    { name: "Cool", value: "Beans" },
    { name: "Data-Protocol", value: "zone" },
    { name: "Type", value: "Profile" },
    { name: "Variant", value: "0.0.2" }
  ]
 */

concat

Same update, except do not replace the associated tags, and instead concatenate to them.

import { concat } from "@permaweb/protocol-tag-utils";

const tags = [
  { name: "Data-Protocol", value: "ao" },
  { name: "Type", value: "Process" },
  { name: "Variant", value: "ao.TN.1" },
  { name: "Data-Protocol", value: "zone" },
  { name: "Type", value: "Profile" },
  { name: "Variant", value: "0.0.2" },
  // end zone tags
];

const pTags = [{ name: "Foo", value: "Bar" }, { name: "Cool", value: "Beans" }];

// ao subsection is appended to
concat("ao", pTags, tags);

/**
 *[
    { name: "Data-Protocol", value: "ao" },
    { name: "Type", value: "Process" },
    { name: "Variant", value: "ao.TN.1" },
    { name: "Foo", value: "Bar" },
    { name: "Cool", value: "Beans" },
    { name: "Data-Protocol", value: "zone" },
    { name: "Type", value: "Profile" },
    { name: "Variant", value: "0.0.2" }
  ]
 */

removeAll

Remove the Data-Protocol section and all associated tags

import { removeAll } from "@permaweb/protocol-tag-utils";

const tags = [
  { name: "Unassociated", value: "Tag" },
  { name: "Another", value: "Unassociated" },
  { name: "Data-Protocol", value: "ao" },
  // these are associated with ao Data-Protocol
  { name: "Type", value: "Process" },
  { name: "Variant", value: "ao.TN.1" },
  // end ao tags
  { name: "Data-Protocol", value: "zone" },
  // these are asscociated with zone Data-Protocol
  { name: "Type", value: "Profile" },
  { name: "Variant", value: "0.0.2" },
  // end zone tags
];

// ao subsection is removed
removeAll("ao", tags);

/**
 *[
    { name: 'Unassociated', value: 'Tag' },
    { name: 'Another', value: 'Unassociated' },
    { name: "Data-Protocol", value: "zone" },
    { name: "Type", value: "Profile" },
    { name: "Variant", value: "0.0.2" }
  ]
 */

removeAllByName

Remove all tags, with the name, associated with the Data-Protocol.

import { removeAllByName } from "@permaweb/protocol-tag-utils";

const tags = [
  { name: "Unassociated", value: "Tag" },
  { name: "Another", value: "Unassociated" },
  { name: "Data-Protocol", value: "ao" },
  { name: "Type", value: "Process" },
  { name: "Variant", value: "ao.TN.1" },
  { name: "Data-Protocol", value: "zone" },
  { name: "Type", value: "Profile" },
  { name: "Type", value: "Other" },
  { name: "Variant", value: "0.0.2" },
];

// all zone 'Type' tags are removed
removeAllByName("zone", "Type", tags);

/**
 *[
    { name: 'Unassociated', value: 'Tag' },
    { name: 'Another', value: 'Unassociated' },
    { name: "Data-Protocol", value: "ao" },
    { name: "Type", value: "Process" },
    { name: "Variant", value: "ao.TN.1" },
    { name: "Data-Protocol", value: "zone" },
    { name: "Variant", value: "0.0.2" }
  ]
 */

parse

Parse tags, associated with the Data-Protocol, into an object with key-value pairs of name -> value.

If multiple tags are found, then the FIRST tag value is used, and subsequent values are discarded. If you'd like to preserve all values, then use parseAll

import { parse } from "@permaweb/protocol-tag-utils";

const tags = [/*...*/];

// { Type: 'Process', Module: '...' }
const aoParsed = parse("ao", tags);

parseAll

Parse tags, associated with the Data-Protocol, into an object with key-value pairs of name -> an array of values.

At each key, the values in each array will be in order of appearance

import { parseAll } from "@permaweb/protocol-tag-utils";

const tags = [/*...*/];

// { Type: ['Process', ...], Module: ['...'] }
const aoParsed = parseAll("ao", tags);

proto

Instead of constantly passing protocol as the first argument every time, you can use this helper.

Build a @permaweb/protocol-tag-utils API for a single Data-Protocol

import { proto } from "@permaweb/protocol-tag-utils";

const ao = proto("ao");
const zone = proto("zone");

const tags = [
  { name: "Data-Protocol", value: "ao" },
  { name: "Type", value: "Process" },
  { name: "Variant", value: "ao.TN.1" },
  { name: "Data-Protocol", value: "zone" },
  { name: "Type", value: "Profile" },
  { name: "Variant", value: "0.0.2" },
];

// 'Process'
const aoType = ao.value("Type", tags);
// ['Profile']
const zoneTypes = zone.values("Type", tags);

Unassociated Tags

The module also has utilities for interacting with tags not associated with any Data-Protocol ie. tags before any Data-Protocol tag. These tags are referred to as "unassociated" tags.

If you'd like to simply add an uassociated tag to the beginning, simply use unshift.

concatUnassoc

Add unassociated tags to the end of unassociated section.

import { concatUnassoc } from "@permaweb/protocol-tag-utils"

const tags = [
  { name: 'Random', value: 'Tag' }
  { name: "Data-Protocol", value: "ao" },
  { name: "Type", value: "Process" },
  { name: "Variant", value: "ao.TN.1" },
  { name: "Data-Protocol", value: "zone" },
  { name: "Type", value: "Profile" },
  { name: "Variant", value: "0.0.2" },
]

/**
[
  { name: 'Random', value: 'Tag' }
  { name: 'Another', value: 'One' },
  ...
]
 */
concatUnassoc([{ name: 'Another', value: 'One' }], tags)

parseUnassoc

Same as parse, but for unassociated tags

import { parseUnassoc } from "@permaweb/protocol-tag-utils";

const tags = [/*...*/];

// { Random: 'Tag', Another: 'One' }
parseUnassoc(tags);

parseAllUassoc

Same as parseAll, but for unassociated tags

import { parseAllUnassoc } from "@permaweb/protocol-tag-utils";

const tags = [/*...*/];

// { Random: ['Tag'], Another: ['One'] }
parseAllUnassoc(tags);