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

recon-js

v0.6.0

Published

Record Notation (RECON) JavaScript Implementation

Downloads

27

Readme

Record Notation (RECON)

RECON brings attributes into the era of object notation, and provides a simple grammar and uniform tree model for attributed text markup. RECON aims to combine the minimalism of JSON with the expressiveness of XML in a human-friendly syntax.

Getting Started

The RECON JavaScript library has no dependencies, and can run in any standard JavaScript environment. Use npm to incorporate the RECON JavaScript library into Node.js projects.

npm install --save recon-js
var recon = require('recon-js');

var record = recon.parse('[Welcome @a(href:"index.html")@em[home].]');

Language Primer

Primtives

RECON has three primitive datatypes: text, number, and data.

Text

Text values take one of two forms: a quoted string, or an unquoted identifier.

"string"
identifier

Numbers

Numbers serialize as decimal literals.

-1
3.14
6.02e23

Data

Binary data serializes as a leading '%' symbol, followed by a base64 literal.

%AA==

Records

RECON's sole aggregate datatype, the record, plays the combined role of array and associative array. Think of a record as a partially keyed list. The example record below contains two ordered items, first a "subject" field with value "Greetings", then the unkeyed string "Hello, Earthlings!".

{ subject: "Greetings", "Hello, Earthlings!" }

A single comma, a single semicolon, or one or more newlines separate items. Newline separated records provide a clean syntax for pretty-printed documents.

{
  subject: "Re: Greetings"
  "Hi Martians!"
}

Records support arbitrary values as slot keys.

{
  @planet Jupiter: {}
  @god Jupiter: {}
}

Blocks

Top-level documents can omit the curly braces around their root record. We call the content of a record, sans curly braces, a block. When a block contains only a single item, the value of the block reduces to just the value of the item it contains. The example block below is equivalent to the sample record above.

subject: "Re: Greetings"
"Hi Martians!"

Attributes

The @ sign introduces an attribute. Attributes call out key fields of a record. The previous markup example further reduces to the form below.

{
  "Hello, "
  {
    "@em":
    "world"
  }
  "!"
}

Note that the @em field above has no explicit value. The RECON data model refers to unspecified–but existent–values as extant. We say that the record @em[world] has an extant attribute named em.

Of course, attributes can have associated values too. Place attribute parameters in parentheses, following the attribute's name.

@answer(42)
@event("onClick")

The above attributes are structurally equivalent to:

{"@answer":42}
{"@event":"onClick"}

Attribute parentheses enclose a block, meaning attribute values construct an implicit record when needed. An example, with its desugared equivalent, follows.

@img(src: "tesseract.png", width: 10, height: 10, depth: 10, time: -1)

{
  "@img": {
    src: "tesseract.png"
    width: 10
    height: 10
    depth: 10
    time: -1
  }
}

Attributes modify adjacent values. Modified values interpolate into the record formed by their adjacent attributes. Here are some examples of values with prefix, postfix, and circumfix attributes:

@duration 30
30 @seconds
@duration 30 @seconds
@relative @duration 30 @seconds

The above attribute expressions desugar to the following records:

{ "@duration":, 30 }
{ 30, "@seconds": }
{ "@duration":, 30, "@seconds": }
{ "@relative":, "@duration":, 30, "@seconds": }

Modified records flatten into the record formed by their adjacent attributes. So @point{x:0,y:0}, reduces to {"@point":,x:0,y:0}, not {"@point":,{x:0,y:0}}.

Markup

Square brackets denote markup. Markup offers an inverted syntax for records, with values embedded in text, as opposed to text embedded in records.

[Hello, @em[world]!]

Markup is really just syntactic sugar for records. The above example expresses the exact same structure as the one below.

{ "Hello, "; @em "world"; "!" }

Curly braces within markup lift the enclosed block into the markup's record. The following records are equivalent.

[Answer: {42}.]
{ "Answer", 42, "." }

Square brackets lift nested markup into the enclosing record. Make sure to backslash escape square brackets if you want to include them verbatim.

[Say [what]?]
{ "Say ", "what", "?"}

[Say \[what\]?]
{ "Say [what]?" }

Sequential attributes within markup don't chain; each markup-embedded attribute inserts a nested record.

[http@colon@slash@slash]
{ "http", @colon, @slash, @slash }

Attributes in markup can prefix curly brace enclosed blocks, and nested markup.

[Goals: @select(max:2){fast,good,cheap}.]
{ "Goals: ", @select(max:2){fast,good,cheap}, "." }

Beware that whitespace inside markup is significant. Notice how the single space added to the example below completely changes its meaning, when compared to the previous example.

[Goals: @select(max:2) {fast,good,cheap}.]
{ "Goals: ", @select(max:2), " ", {fast,good,cheap}, "." }

JavaScript Transcoding

RECON types map to JavaScript types as follows:

| RECON Type | JavaScript Type | |------------|------------------| | Text | String | | Number | Number | | Data | Uint8Array | | Field | Object | | Record | Array | | Extant | null | | Absent | undefined | | true | true | | false | false |

Note that RECON treats true and false as ordinary text values. But for compatibility with JSON, the RECON JavaScript library decodes true and false as JavaScript boolean values.

JavaScript Records

Since the order of items in RECON records is significant, records transcode as JavaScript arrays. Fields within a record transcode as a JavaScript object with a single key-value pair. Attrinbutes preserve the @ prefix in their names to distinguish them from other fields.

For convenience, the RECON decoder also defines a non-enumerable object member for each record field decoded into a JavaScript object. This enables field access with subscript notation, without causing duplicate values to appear when invoking JSON.stringify() on a decoded RECON object. Note that updating a JavaScript object member with subscript notation will leave its corresponding array element in an inconsistent state. This shouldn't be an issue in cases where the application doesn't care about the order of RECON-decoded JavaScript objects. The RECON JavaScript library takes care to use the mutated versions of object members when encoding a JavaScript object as RECON. The library also provides a recon.set(object, key, value) function that will update both the named object member and the object's ordered field member, if present.

JSON Examples

RECON: {1, 2, 3}
JSON:  [1, 2, 3]

RECON: {subject: "Greetings", "Hello, Jovians!"}
JSON:  [{"subject": "Greetings"}, "Hello, Jovians!"]

RECON: @event("onClick")
JSON:  {"@event": "onClick"}

RECON: [Hello, @em[world]!]
JSON:  ["Hello, ", [{"@em": null}, "world"], "!"]

JavaScript API

recon.parse(string)

Parses a string for a RECON value.

recon.stringify(value)

Serializes a JavaScript value as a RECON string.

recon.base64(string)

Base64-decodes a string into a Uint8Array.

recon.length(value)

Returns the number of items in value, if value is a record. Returns 0 if value is not a record.

recon.tag(value)

Returns the key of the head field, if value is a record and its head value is a field. Otherwise returns undefined.

recon.target(value)

Returns the first non-Field item in value, if value is a record. Returns value itself if value is not a record, or if it has no non-Field items.

recon.flattened(value)

If value is a record that contains a single value, returns that value. Returns null if value is an empty record or not a record.

recon.header(value, tag)

Returns the value of the tag field of value if value is a record whose head item is a Field with key tag. Otherwise returns undefined.

recon.headers(value, tag)

Returns the value of the tag field of value, coerced to a record, if value is a record whose head item is a Field with key tag. Otherwise returns undefined.

recon.head(value)

Returns the first value or field value, if value is a record. Returns value itself if value is not a record.

recon.tail(value)

Returns a record containing all but the first item of value, if value is a record. Returns an empty record if value is not a record.

recon.body(value)

Returns the flattened tail of value.

recon.has(record, key)

Returns true if some record has a value associated with a key.

recon.get(record, key)

Returns the value associated with a key in some record.

recon.set(record, key, value)

Associates a value with a key in some record, keeping the record's array representation consistent with its object representation.

recon.remove(record, key)

Removes any field with the given key from some record.

recon.keys(value)

Returns an array containing the field keys of value, if value is a record. Returns an empty array if value is not a record.

recon.values(value)

Returns an array containing the field and item values of value, if value is a record. Returns an empty array if value is not a record.

recon.forEach(record, callback[, thisArg])

Invokes callback for every item in record. If provided, thisArg will be passed to each invocation of callback for use as its this value.

callback is invoked with three arguments:

  • the item value, if the item is a field, otherwise the item itself
  • the item key, if the item is a field, otherwise undefined
  • the record being traversed

recon.concat(x, y)

Concatenates two RECON valuesinto a single, flattened record.

recon.equal(x, y)

Compares two RECON items for equality.

recon.compare(x, y)

Orders two RECON items relative to each other. Returns -1 if x comes before y, returns 1 if x comes after y, and returns 0 if x and y are equivalent.

RECON defines a total ordering over all items. Items of different types sort in the following relative order: attributes, slots, records, data, text, numbers, extant, then absent.

recon.Uri.parse(string)

Parses a URI string into a structured URI object. Parsed URIs have the following structure:

{
  scheme: <string>,
  authority: {
    (host | ipv4 | ipv6): <string>,
    (userInfo | username + password): <string>,
  },
  path: [<string>],
  query: <string> | [], // key
  fragment: <string>
}

If a URI string has an undefined component, then the corresponding field of the parsed URI object will also be undefined. Query arrays have a corresponding field member set for each key-value parameter.

Examples

recon.Uri.parse('http://example.com');
// {scheme: "http", authority: {host: "example.com"}}

recon.Uri.parse('http://example.com/');
// {scheme: "http", authority: {host: "example.com"}, path: ["/"]}

recon.Uri.parse('http://example.com/foo/bar');
// {scheme: "http", authority: {host: "example.com"}, path: ["/", "foo", "/", "bar"]}

recon.Uri.parse('http://example.com?search');
// {scheme: "http", authority: {host: "example.com"}, query: "search"}

recon.Uri.parse('http://example.com?key=value');
// {scheme: "http", authority: {host: "example.com"}, query: {key: "value"}}

recon.Uri.parse('http://example.com?key=value&other');
// {scheme: "http", authority: {host: "example.com"}, query: [{key: "value"}, "other"]}

recon.Uri.parse('http://example.com#anchor');
// {scheme: "http", authority: {host: "example.com"}, fragment: "anchor"}

recon.Uri.parse('http://[email protected]');
// {scheme: "http", authority: {host: "example.com", userInfo: "user"}}

recon.Uri.parse('http://user:[email protected]');
// {scheme: "http", authority: {host: "example.com", username: "user", password: "pass"}}

recon.Uri.stringify(uri)

Serializes a parsed URI object as a URI string.

recon.Uri.resolve(base, relative)

Returns the parsed absolute URI obtained by resolving a relative URI against some base URI;

recon.Uri.unresolve(base, absolute)

Returns the parsed relative URI obtained by unresolving an absolute URI against some base URI.

Language Grammar

SP ::= #x20 | #x9

NL ::= #xA | #xD

WS ::= SP | NL

Char ::= [#x1-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]

NameStartChar ::=
  [A-Z] | "_" | [a-z] |
  [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] |
  [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] |
  [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] |
  [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF]

NameChar ::=  NameStartChar | '-' | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040]

MarkupChar ::= Char - ('\\' | '@' | '{' | '}' | '[' | ']')

StringChar ::= Char - ('"' | '\\' | '@' | '{' | '}' | '[' | ']' | '\b' | '\f' | '\n' | '\r' | '\t')

CharEscape ::= '\\' ('"' | '\\' | '/' | '@' | '{' | '}' | '[' | ']' | 'b' | 'f' | 'n' | 'r' | 't')

Base64Char ::= [A-Za-z0-9+/]

Block ::= WS* Slots WS*

Slots ::= Slot SP* ((',' | ';' | NL) WS* Slots)?

Slot ::= BlockValue (SP* ':' SP* BlockValue?)?

Attr ::= '@' (Ident | String) ('(' Block ')')?

BlockAttr ::= (Attr | Comment) SP* BlockValue?

BlockValue ::= (BlockAttr | Record | Markup | Ident | String | Number | Data | Comment) SP* BlockAttr?

InlineValue ::= Attr (Record | Markup)? | Record | Markup

Record ::= '{' Block '}'

Markup ::= '[' (MarkupChar* | CharEscape | InlineValue)* ']'

Ident ::= NameStartChar NameChar*

String ::= '"' (StringChar* | CharEscape)* '"'

Number ::= '-'? (([1-9] [0-9]*) | [0-9]) ('.' [0-9]+)? (('E' | 'e') ('+' | '-')? [0-9]+)?

Data ::= '%' (Base64Char{4})* (Base64Char Base64Char ((Base64Char '=') | ('=' '=')))?

Comment ::= '#' [^\n]*