NANOS is a versatile JavaScript data structure that combines the features of ordered, indexed arrays and named-key objects (maps). It provides a rich, fluent API for managing collections of data where both numerical position and unique keys are important, while strictly maintaining insertion order for all elements.
It is designed as a single ES module with no external dependencies.
- Hybrid Structure: Stores both indexed (numbered) and keyed (named) values in a single data structure.
- Order-Preserving: Maintains the original insertion order for all keys and indexes (subject to the constraint that indexed values appear in ascending order).
- Rich API: Provides a comprehensive set of methods similar to
ArrayandMap, includingpush,pop,shift,unshift,set,get,at,filter,forEach,keys,values, andentries. - Array-like Indexing: Supports negative indices to access elements from the end of the indexed list (e.g.,
-1for the last item). - Immutability: Includes methods like
freeze(),deepFreeze(), andlock()to create read-only data structures. - Custom Serialization:
- SLID (Static List Data): A custom, human-readable data format for serialization and deserialization.
- QJSON (Quasi-JSON): A relaxed JSON-like parser for easier data entry.
- Reactive Interface: Can integrate with reactive libraries through a
rio(Reactive Interface Object) property to automatically track changes. The RIO can be a "basic" RIO that only tracks packaging changes (key addition/removal), or an "extended" RIO that also tracks value changes and can automatically wrap new values in reactives. - Value Redaction: Provides a
redact()method to securely exclude sensitive values from string output.
Since NANOS is an ES module, you can import it directly into your project.
import { NANOS, parseSLID, parseQJSON } from './src/nanos.esm.js';// Create an empty instance
const data = new NANOS();
// Create with initial items
const items = new NANOS('a', 'b', { key: 'value' });NANOS allows you to add data using indexed positions or named keys.
const n = new NANOS();
// Add indexed values using push()
n.push(10, 20, 30);
// Add named values using set()
n.set('name', 'example');
n.set('id', 123);
// Access values using at() or get()
console.log(n.at(0)); // Output: 10
console.log(n.at('name')); // Output: 'example'
// Use negative indices to access from the end
console.log(n.at(-1)); // Output: 30You can iterate over entries, keys, or values. The iteration order is always the (constrained-index) insertion order.
for (const [key, value] of n.entries()) {
console.log(`${key}: ${value}`);
}
// Output:
// 0: 10
// 1: 20
// 2: 30
// name: example
// id: 123The toString() method serializes the NANOS instance to the SLID format.
const n = new NANOS(1, 'two');
n.set('id', 3);
n.set('status', 'ok');
const slidString = n.toString();
console.log(slidString);
// Output: [(1 two id=3 status=ok)]You can parse a SLID string back into a NANOS instance.
const parsed = NANOS.parseSLID(slidString);
console.log(parsed.at('status')); // Output: 'ok'Creates a new NANOS instance.
...items: Initial items to.pushinto the instance.
Gets the value at a specified key or index. Negative indices are resolved relative to the end of the indexed portion. If the value is reactive, the "final" (non-reactive) value is returned by default.
key: The key or index to look up, or an array of keys/indexes to recursively traverse.opts.default: An optional default value to return if the key is not found.opts.raw: Iftrue, returns the raw value, which may be a reactive.- Returns: The value, or
opts.defaultif not found. - Alias:
.get()
Gets the raw value at a specified key or index. This is a shortcut for .at(key, { raw: true }).
key: The key or index to look up.opts.default: An optional default value to return if the key is not found.- Returns: The raw value, or
opts.defaultif not found.
Removes all key-value pairs from the instance. Throws an error if the instance is locked.
- Returns:
this.
Recursively freezes this NANOS instance and any nested NANOS values, making them completely immutable.
- Returns:
this.
Deletes a key-value pair by its key or index.
key: The key or index to delete.opts.raw: Iftrue, returns the raw (potentially reactive) value.- Returns: The value that was deleted.
Signals a dependency for reactive interfaces (like Svelte or Vue).
Returns an iterator that yields [key, value] pairs.
opts.compact: Iftrue, index keys are returned as numbers instead of strings.opts.raw: Iftrue, yields raw (potentially reactive) values.- Returns: An iterator for all entries.
Creates a new NANOS instance with all elements that pass the test implemented by the provided function.
f(value, key, nanos): The testing function.- Returns: A new, filtered NANOS instance.
Returns the first [key, value] pair for which the testing function f returns true.
f(value, key, nanos): The testing function.opts.raw: Iftrue, passes raw (potentially reactive) values to the testing function.- Returns: The
[key, value]pair, orundefined.
Returns the last [key, value] pair for which the testing function f returns true.
f(value, key, nanos): The testing function.opts.raw: Iftrue, passes raw (potentially reactive) values to the testing function.- Returns: The
[key, value]pair, orundefined.
Executes a provided function once for each key/value pair.
f(value, key, nanos): The function to execute.opts.raw: Iftrue, passes raw (potentially reactive) values to the function.
Freezes the instance, making the key set and all values immutable.
- Returns:
this.
Populates the instance from an array of [key, value] entries.
entries: An array of[key, value]pairs.insert: Iftrue, entries are inserted at the beginning; otherwise, they are appended.- Returns:
this.
Populates the instance from a flat list of key1, value1, key2, value2, ... pairs.
...pairs: The pairs to populate from.- Returns:
this.
Checks if a key exists in the instance.
key: The key or index to check.- Returns:
trueif the key exists, otherwisefalse.
Checks if a value exists in the instance.
value: The value to search for.- Returns:
trueif the value exists, otherwisefalse.
Returns an iterator that yields [key, value] pairs for indexed entries only.
compact: Iftrue, index keys are returned as numbers instead of strings.- Returns: An iterator for indexed entries.
Returns an iterator that yields the keys of indexed entries only.
- Returns: An iterator for index keys.
Checks if the instance (or a specific key) is locked.
key: Optional key to check. If omitted, checks if the key set is locked.- Returns:
trueif locked, otherwisefalse.
Checks if a specific key is redacted.
key: The key to check.- Returns:
trueif redacted, otherwisefalse.
Returns the first key associated with a given value.
value: The value to locate.- Returns: The key, or
undefinedif not found.
Returns an iterator that yields the keys of the instance in insertion order.
Locks specific values by key, making them read-only. Does not prevent adding or removing other keys.
...keys: The keys of the values to lock.- Returns:
this.
Locks all current values.
andNew: Iftrue, any new values added later will also be locked.- Returns:
this.
Locks the key set, preventing any additions or deletions of keys.
- Returns:
this.
Returns the last key associated with a given value.
value: The value to locate.- Returns: The key, or
undefinedif not found.
Returns an iterator that yields [key, value] pairs for named entries only.
- Returns: An iterator for named entries.
Returns an iterator that yields the keys of named entries only.
- Returns: An iterator for named keys.
Returns the next available numerical index (equivalent to array.length).
Returns a flat array of [key1, value1, key2, value2, ...].
compact: Iftrue, index keys are returned as numbers instead of strings.- Returns: A flat array of key-value pairs.
Parses a relaxed, "quasi-JSON" string into a NANOS instance or tree of NANOS instances.
str: The string to parse.- Returns: A new NANOS instance.
- The distinction between objects (
{ }) and arrays ([ ]) is ignored. - Values may be separated by commas or spaces.
- Text without special characters need not be quoted.
- Key-value pairs may be separated by
:or=.
Parses a SLID (Static List Data) formatted string into a NANOS instance or tree of NANOS instances.
str: The string to parse.- Returns: A new NANOS instance.
- See the included SLID documentation for details.
Removes and returns the last indexed value.
opts.raw: Iftrue, returns the raw (potentially reactive) value.- Returns: The removed value, or
undefinedif empty.
Appends one or more elements to the end of the instance.
...items: The items to add.- Returns:
this. - If an item is a scalar value, it is added directly.
- If an item is an array or Set, the values in the array or Set are added (preserving any array gaps).
- If an item is a plain object or Map, the key/value entries are added.
- To push an array, object, Map, or Set directly, wrap it in an array (
[value]).
Hides specified values from toString() or toSLID() output.
...keys: Keys to redact. Can also passtrueto redact all values.- Returns:
this.
Reverses the order of all elements in place.
- Returns:
this.
Returns an iterator that yields [key, value] pairs in reverse (last-to-first) key order.
compact: Iftrue, index keys are returned as numbers instead of strings.- Returns: An iterator for all entries in reverse order.
Gets or sets the reactive-interface object for integration with UI frameworks.
Sets a key-value pair. If key is undefined, the next sequential index is used.
key: The key or index.value: The value to set.opts.insert: Iftrue, the new key is inserted instead of appended.opts.raw: Iftrue, sets the value directly without invoking the RIO'sonSethandler.- Returns: The
valuethat was set. - In append mode (the default), new named values are added at the end, and indexed values are added at the last position that preserves ascending index order.
- In insert mode, new named values are inserted at the beginning, and indexed values are added at the first position that preserves ascending index order.
- Example: If the current keys are
['a', '1', 'b', '3', 'c'], named values (with names other thana,b, orc) will be inserted beforeaor appended afterc; a value with index 2 would be inserted beforeb(2 may not appear before 1, so this is the first eligible position in this key-set) or appended afterb(2 may not appear after 3, so this is the last eligible position in this key-set).
Sets a raw value, bypassing any RIO onSet handler. This is a shortcut for .set(key, value, { raw: true }).
key: The key or index.value: The value to set.opts.insert: Iftrue, the new key is inserted instead of appended.- Returns: The
valuethat was set.
Sets (merges) options for the NANOS instance.
options: An object containing options to set.autoReactive: Iftrue, automatically wraps new values in reactives when using an extended RIO.opaqueMaps: Iftrue,Mapobjects are treated as opaque values and not introspected.opaqueSets: Iftrue,Setobjects are treated as opaque values and not introspected.transform: A string that controls how object-like values are handled when being set or pushed. See the Object-Value Transformations section for details.
- Returns:
this. - Alias:
.setOptions()
Fluent interface for setting the reactive-interface object (RIO).
r: The RIO object.- Returns:
this.
Removes and returns the first indexed value.
opts.raw: Iftrue, returns the raw (potentially reactive) value.- Returns: The removed value, or
undefinedif empty.
Returns the total number of key-value pairs.
Creates a new NANOS instance with the same configuration as the current one.
...items: Optional initial items for the new instance.- Returns: A new NANOS instance.
Gets the underlying storage object, which contains the key-value data.
- Returns: The internal storage object.
Returns a new, reversed copy of the instance.
- Returns: A new NANOS instance.
Returns a JSON-representable object for JSON.stringify().
- Returns: An object with
type,next, andpairsproperties.
Generates a SLID (Static List Data) formatted string.
options: An object withcompactandredactbooleans.- Returns: The SLID string.
Generatea a SLID-formatted string for value.
- Returns: The SLID string.
Converts the instance to a SLID string, redacting sensitive data by default.
Prepends one or more elements to the beginning of the instance.
...items: The items to add.- Returns:
this. - See
.pushfor the interpretation ofitems. - Existing indexed items are renumbered to accomodate new indexed items as needed.
- Example:
.unshift(['a', /*gap*/, 'b']);increases current indexes by 3;.at(0)will beaand.at(2)will beb. - Unlike
push, which processes left-to-right,unshiftprocesses right-to-left:
push({k:'a'},{k:'b'}).unshift({k:'c'},{k:'d'}) // k: a->b->d->c (final)
Returns an iterator that yields the (sparse) indexed values in order.
opts.raw: Iftrue, yields raw (potentially reactive) values.
Checks if a given key is a valid, non-negative integer string.
key: The key to check.- Returns:
trueif the key is a valid index.
Checks if a given key is a valid negative integer string.
key: The key to check.- Returns:
trueif the key is a negative index.
| Context | Array | Map | NANOS | Object | Set |
|---|---|---|---|---|---|
| set/named (disabled) | original | original | original | original | original |
| set/named (enabled) | NANOS | NANOS | original | NANOS | NANOS |
| push/unshift (outer) | contents | contents | contents | contents | contents |
| push/unshift (disabled) | original | original | original | original | original |
| push/unshift (sets) | NANOS | contents | original | contents | NANOS |
| push/unshift (all) | NANOS | NANOS | original | NANOS | NANOS |
KEY:
set/named: Applies to .set operations and named (non-positional) values
push/unshift: Applies to .push and .unshift operations
disabled: Applies when the transform option is JavaScript-"falsey"
enabled: Applies when the transform option is JavaScript-"truthy"
sets: Applies when the transform option is sets
all: Applies when the transform option is all
outer: Applies to outer/top-level (as opposed to inner/nested) values:
push(outerValue) versus push([ innerValue ], { key: innerValue })
normal
original: The original object value is used
contents: The object contents are added to/merged with the target NANOS
NANOS: A new NANOS object with equivalent content is used in place of the original value
NOTES:
- In
alltransformation mode, both set-ish (Array and transparent Set) inner values and map-ish (plain Object and transparent Map) inner values are transformed into nested NANOS values. - In
setstransformation mode, set-ish inner values are transformed into nested NANOS values and map-ish inner values are merged into the containing NANOS. - In
setstransformation mode, inner value sparseness is not preserved (e.g., for['first',{key:'value'},'last'],lastis at index 1 of the NANOS, even though it's at index 2 of the input). - "Map"-column entries only apply when transparent (i.e.,
opaqueMapsmode is not enabled) - "Set"-column entries only apply when transparent (i.e.,
opaqueSetsmode is not enabled)
A RIO (reactive interface object) is an interface abstraction object that allows NANOS to potentially work with a variety of different reactive-value implementations.
A basic RIO only handles "packaging" changes and must provide four functions as properties:
Executes the callback function in a reactive batch, deferring dependency recalculations as much as possible during the batch.
callback: The callback function that performs the actions to be done in a batch- Returns: The return value of the callback function.
Called to indicate a state change not directly tracked by other reactive values (e.g. keys added, removed, or locked; changes to the next available index; etc.). This should trigger dependent reactive consumers to reevaluate/rerun.
Called to create a new RIO object instance for additional, automatically-generated, nested NANOS instances.
- Returns: A new RIO object with its own, independent, reactive value.
Called to record a reactive tracking dependency (in other words, any pending reactive computation or effect should be notified when .changed is called).
An extended RIO adds support for value-level reactivity. In addition to the four basic RIO methods, it must also provide the following:
Returns the non-reactive value from a reactive one.
reactiveValue: The reactive value to get the final value of.
Returns true if a value is reactive.
value: The value to check.
Called whenever a value is being set in a NANOS instance. This function should return the raw value to be stored. Typical return values include the unwrapped value if it shouldn't be reactive (i.e. for new keys when nanos.options.autoReactive is not set), a new reactive wrapper around value for new keys, or the existing reactive wrapper updated with value for existing keys.
nanos: The NANOS instance being modified.key: The key being set.value: The value being set.- Returns: The value to be stored.
This project is licensed under the terms specified in the LICENSE file. Copyright 2024-2025 by Kappa Computer Solutions, LLC and Brian Katzung.