Gathering detailed insights and metrics for @wry/tuple
Gathering detailed insights and metrics for @wry/tuple
Gathering detailed insights and metrics for @wry/tuple
Gathering detailed insights and metrics for @wry/tuple
A collection of packages that are probably a little too clever. Use at your own wrisk.
npm install @wry/tuple
Typescript
Module System
Min. Node Version
Node Version
NPM Version
TypeScript (95.93%)
JavaScript (3.87%)
Shell (0.2%)
Total Downloads
0
Last Day
0
Last Week
0
Last Month
0
Last Year
0
MIT License
88 Stars
353 Commits
16 Forks
6 Watchers
15 Branches
8 Contributors
Updated on Apr 25, 2025
Latest Version
0.3.1
Package Id
@wry/tuple@0.3.1
Unpacked Size
17.80 kB
Size
6.38 kB
File Count
11
NPM Version
lerna/4.0.0/node@v16.6.1+x64 (darwin)
Node Version
16.6.1
Cumulative downloads
Total Downloads
Last Day
0%
NaN
Compared to previous day
Last Week
0%
NaN
Compared to previous week
Last Month
0%
NaN
Compared to previous month
Last Year
0%
NaN
Compared to previous year
Immutable finite list objects with constant-time equality testing (===
)
and no hidden memory leaks.
First install the package from npm:
1npm install @wry/tuple
This package has a default export that can be imported using any name, but
is typically named tuple
:
1import assert from "assert"; 2import tuple from "@wry/tuple"; 3 4// Tuples are array-like: 5assert(tuple(1, 2, 3).length === 3); 6assert(tuple("a", "b")[1] === "b"); 7 8// Deeply equal tuples are also === equal! 9assert.strictEqual( 10 tuple(1, tuple(2, 3), 4), 11 tuple(1, tuple(2, 3), 4), 12);
In addition to the default export, @wry/tuple
exports the Tuple
class,
whose Tuple.from
function provides the default export; and the
WeakTrie
class, which you can learn more about by reading its
code.
You probably will not need to use these exports directly:
import tuple, { Tuple, WeakTrie } from "@wry/tuple";
assert(tuple === Tuple.from);
The tuple
function takes any number of arguments and returns a unique,
immutable object that inherits from Tuple.prototype
and is guaranteed to
be ===
any other Tuple
object created from the same sequence of
arguments:
1const obj = { asdf: 1234 }; 2const t1 = tuple(1, "asdf", obj); 3const t2 = tuple(1, "asdf", obj); 4 5assert.strictEqual(t1 === t2, true); 6assert.strictEqual(t1, t2);
A tuple has a fixed numeric length
property, and its elements may
be accessed using array index notation:
1assert.strictEqual(t1.length, 3);
2
3t1.forEach((x, i) => {
4 assert.strictEqual(x, t2[i]);
5});
Since Tuple
objects are just another kind of JavaScript object,
naturally tuples can contain other tuples:
1assert.strictEqual( 2 tuple(t1, t2), 3 tuple(t2, t1) 4); 5 6assert.strictEqual( 7 tuple(1, t2, 3)[1][2], 8 obj 9);
However, because tuples are immutable and always distinct from any of their arguments, it is not possible for a tuple to contain itself, nor to contain another tuple that contains the original tuple, and so forth.
===
equalitySince Tuple
objects are identical when (and only when) their elements
are identical, any two tuples can be compared for equality in constant
time, regardless of how many elements they contain.
This behavior also makes Tuple
objects useful as keys in a Map
, or
elements in a Set
, without any extra hashing or equality logic:
1const map = new Map; 2 3map.set(tuple(1, 12, 3), { 4 author: tuple("Ben", "Newman"), 5 releaseDate: Date.now() 6}); 7 8const version = "1.12.3"; 9const info = map.get(tuple(...version.split(".").map(Number))); 10if (info) { 11 console.log(info.author[1]); // "Newman" 12}
While the identity, number, and order of elements in a tuple
is fixed,
please note that the contents of the individual elements are not frozen in
any way:
1const obj = { asdf: 1234 }; 2tuple(1, "asdf", obj)[2].asdf = "oyez"; 3assert.strictEqual(obj.asdf, "oyez");
Every Tuple
object is array-like and iterable, so ...
spreading and
destructuring work as they should:
1func(...tuple(a, b)); 2func.apply(this, tuple(c, d, e)); 3 4assert.deepEqual( 5 [1, ...tuple(2, 3), 4], 6 [1, 2, 3, 4] 7); 8 9assert.strictEqual( 10 tuple(1, ...tuple(2, 3), 4), 11 tuple(1, 2, 3, 4) 12); 13 14const [a, [_, b]] = tuple(1, tuple(2, 3), 4); 15assert.strictEqual(a, 1); 16assert.strictEqual(b, 3);
Any data structure that guarantees ===
equality based on structural equality must maintain some sort of internal pool of previously encountered instances.
Implementing such a pool for tuple
s is fairly straightforward (though feel free to give it some thought before reading this code, if you like figuring things out for yourself):
1const pool = new Map; 2 3function tuple(...items) { 4 let node = pool; 5 6 items.forEach(item => { 7 let child = node.get(item); 8 if (!child) node.set(item, child = new Map); 9 node = child; 10 }); 11 12 // If we've created a tuple instance for this sequence of elements before, 13 // return that instance again. Otherwise create a new immutable tuple instance 14 // with the same (frozen) elements as the items array. 15 return node.tuple || (node.tuple = Object.create( 16 tuple.prototype, 17 Object.getOwnPropertyDescriptors(Object.freeze(items)) 18 )); 19}
This implementation is pretty good, because it requires only linear time (O(items.length
)) to determine if a tuple
has been created previously for the given items
, and you can't do better than linear time (asymptotically speaking) because you have to look at all the items.
This code is also useful as an illustration of exactly how the tuple
constructor behaves, in case you weren't satisfied by my examples in the previous section.
The simple implementation above has a serious problem: in a
garbage-collected language like JavaScript, the pool
itself will retain
references to all Tuple
objects ever created, which prevents Tuple
objects and their elements (which can be arbitrarily large) from ever
being reclaimed by the garbage collector, even after they become
unreachable by any other means. In other words, storing objects in this
kind of Tuple
would inevitably cause memory leaks.
To solve this problem, it's tempting to try changing Map
to
WeakMap
here:
1const pool = new WeakMap;
and here:
1if (!child) node.set(item, child = new WeakMap);
This approach is appealing because a WeakMap
should allow its keys to be
reclaimed by the garbage collector. That's the whole point of a WeakMap
,
after all. Once a tuple
becomes unreachable because the program has
stopped using it anywhere else, its elements are free to disappear from
the pool of WeakMap
s whenever they too become unreachable. In other
words, something like a WeakMap
is exactly what we need here.
Unfortunately, this strategy stumbles because a tuple
can contain
primitive values as well as object references, whereas a WeakMap
only
allows keys that are object references.
In other words, node.set(item, ...)
would fail whenever item
is not an
object, if node
is a WeakMap
. To see how the @wry/tuple
library
cleverly gets around this WeakMap
limitation, have a look at
this module.
No vulnerabilities found.
Reason
no dangerous workflow patterns detected
Reason
no binaries found in the repo
Reason
license file detected
Details
Reason
Found 3/13 approved changesets -- score normalized to 2
Reason
detected GitHub workflow tokens with excessive permissions
Details
Reason
0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Reason
dependency not pinned by hash detected -- score normalized to 0
Details
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
security policy file not detected
Details
Reason
project is not fuzzed
Details
Reason
branch protection not enabled on development/release branches
Details
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
Reason
19 existing vulnerabilities detected
Details
Score
Last Scanned on 2025-07-07
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More