@permaweb/protocol-tag-utils v0.0.2
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-Protocoltag. A corollary is that "unassociated" tags, not belonging to aData-Protocol, appear at the beginning, before the firstData-Protocoltag.**
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
protocolfirst, over and over, might get verbose. Alternatively, you can use theprotohelper.
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);