Skip to content

Commit

Permalink
feat(parse-data-protocol): add utility that implements NAS-115 groupi…
Browse files Browse the repository at this point in the history
…ng convention #1059
  • Loading branch information
TillaTheHun0 committed Oct 31, 2024
1 parent a83cc87 commit a95e2c7
Show file tree
Hide file tree
Showing 7 changed files with 1,355 additions and 0 deletions.
285 changes: 285 additions & 0 deletions parse-data-protocol/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,285 @@
# parse-data-protocol

A set of utilities for extracting and parsing tags associated with ANS-115
`Data-Protocol`s

<!-- toc -->

- [Why](#why)
- [Usage](#usage)
- [`findAll`](#findall)
- [`findAllByName`](#findallbyname)
- [`findByName`](#findbyname)
- [`parseAll`](#parseall)
- [`parse`](#parse)
- [`create`](#create)
- [`assoc`](#assoc)
- [`proto`](#proto)

<!-- tocstop -->

## 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:

```js
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.

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 tags not belonging to a `Data-Protocol` would appear first, before any**
> **`Data-Protocol` tags.**
```js
const tags = [
{ 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" },
];
```

This module provides utilties for interacting with Data-Protocol tags, using
that convention. In this module, this convention is referred to as
"`association`" ie. tags are associated with the most recent `Data-Protocol`

## Usage

```js
import {
concat,
create,
findAll,
findAllByName,
findByName,
parse,
parseAll,
proto,
} from "@permaweb/parse-data-protocol";

const tags = [
{ 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
];

// 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 first
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`](#proto) helper.
### `findAll`

Extract the tags associated with the provided `Data-Protocol`.

If the `Data-Protocol` tag is NOT found, then ALL tags are considered associated
with the `Data-Protocol`.

```js
import { findAll } from "@permaweb/parse-data-protocol";

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

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

### `findAllByName`

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

```js
import { findAllByName } from '@permaweb/parse-data-protocol'

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

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

### `findByName`

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

```js
import { findByName } from '@permaweb/parse-data-protocol'

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

// { name, value }
const aoType = findAllByName('ao', 'Type' 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

```js
import { parseAll } from "@permaweb/parse-data-protocol";

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

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

### `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`](#parseall)

```js
import { parse } from "@permaweb/parse-data-protocol";

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

// { Type: 'Process', Module: '...' }
const aoParsed = parse("ao", 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.

```js
import { create } from "@permaweb/parse-data-protocol";

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

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

### `assoc`

Produce a new array of tags by associating the provided tags with the
`Data-Protocol`. The new associations are appended to end of an existing
_subsection_.

If there is no existing _subsection_ of tags for the `Data-Protocol`, then the
new section is appended to the end

NO deduplication is performed on the associated tags.

```js
import { assoc } from "@permaweb/parse-data-protocol";

const tags = [
{ 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
];

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

// ao subsection is appended to
const newTags = assoc("ao", pTags, tags)[
{ name: "Data-Protocol", value: "ao" },
// these are associated with ao Data-Protocol
{ name: "Type", value: "Process" },
{ name: "Variant", value: "ao.TN.1" },
{ name: "Foo", value: "Bar" },
{ name: "Cool", value: "Beans" },
// 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
];
```

### `proto`

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

Build a `@permaweb/parse-data-protocol` API for a single `Data-Protocol`

```js
import { proto } from "@permaweb/parse-data-protocol";

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

const tags = [
{ 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" },
];

// 'Process'
const aoType = ao.value("Type", tags);
// ['Profile']
const zoneTypes = zone.values("Type", tags);
```
25 changes: 25 additions & 0 deletions parse-data-protocol/esbuild.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { readFileSync } from 'node:fs'
import * as esbuild from 'esbuild'

/**
* By importing from manifest, build will always be in sync with the manifest
*/
const manifest = JSON.parse(readFileSync('./package.json'))

// CJS
await esbuild.build({
entryPoints: ['index.js'],
platform: 'node',
format: 'cjs',
bundle: true,
outfile: manifest.main
})

// ESM
await esbuild.build({
entryPoints: ['index.js'],
platform: 'node',
format: 'esm',
bundle: true,
outfile: manifest.module
})
Loading

0 comments on commit a95e2c7

Please sign in to comment.