tuplerone 4.0.0-next.4 → 4.0.0-next.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,279 +1,72 @@
1
1
  <h1 align="center"><a href="https://github.com/slikts/tuplerone"><img width="550" src="https://raw.githubusercontent.com/slikts/tuplerone/master/logo.svg?sanitize=true" alt="tuplerone"></a></h1>
2
2
 
3
3
  <p align="center">
4
- <a href="https://www.npmjs.com/package/tuplerone"><img src="https://img.shields.io/npm/v/tuplerone.svg" alt="Latest Stable Version"></a>
5
- <a href="https://coveralls.io/github/slikts/tuplerone"><img src="https://img.shields.io/coveralls/slikts/tuplerone.svg" alt="Coveralls"></a>
6
- <a href="https://github.com/semantic-release/semantic-release"><img src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg" alt="semantic-release"></a>
7
- <a href="https://github.com/slikts/tuplerone"><img src="https://img.shields.io/github/license/slikts/tuplerone.svg" alt="License"></a>
4
+ <a href="https://img.shields.io/npm/v/tuplerone.svg?style=flat"><img src="https://img.shields.io/npm/v/tuplerone.svg?style=flat" alt="View this project on npm"></a>
8
5
  </p>
9
6
 
10
7
  <p align="center">A lightweight, efficient tuple and value object implementation for JavaScript and TypeScript.</p>
11
8
 
12
9
  ---
13
10
 
14
- A quick reminder about what tuples are (using Python):
11
+ In JavaScript, objects with the same contents aren't equal:
15
12
 
16
- ```python
17
- (1, 2, 3) == (1, 2, 3) # → True
18
- ```
19
-
20
- A JavaScript version of something similar looks like this:
21
-
22
- ```js
23
- '[1,2,3]' === '[1,2,3]'; // → true
24
- ```
25
-
26
- Except it's using a string and would need to be unserialized with `JSON.parse()` to allow accessing the separate members. Moreover, JSON is limited in what values can be serialized.
27
-
28
- You could alternatively use `"1,2,3"` and `String.split(",")`, but it's also not very convenient. Just using an array doesn't work:
29
-
30
- ```js
13
+ ```ts
31
14
  [1, 2, 3] === [1, 2, 3]; // → false
32
15
  ```
33
16
 
34
- Each JavaScript array is a different object and so its value is the reference to that object. Tuples are a way to make that reference the same if the array members are the same. Using Tuplerone:
35
-
36
- ```js
37
- Tuple(1, 2, 3) === Tuple(1, 2, 3);
38
- ```
39
-
40
- Example use case for tuples is dealing with memoization like React's [`memo()`][memo] or `PureComponent`, since you can pass lists as props to components without forcing re-renders or manually caching the list. It's also useful for using multiple values as keys with `Map()`. In general, it's just a nice thing to have in your toolbox.
41
-
42
- **[Try Tuplerone in a sandbox][sandbox]**
17
+ Tuplerone fixes this. The same arguments always produce the same object reference, so `===` just works:
43
18
 
44
- ---
45
-
46
- This library is:
47
-
48
- - _tiny_ (bundle size is [under one kilobyte][tiny] compressed), with no dependencies
49
- - _well-typed_ using TypeScript (but can still be used from JavaScript, of course)
50
- - _well-tested_ with full coverage
51
- - _efficient_ using an ES2015 [`WeakMap`][weakmap]-based directed acyclic graph for lookups
52
-
53
- The `Tuple` objects are:
54
-
55
- - _immutable_ – properties cannot be added, removed or changed, and it's enforced with [`Object.freeze()`][frozen]
56
- - [array-like] – tuple members can be accessed by indexing, and there's a `length` property, but no `Array` prototype methods
57
- - [iterable] – tuple members can be iterated over, for example, using [`for-of`][for-of] loops or spread syntax
58
-
59
- There exists a [stage-1 proposal][proposal] for adding a tuple type to JavaScript and a [different stage-1 proposal][composite] for adding a more limited value-semantic type.
60
-
61
- ## Theory
62
-
63
- [Tuples] are **finite ordered sequences of values** that serve two main purposes in programming languages:
64
-
65
- - grouping together heterogenous (mixed) data types within a static type system (this doesn't apply to a dynamically typed language like JavaScript)
66
- - simplifying value-semantic comparisons of lists, which is what this library is mainly about
67
-
68
- ### Value semantics
69
-
70
- A simple way to explain value semantics is to look at the difference between primitive values (like numbers and strings) and object values in JavaScript. Primitives are value-semantic by default,
71
- meaning that the default comparison methods (`==`, `===` and `Object.is()`) compare primitive values by their contents, so, for example, any string is equal to any other string created with the same contents:
72
-
73
- ```js
74
- 'abc' === 'abc'; // → true, because both string literals create a value with the same contents
75
- ```
76
-
77
- The contents of primitive values are also immutable (can't change at runtime), so the results of comparing primitive value equality can't be invalidated by the contents of the values changing.
78
-
79
- Meanwhile, each object value (instance) in JavaScript has a unique identity, so each instance is only equal to itself and not any other instances:
80
-
81
- ```js
82
- [1, 2, 3] === [1, 2, 3]; // → false, because both array literals create separate array instances
83
- ```
84
-
85
- Objects by default can't be thought of as their contents since the contents can change, and this is called reference semantics, since objects essentially represent a place in memory. The downside is that it makes reasoning about a program harder, since the programmer has to consider potential changes.
86
-
87
- A more direct practical consequence of reference semantics is that comparing instances requires _deep comparisons_, such as [`_.isEqual()`][isequal] in lodash or serializing the object values to JSON:
88
-
89
- ```js
90
- let a = [1, 2, 3];
91
- let b = [1, 2, 3];
92
- let result = JSON.stringify(a) === JSON.stringify(b); // → true, because it's a deep comparison
93
- a.push(4); // a and b contents are now different, so the cached comparison result is invalid
94
- ```
95
-
96
- Deep comparison results can't be reliably cached since the compared instances can change, and it's also less efficient than just being able to use `===` directly. An another thing that's not possible with reference semantics is combining different values to use as a composite key (such as with `Map` or `WeakMap`).
97
-
98
- ### Directed acyclic graphs
99
-
100
- Directed acyclic graphs (DAGs) are a data structure that allows efficiently mapping a sequence of values to a unique object containing them, which is how this library is implemented. Specifically, it uses a `WeakMap` object (optionally a `Map` as well if mapping primitives) for each node, and the nodes are re-used for overlapping paths in the graph. Map access has constant time complexity, so the number of tuples created doesn't slow down access speed. Using `WeakMap` ensures that if the values used to create the tuple are dereferenced, the tuple object gets garbage collected.
101
-
102
- ## Installation
103
-
104
- ```sh
105
- npm install tuplerone
106
- ```
107
-
108
- ```sh
109
- yarn add tuplerone
110
- ```
111
-
112
- ```js
113
- import { Tuple } from 'https://cdn.skypack.dev/tuplerone';
114
- ```
115
-
116
- ```html
117
- <script src="https://unpkg.com/tuplerone/dist/tuplerone.umd.js"></script>
118
- ```
119
-
120
- ## Usage
121
-
122
- ### `Tuple(…values)`
123
-
124
- ```js
19
+ ```ts twoslash
125
20
  import { Tuple } from 'tuplerone';
126
21
 
127
- // Dummy objects
128
- const a = Object('a');
129
- const b = Object('b');
130
- const c = Object('c');
22
+ const a = { id: 1 };
23
+ const b = { id: 2 };
131
24
 
132
- // Structural equality testing using the identity operator
133
- Tuple(a, b, c) === Tuple(a, b, c); // → true
134
- Tuple(a, b) === Tuple(b, a); // → false
25
+ Tuple(a, b) === Tuple(a, b); // true
135
26
 
136
- // Mapping using a pair of values as key
27
+ // Use as composite Map key
137
28
  const map = new Map();
138
- map.set(Tuple(a, b), 123).get(Tuple(a, b)); // → 123
139
-
140
- // Nesting tuples
141
- Tuple(a, Tuple(b, c)) === Tuple(a, Tuple(b, c)); // → true
142
-
143
- // Using primitive values
144
- Tuple(1, 'a', a); // → Tuple(3) [1, "a", Object("a")]
145
-
146
- // Indexing
147
- Tuple(a, b)[1]; // → Object("b")
148
-
149
- // Checking arity
150
- Tuple(a, b).length; // → 2
151
-
152
- // Failing to mutate
153
- Tuple(a, b)[0] = c; // throws an error
154
- ```
155
-
156
- The tuple function caches or memoizes its arguments to produce the same tuple object for the same arguments.
157
-
158
- ### Types
159
-
160
- The library is well-typed using TypeScript:
161
-
162
- ```ts
163
- import { Tuple, Tuple0, Tuple1, Tuple2 } from 'tuplerone';
164
-
165
- // Dummy object for use as key
166
- const o = {};
167
-
168
- const tuple0: Tuple0 = Tuple(); // 0-tuple
169
- const tuple1: Tuple1<typeof o> = Tuple(o); // 1-tuple
170
- const tuple2: Tuple2<typeof o, number> = Tuple(o, 1); // 2-tuple
171
-
172
- Tuple(o) === Tuple(o, 1); // TS compile error due to different arities
173
-
174
- // Spreading a TypeScript tuple:
175
- Tuple(...([1, 2, 3] as const)); // → Tuple3<1, 2, 3>
176
- ```
177
-
178
- In editors like VS Code, the type information is also available when the library is consumed as JavaScript.
179
-
180
- ### `CompositeSymbol(…values)`
181
-
182
- It's possible to avoid creating an `Array`-like tuple for cases where iterating the tuple members isn't needed (for example, just to use it as a key):
183
-
184
- ```js
185
- import { CompositeSymbol } from 'tuplerone';
186
-
187
- typeof CompositeSymbol(1, 2, {}) === 'symbol'; // → true
188
- ```
189
-
190
- A symbol is more space efficient than a tuple and can be used as a key for plain objects.
191
-
192
- ### `ValueObject(object)`
193
-
194
- Tuplerone also includes a simple [value object] implementation:
195
-
196
- ```js
197
- import { ValueObject } from 'tuplerone';
198
-
199
- ValueObject({ a: 1, { b: { c: 2 } }}) === ValueObject({ a: 1, { b: { c: 2 } }}); // → true
200
- ```
201
-
202
- Note that the passed objects are frozen with [`Object.freeze()`][frozen].
203
-
204
- ## Caveats
205
-
206
- Since this is a userspace implementation, there are a number of limitations.
207
-
208
- ### At least one member must be an object to avoid memory leaks
209
-
210
- Due to `WeakMap` being limited to using objects as keys, there must be at least one member of a tuple with the object type, or the tuples would leak memory. Trying to create tuples with only primitive members will throw an error.
211
-
212
- ```ts
213
- Tuple(1, 2); // throws TypeError
214
- Tuple(1, 2, {}); // works
215
- ```
216
-
217
- `WeakMap` is an ES2015 feature which is difficult to polyfill (the [polyfills][polyfill] don't support frozen objects), but this applies less to environments like node or browser extensions.
218
-
219
- #### `UnsafeTuple`
220
-
221
- There is an `UnsafeTuple` type for advanced use cases where the values not being garbage-collectable is acceptable, so it doesn't require having an object member:
222
-
223
- ```js
224
- import { UnsafeTuple as Tuple } from 'tuplerone';
225
-
226
- Tuple(1, 2, 3) === Tuple(1, 2, 3); // → true
29
+ map.set(Tuple(a, b), 'pair');
30
+ map.get(Tuple(a, b)); // → 'pair'
227
31
  ```
228
32
 
229
- ### Can't be compared with operators like `<` or `>`
230
-
231
- tuplerone tuples are not supported by the relation comparison operators like `<`, whereas in a language like Python the following (comparing tuples by arity) would evaluate to true: `(1,) < (1, 2)`.
232
-
233
- ### `Array`-like but there's no `Array` prototypes methods
33
+ Useful for memoization (e.g. React's [`memo()`][memo]), composite `Map` keys, and anywhere you need structural equality without deep comparison.
234
34
 
235
- Tuples subclass `Array`:
35
+ ## Features
236
36
 
237
- ```typescript
238
- Array.isArray(Tuple()); // true
239
- ```
240
-
241
- Yet tuples don't support mutative `Array` prototype methods like `Array.sort()`, since tuples are frozen.
242
-
243
- The advantage of subclassing `Array` is ergonomic console representation (it's represented as an array would be), which is based on `Array.isArray()` and so requires subclassing `Array`.
37
+ - **Tiny**—[under 1KB][bundlephobia] compressed, zero dependencies
38
+ - **Well-typed**—full TypeScript support (works from JavaScript too)
39
+ - **Well-tested**—full test coverage
40
+ - **Efficient**—[`WeakMap`][weakmap]-based directed acyclic graph for lookups
41
+ - **Immutable**—tuples are frozen with [`Object.freeze()`][frozen]
42
+ - **Iterable**—supports [`for-of`][for-of], spread, and destructuring
244
43
 
245
- ### Limited number of arities
44
+ ## Documentation
246
45
 
247
- The tuples are currently typed up to 8-tuple (octuple) because TypeScript doesn't yet support [variadic generics]. The types are implemented using function overloads.
46
+ **[Read the full documentation][docs]**
248
47
 
249
- ### `instanceof` doesn't work as expected
48
+ - [Getting Started][docs]
49
+ - [Tuple][docs-tuple]
50
+ - [CompositeSymbol][docs-composite-symbol]
51
+ - [ValueObject][docs-value-object]
52
+ - [Theory & Internals][docs-theory]
53
+ - [Caveats][docs-caveats]
250
54
 
251
- Tuples can be constructed without the `new` keyword to make them behave like other primitive values
252
- (`Symbol`, `Boolean`, `String`, `Number`) that also don't require `new` and also are value-semantic. This means that `instanceof` doesn't work the same as for other objects, but can still be used like so:
55
+ ## Related
253
56
 
254
- ```js
255
- Tuple() instanceof Tuple.constructor; // → true
256
- ```
57
+ - [TC39 Record & Tuple proposal][proposal]
58
+ - [TC39 Composite Keys proposal][composite]
257
59
 
60
+ [memo]: https://react.dev/reference/react/memo
258
61
  [weakmap]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap
259
- [map]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
260
- [tuples]: https://en.wiktionary.org/wiki/tuple
261
- [isequal]: https://lodash.com/docs/4.17.10#isEqual
262
62
  [frozen]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze
263
- [composite]: https://github.com/bmeck/proposal-richer-keys/tree/master/compositeKey
264
- [iterable]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#The_iterable_protocol
265
- [tuple]: https://en.wiktionary.org/wiki/tuple
266
- [array-like]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Indexed_collections#Working_with_array-like_objects
267
63
  [for-of]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of
268
- [tiny]: https://bundlephobia.com/result?p=tuplerone
269
- [polyfill]: https://github.com/medikoo/es6-weak-map#readme
270
- [value semantics]: https://en.wikipedia.org/wiki/Value_semantics
271
- [value types]: https://en.wikipedia.org/wiki/Value_type_and_reference_type
272
- [isequal]: https://lodash.com/docs/#isEqual
64
+ [bundlephobia]: https://bundlephobia.com/result?p=tuplerone
273
65
  [proposal]: https://github.com/tc39/proposal-record-tuple
274
- [memo]: https://reactjs.org/docs/react-api.html#reactmemo
275
- [variadic generics]: https://github.com/microsoft/TypeScript/issues/5453
276
- [sandbox]: https://codesandbox.io/s/tuplerone-dm90w?expanddevtools=1
277
- [value object]: https://en.wikipedia.org/wiki/Value_object
278
- [composite-symbol]: https://github.com/loilo/composite-symbol
279
- [richer-keys]: https://github.com/tc39/proposal-richer-keys
66
+ [composite]: https://github.com/bmeck/proposal-richer-keys/tree/master/compositeKey
67
+ [docs]: https://slikts.github.io/tuplerone/
68
+ [docs-tuple]: https://slikts.github.io/tuplerone/docs/usage/tuple
69
+ [docs-composite-symbol]: https://slikts.github.io/tuplerone/docs/usage/composite-symbol
70
+ [docs-value-object]: https://slikts.github.io/tuplerone/docs/usage/value-object
71
+ [docs-theory]: https://slikts.github.io/tuplerone/docs/theory
72
+ [docs-caveats]: https://slikts.github.io/tuplerone/docs/caveats
@@ -0,0 +1,83 @@
1
+ //#region src/types.d.ts
2
+ type TupleN<T extends readonly unknown[]> = Tuple<T[number]> & Readonly<T>;
3
+ interface Tuple0 extends Tuple<never> {
4
+ readonly length: 0;
5
+ }
6
+ /** Singleton */
7
+ type Tuple1<A> = TupleN<[A]>;
8
+ /** Pair */
9
+ type Tuple2<A, B> = TupleN<[A, B]>;
10
+ /** Triple */
11
+ type Tuple3<A, B, C> = TupleN<[A, B, C]>;
12
+ /** Quadruple */
13
+ type Tuple4<A, B, C, D> = TupleN<[A, B, C, D]>;
14
+ /** Quintuple */
15
+ type Tuple5<A, B, C, D, E> = TupleN<[A, B, C, D, E]>;
16
+ /** Sextuple */
17
+ type Tuple6<A, B, C, D, E, F> = TupleN<[A, B, C, D, E, F]>;
18
+ /** Septuple */
19
+ type Tuple7<A, B, C, D, E, F, G> = TupleN<[A, B, C, D, E, F, G]>;
20
+ /** Octuple */
21
+ type Tuple8<A, B, C, D, E, F, G, H> = TupleN<[A, B, C, D, E, F, G, H]>;
22
+ type CompositeSymbol<T extends readonly unknown[]> = {
23
+ t: T;
24
+ } & symbol;
25
+ declare const CompositeSymbol0: CompositeSymbol<readonly []>;
26
+ type CompositeSymbol1<A> = CompositeSymbol<[A]>;
27
+ type CompositeSymbol2<A, B> = CompositeSymbol<[A, B]>;
28
+ type CompositeSymbol3<A, B, C> = CompositeSymbol<[A, B, C]>;
29
+ type CompositeSymbol4<A, B, C, D> = CompositeSymbol<[A, B, C, D]>;
30
+ type CompositeSymbol5<A, B, C, D, E> = CompositeSymbol<[A, B, C, D, E]>;
31
+ type CompositeSymbol6<A, B, C, D, E, F> = CompositeSymbol<[A, B, C, D, E, F]>;
32
+ type CompositeSymbol7<A, B, C, D, E, F, G> = CompositeSymbol<[A, B, C, D, E, F, G]>;
33
+ type CompositeSymbol8<A, B, C, D, E, F, G, H> = CompositeSymbol<[A, B, C, D, E, F, G, H]>;
34
+ //#endregion
35
+ //#region src/Tuple.d.ts
36
+ declare class Tuple<A> extends Array<A> implements ArrayLike<A>, Iterable<A> {
37
+ [i: number]: A;
38
+ length: number;
39
+ /**
40
+ * @throws {TypeError} Will throw if called non-locally; use the tuple() method instead.
41
+ */
42
+ constructor(iterable: Iterable<A>, confirm: typeof localToken);
43
+ /**
44
+ * Constructs a tuple.
45
+ */
46
+ static tuple<const T extends readonly unknown[]>(...values: T): TupleN<T>;
47
+ static symbol<const T extends readonly unknown[]>(...values: T): CompositeSymbol<T>;
48
+ static unsafe(...values: unknown[]): unknown;
49
+ static unsafeSymbol(...values: unknown[]): unknown;
50
+ }
51
+ declare const localToken: unique symbol;
52
+ declare const tuple: typeof Tuple.tuple, symbol: typeof Tuple.symbol, unsafe: typeof Tuple.unsafe, unsafeSymbol: typeof Tuple.unsafeSymbol;
53
+ //#endregion
54
+ //#region src/DeepCompositeSymbol.d.ts
55
+ /**
56
+ * Recursively creates a "composite key" (like a "value identity") for
57
+ * an object's entries (key-value pairs).
58
+ */
59
+ declare const DeepCompositeSymbol: (object: object, filter?: (entry: [string, unknown]) => boolean) => unknown;
60
+ //#endregion
61
+ //#region src/ValueObject.d.ts
62
+ type DeepReadonly<A> = { readonly [P in keyof A]: A[P] extends object ? DeepReadonly<A[P]> : A[P] };
63
+ /**
64
+ * Works somewhat similarly to Record in the Record & Tuple proposal:
65
+ * https://github.com/tc39/proposal-record-tuple
66
+ */
67
+ declare function ValueObject<A extends object>(object: A, filter?: (entry: [string, unknown]) => boolean): DeepReadonly<A>;
68
+ //#endregion
69
+ //#region src/memoize.d.ts
70
+ type Fn = (this: unknown, ...args: never[]) => unknown;
71
+ declare const memoize: <A extends Fn>(fn: A, cache?: WeakMap<object, unknown>) => A;
72
+ //#endregion
73
+ //#region src/tuplerone.d.ts
74
+ /**
75
+ * A tuple whose members are allowed to all be primitive,
76
+ * so it can't be garbage-collected and should only be used
77
+ * in advanced contexts.
78
+ */
79
+ declare const UnsafeTuple: typeof tuple;
80
+ declare const UnsafeCompositeSymbol: typeof tuple;
81
+ //#endregion
82
+ export { symbol as CompositeSymbol, type CompositeSymbol0, type CompositeSymbol1, type CompositeSymbol2, type CompositeSymbol3, type CompositeSymbol4, type CompositeSymbol5, type CompositeSymbol6, type CompositeSymbol7, type CompositeSymbol8, type CompositeSymbol as CompositeSymbolType, DeepCompositeSymbol, tuple as Tuple, type Tuple0, type Tuple1, type Tuple2, type Tuple3, type Tuple4, type Tuple5, type Tuple6, type Tuple7, type Tuple8, type TupleN, UnsafeCompositeSymbol, UnsafeTuple, ValueObject, memoize };
83
+ //# sourceMappingURL=tuplerone.d.mts.map