@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-Protocol
s 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 aData-Protocol
, appear at the beginning, before the firstData-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 theproto
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);