@radically-straightforward/utilities 0.0.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
package/CHANGELOG.md CHANGED
@@ -1 +1,13 @@
1
1
  # Changelog
2
+
3
+ ## 0.0.3 · 2024-01-05
4
+
5
+ - Made `intern()` more strict in terms of types and provide auxiliary types for it.
6
+
7
+ ## 0.0.2 · 2024-01-05
8
+
9
+ - **Breaking Change:** Modified `intern()` to be shallow, which may be easier to reason about and faster.
10
+
11
+ ## 0.0.1 · 2024-01-04
12
+
13
+ - Preliminary release with `intern()`.
package/README.md CHANGED
@@ -11,27 +11,61 @@ $ npm install @radically-straightforward/utilities
11
11
  ## Usage
12
12
 
13
13
  ```typescript
14
- import * as node from "@radically-straightforward/utilities";
14
+ import * as utilities from "@radically-straightforward/utilities";
15
15
  ```
16
16
 
17
17
  <!-- DOCUMENTATION START: ./source/index.mts -->
18
18
 
19
+ ### `Intern`
20
+
21
+ ```typescript
22
+ export type Intern<Type> = Readonly<
23
+ Type & {
24
+ [internSymbol]: true;
25
+ }
26
+ >;
27
+ ```
28
+
29
+ Utility type for `intern()`.
30
+
31
+ ### `InternInnerValue`
32
+
33
+ ```typescript
34
+ export type InternInnerValue =
35
+ | string
36
+ | number
37
+ | bigint
38
+ | boolean
39
+ | symbol
40
+ | undefined
41
+ | null
42
+ | Intern<unknown>;
43
+ ```
44
+
45
+ Utility type for `intern()`.
46
+
19
47
  ### `intern()`
20
48
 
21
49
  ```typescript
22
- export function intern<T extends WeakKey>(value: T): T;
50
+ export function intern<
51
+ T extends
52
+ | Array<InternInnerValue>
53
+ | {
54
+ [key: string]: InternInnerValue;
55
+ },
56
+ >(value: T): Intern<T>;
23
57
  ```
24
58
 
25
59
  [Interning](<https://en.wikipedia.org/wiki/Interning_(computer_science)>) a value makes it unique across the program, which is useful for checking equality with `===` (reference equality), using it as a key in a `Map`, adding it to a `Set`, and so forth:
26
60
 
27
- ```javascript
61
+ ```typescript
28
62
  import { intern as $ } from "@radically-straightforward/utilities";
29
63
 
30
64
  [1] === [1]; // => false
31
65
  $([1]) === $([1]); // => true
32
66
 
33
67
  {
34
- const map = new Map();
68
+ const map = new Map<number[], number>();
35
69
  map.set([1], 1);
36
70
  map.set([1], 2);
37
71
  map.size; // => 2
@@ -39,7 +73,7 @@ $([1]) === $([1]); // => true
39
73
  }
40
74
 
41
75
  {
42
- const map = new Map();
76
+ const map = new Map<utilities.Intern<number[]>, number>();
43
77
  map.set($([1]), 1);
44
78
  map.set($([1]), 2);
45
79
  map.size; // => 1
@@ -47,7 +81,7 @@ $([1]) === $([1]); // => true
47
81
  }
48
82
 
49
83
  {
50
- const set = new Set();
84
+ const set = new Set<number[]>();
51
85
  set.add([1]);
52
86
  set.add([1]);
53
87
  set.size; // => 2
@@ -55,7 +89,7 @@ $([1]) === $([1]); // => true
55
89
  }
56
90
 
57
91
  {
58
- const set = new Set();
92
+ const set = new Set<utilities.Intern<number[]>>();
59
93
  set.add($([1]));
60
94
  set.add($([1]));
61
95
  set.size; // => 1
@@ -65,23 +99,23 @@ $([1]) === $([1]); // => true
65
99
 
66
100
  > **Note:** We recommend that you alias `intern as $` when importing it to make your code less noisy.
67
101
 
68
- > **Note:** The default notion of equality used to intern values is [Lodash’s `isEqual()`](https://lodash.com/docs/4.17.15#isEqual). You may change that by overriding `intern.isEqual: (value: any, other: any) => boolean` before using `intern()` for the first time.
69
- >
70
- > In particular, note that `intern()` uses a notion of equality that is deep: it compares, for example, objects within objects by value. This is more ergonomic, because it means that you only have to call `intern()` on the outer object, for example, `$({ a: { b: 2 } })` instead of `$({ a: $({ b: 2 }) })`. But this is slower.
71
- >
72
- > You may replace the notion of equality with shallow equality and use the `$({ a: $({ b: 2 }) })` pattern to speed things up. That is, for example, [what React does](https://legacy.reactjs.org/docs/react-api.html#reactpurecomponent). It’s also what the [**JavaScript Records & Tuples Proposal**](https://github.com/tc39/proposal-record-tuple) includes as of January 2024, so it may make your code easier to adapt in the future.
102
+ > **Node:** Inner values must be either primitives or interned values themselves, for example, `$([1, $({})])` is valid, but `$([1, {}])` is not.
103
+
104
+ > **Node:** Currently only arrays (tuples) and objects (records) may be interned. In the future we may support more types, for example, `Map`, `Set`, regular expressions, and so forth.
73
105
 
74
- > **Note:** You must not mutate an interned value. Interned values are deeply [frozen](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) with [`deep-freeze-es6`](https://npm.im/deep-freeze-es6) to prevent you from mutating them.
106
+ > **Note:** You must not mutate an interned value. Interned values are [frozen](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) to prevent mutation.
75
107
 
76
108
  > **Note:** Interning a value is a costly operation which grows more expensive as you intern more values. Only intern values when really necessary.
77
109
 
78
- > **Note:** The pool of interned values is available as `intern.pool: Map<Symbol, WeakRef<any>>`. There’s a `FinalizationRegistry` at `intern.finalizationRegistry` that cleans up values that have been garbage collected.
110
+ > **Note:** Interned objects do not preserve the order of the attributes: `$({ a: 1, b: 2 }) === $({ b: 2, a: 1 })`.
111
+
112
+ > **Note:** The pool of interned values is available as `intern.pool`. There’s a `FinalizationRegistry` at `intern.finalizationRegistry` that cleans up interned values that have been garbage collected.
79
113
 
80
114
  **Related Work**
81
115
 
82
116
  **[JavaScript Records & Tuples Proposal](https://github.com/tc39/proposal-record-tuple)**
83
117
 
84
- A proposal to include immutable objects (Records) and immutable arrays (Tuples) in JavaScript. This subsumes most of the need for `intern()` even though it doesn’t cover `Map`s, `Set`s, regular expressions, and so forth.
118
+ A proposal to include immutable objects (Records) and immutable arrays (Tuples) in JavaScript. This subsumes most of the need for `intern()`.
85
119
 
86
120
  It includes a [polyfill](https://github.com/bloomberg/record-tuple-polyfill) which works very similarly to `intern()` but requires different functions for different data types.
87
121
 
@@ -123,10 +157,4 @@ Similar to `collections-deep-equal` but either incomplete, or lacking type defin
123
157
  - <https://twitter.com/swannodette/status/1067962983924539392>
124
158
  - <https://gist.github.com/modernserf/c000e62d40f678cf395e3f360b9b0e43>
125
159
 
126
- **Implementation Notes**
127
-
128
- - Besides [Lodash’s `isEqual()`](https://lodash.com/docs/4.17.15#isEqual) we also considered defaulting to [Node.js’s notion of deep equality](https://nodejs.org/dist/latest-v21.x/docs/api/util.html#utilisdeepstrictequalval1-val2) with the [`deep-equal`](https://npm.im/package/deep-equal) polyfill for the browser.
129
-
130
- - Besides [`deep-freeze-es6`](https://npm.im/deep-freeze-es6) we also considered doing the deep freezing with [`deep-freeze-strict`](https://npm.im/deep-freeze-strict), [`deep-freeze-node`](https://npm.im/deep-freeze-node), and [`deep-freeze`](https://npm.im/deep-freeze).
131
-
132
160
  <!-- DOCUMENTATION END: ./source/index.mts -->
package/build/index.d.mts CHANGED
@@ -1,14 +1,24 @@
1
+ /**
2
+ * Utility type for `intern()`.
3
+ */
4
+ export type Intern<Type> = Readonly<Type & {
5
+ [internSymbol]: true;
6
+ }>;
7
+ /**
8
+ * Utility type for `intern()`.
9
+ */
10
+ export type InternInnerValue = string | number | bigint | boolean | symbol | undefined | null | Intern<unknown>;
1
11
  /**
2
12
  * [Interning](<https://en.wikipedia.org/wiki/Interning_(computer_science)>) a value makes it unique across the program, which is useful for checking equality with `===` (reference equality), using it as a key in a `Map`, adding it to a `Set`, and so forth:
3
13
  *
4
- * ```javascript
14
+ * ```typescript
5
15
  * import { intern as $ } from "@radically-straightforward/utilities";
6
16
  *
7
17
  * [1] === [1]; // => false
8
18
  * $([1]) === $([1]); // => true
9
19
  *
10
20
  * {
11
- * const map = new Map();
21
+ * const map = new Map<number[], number>();
12
22
  * map.set([1], 1);
13
23
  * map.set([1], 2);
14
24
  * map.size; // => 2
@@ -16,7 +26,7 @@
16
26
  * }
17
27
  *
18
28
  * {
19
- * const map = new Map();
29
+ * const map = new Map<utilities.Intern<number[]>, number>();
20
30
  * map.set($([1]), 1);
21
31
  * map.set($([1]), 2);
22
32
  * map.size; // => 1
@@ -24,7 +34,7 @@
24
34
  * }
25
35
  *
26
36
  * {
27
- * const set = new Set();
37
+ * const set = new Set<number[]>();
28
38
  * set.add([1]);
29
39
  * set.add([1]);
30
40
  * set.size; // => 2
@@ -32,7 +42,7 @@
32
42
  * }
33
43
  *
34
44
  * {
35
- * const set = new Set();
45
+ * const set = new Set<utilities.Intern<number[]>>();
36
46
  * set.add($([1]));
37
47
  * set.add($([1]));
38
48
  * set.size; // => 1
@@ -42,23 +52,23 @@
42
52
  *
43
53
  * > **Note:** We recommend that you alias `intern as $` when importing it to make your code less noisy.
44
54
  *
45
- * > **Note:** The default notion of equality used to intern values is [Lodash’s `isEqual()`](https://lodash.com/docs/4.17.15#isEqual). You may change that by overriding `intern.isEqual: (value: any, other: any) => boolean` before using `intern()` for the first time.
46
- * >
47
- * > In particular, note that `intern()` uses a notion of equality that is deep: it compares, for example, objects within objects by value. This is more ergonomic, because it means that you only have to call `intern()` on the outer object, for example, `$({ a: { b: 2 } })` instead of `$({ a: $({ b: 2 }) })`. But this is slower.
48
- * >
49
- * > You may replace the notion of equality with shallow equality and use the `$({ a: $({ b: 2 }) })` pattern to speed things up. That is, for example, [what React does](https://legacy.reactjs.org/docs/react-api.html#reactpurecomponent). It’s also what the [**JavaScript Records & Tuples Proposal**](https://github.com/tc39/proposal-record-tuple) includes as of January 2024, so it may make your code easier to adapt in the future.
55
+ * > **Node:** Inner values must be either primitives or interned values themselves, for example, `$([1, $({})])` is valid, but `$([1, {}])` is not.
56
+ *
57
+ * > **Node:** Currently only arrays (tuples) and objects (records) may be interned. In the future we may support more types, for example, `Map`, `Set`, regular expressions, and so forth.
50
58
  *
51
- * > **Note:** You must not mutate an interned value. Interned values are deeply [frozen](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) with [`deep-freeze-es6`](https://npm.im/deep-freeze-es6) to prevent you from mutating them.
59
+ * > **Note:** You must not mutate an interned value. Interned values are [frozen](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) to prevent mutation.
52
60
  *
53
61
  * > **Note:** Interning a value is a costly operation which grows more expensive as you intern more values. Only intern values when really necessary.
54
62
  *
55
- * > **Note:** The pool of interned values is available as `intern.pool: Map<Symbol, WeakRef<any>>`. There’s a `FinalizationRegistry` at `intern.finalizationRegistry` that cleans up values that have been garbage collected.
63
+ * > **Note:** Interned objects do not preserve the order of the attributes: `$({ a: 1, b: 2 }) === $({ b: 2, a: 1 })`.
64
+ *
65
+ * > **Note:** The pool of interned values is available as `intern.pool`. There’s a `FinalizationRegistry` at `intern.finalizationRegistry` that cleans up interned values that have been garbage collected.
56
66
  *
57
67
  * **Related Work**
58
68
  *
59
69
  * **[JavaScript Records & Tuples Proposal](https://github.com/tc39/proposal-record-tuple)**
60
70
  *
61
- * A proposal to include immutable objects (Records) and immutable arrays (Tuples) in JavaScript. This subsumes most of the need for `intern()` even though it doesn’t cover `Map`s, `Set`s, regular expressions, and so forth.
71
+ * A proposal to include immutable objects (Records) and immutable arrays (Tuples) in JavaScript. This subsumes most of the need for `intern()`.
62
72
  *
63
73
  * It includes a [polyfill](https://github.com/bloomberg/record-tuple-polyfill) which works very similarly to `intern()` but requires different functions for different data types.
64
74
  *
@@ -99,17 +109,25 @@
99
109
  * - <https://medium.com/@modernserf/the-tyranny-of-triple-equals-de46cc0c5723>
100
110
  * - <https://twitter.com/swannodette/status/1067962983924539392>
101
111
  * - <https://gist.github.com/modernserf/c000e62d40f678cf395e3f360b9b0e43>
102
- *
103
- * **Implementation Notes**
104
- *
105
- * - Besides [Lodash’s `isEqual()`](https://lodash.com/docs/4.17.15#isEqual) we also considered defaulting to [Node.js’s notion of deep equality](https://nodejs.org/dist/latest-v21.x/docs/api/util.html#utilisdeepstrictequalval1-val2) with the [`deep-equal`](https://npm.im/package/deep-equal) polyfill for the browser.
106
- *
107
- * - Besides [`deep-freeze-es6`](https://npm.im/deep-freeze-es6) we also considered doing the deep freezing with [`deep-freeze-strict`](https://npm.im/deep-freeze-strict), [`deep-freeze-node`](https://npm.im/deep-freeze-node), and [`deep-freeze`](https://npm.im/deep-freeze).
108
112
  */
109
- export declare function intern<T extends WeakKey>(value: T): T;
113
+ export declare function intern<T extends Array<InternInnerValue> | {
114
+ [key: string]: InternInnerValue;
115
+ }>(value: T): Intern<T>;
110
116
  export declare namespace intern {
111
- var isEqual: (value: any, other: any) => boolean;
112
- var pool: Map<Symbol, WeakRef<any>>;
113
- var finalizationRegistry: FinalizationRegistry<Symbol>;
117
+ var pool: {
118
+ tuple: Map<Symbol, WeakRef<Readonly<InternInnerValue[] & {
119
+ [internSymbol]: true;
120
+ }>>>;
121
+ record: Map<Symbol, WeakRef<Readonly<{
122
+ [key: string]: InternInnerValue;
123
+ } & {
124
+ [internSymbol]: true;
125
+ }>>>;
126
+ };
127
+ var finalizationRegistry: FinalizationRegistry<{
128
+ type: "tuple" | "record";
129
+ key: Symbol;
130
+ }>;
114
131
  }
132
+ export declare const internSymbol: unique symbol;
115
133
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../source/index.mts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2GG;AACH,wBAAgB,MAAM,CAAC,CAAC,SAAS,OAAO,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAUrD;yBAVe,MAAM"}
1
+ {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../source/index.mts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,MAAM,CAAC,IAAI,IAAI,QAAQ,CAAC,IAAI,GAAG;IAAE,CAAC,YAAY,CAAC,EAAE,IAAI,CAAA;CAAE,CAAC,CAAC;AAErE;;GAEG;AACH,MAAM,MAAM,gBAAgB,GACxB,MAAM,GACN,MAAM,GACN,MAAM,GACN,OAAO,GACP,MAAM,GACN,SAAS,GACT,IAAI,GACJ,MAAM,CAAC,OAAO,CAAC,CAAC;AAEpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqGG;AACH,wBAAgB,MAAM,CACpB,CAAC,SAAS,KAAK,CAAC,gBAAgB,CAAC,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAAA;CAAE,EACvE,KAAK,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CA2CrB;yBA7Ce,MAAM;;;;;;;;;;;;;;;;AA+CtB,eAAO,MAAM,YAAY,eAAmB,CAAC"}
package/build/index.mjs CHANGED
@@ -1,16 +1,14 @@
1
- import lodash from "lodash";
2
- import deepFreeze from "deep-freeze-es6";
3
1
  /**
4
2
  * [Interning](<https://en.wikipedia.org/wiki/Interning_(computer_science)>) a value makes it unique across the program, which is useful for checking equality with `===` (reference equality), using it as a key in a `Map`, adding it to a `Set`, and so forth:
5
3
  *
6
- * ```javascript
4
+ * ```typescript
7
5
  * import { intern as $ } from "@radically-straightforward/utilities";
8
6
  *
9
7
  * [1] === [1]; // => false
10
8
  * $([1]) === $([1]); // => true
11
9
  *
12
10
  * {
13
- * const map = new Map();
11
+ * const map = new Map<number[], number>();
14
12
  * map.set([1], 1);
15
13
  * map.set([1], 2);
16
14
  * map.size; // => 2
@@ -18,7 +16,7 @@ import deepFreeze from "deep-freeze-es6";
18
16
  * }
19
17
  *
20
18
  * {
21
- * const map = new Map();
19
+ * const map = new Map<utilities.Intern<number[]>, number>();
22
20
  * map.set($([1]), 1);
23
21
  * map.set($([1]), 2);
24
22
  * map.size; // => 1
@@ -26,7 +24,7 @@ import deepFreeze from "deep-freeze-es6";
26
24
  * }
27
25
  *
28
26
  * {
29
- * const set = new Set();
27
+ * const set = new Set<number[]>();
30
28
  * set.add([1]);
31
29
  * set.add([1]);
32
30
  * set.size; // => 2
@@ -34,7 +32,7 @@ import deepFreeze from "deep-freeze-es6";
34
32
  * }
35
33
  *
36
34
  * {
37
- * const set = new Set();
35
+ * const set = new Set<utilities.Intern<number[]>>();
38
36
  * set.add($([1]));
39
37
  * set.add($([1]));
40
38
  * set.size; // => 1
@@ -44,23 +42,23 @@ import deepFreeze from "deep-freeze-es6";
44
42
  *
45
43
  * > **Note:** We recommend that you alias `intern as $` when importing it to make your code less noisy.
46
44
  *
47
- * > **Note:** The default notion of equality used to intern values is [Lodash’s `isEqual()`](https://lodash.com/docs/4.17.15#isEqual). You may change that by overriding `intern.isEqual: (value: any, other: any) => boolean` before using `intern()` for the first time.
48
- * >
49
- * > In particular, note that `intern()` uses a notion of equality that is deep: it compares, for example, objects within objects by value. This is more ergonomic, because it means that you only have to call `intern()` on the outer object, for example, `$({ a: { b: 2 } })` instead of `$({ a: $({ b: 2 }) })`. But this is slower.
50
- * >
51
- * > You may replace the notion of equality with shallow equality and use the `$({ a: $({ b: 2 }) })` pattern to speed things up. That is, for example, [what React does](https://legacy.reactjs.org/docs/react-api.html#reactpurecomponent). It’s also what the [**JavaScript Records & Tuples Proposal**](https://github.com/tc39/proposal-record-tuple) includes as of January 2024, so it may make your code easier to adapt in the future.
45
+ * > **Node:** Inner values must be either primitives or interned values themselves, for example, `$([1, $({})])` is valid, but `$([1, {}])` is not.
52
46
  *
53
- * > **Note:** You must not mutate an interned value. Interned values are deeply [frozen](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) with [`deep-freeze-es6`](https://npm.im/deep-freeze-es6) to prevent you from mutating them.
47
+ * > **Node:** Currently only arrays (tuples) and objects (records) may be interned. In the future we may support more types, for example, `Map`, `Set`, regular expressions, and so forth.
48
+ *
49
+ * > **Note:** You must not mutate an interned value. Interned values are [frozen](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) to prevent mutation.
54
50
  *
55
51
  * > **Note:** Interning a value is a costly operation which grows more expensive as you intern more values. Only intern values when really necessary.
56
52
  *
57
- * > **Note:** The pool of interned values is available as `intern.pool: Map<Symbol, WeakRef<any>>`. There’s a `FinalizationRegistry` at `intern.finalizationRegistry` that cleans up values that have been garbage collected.
53
+ * > **Note:** Interned objects do not preserve the order of the attributes: `$({ a: 1, b: 2 }) === $({ b: 2, a: 1 })`.
54
+ *
55
+ * > **Note:** The pool of interned values is available as `intern.pool`. There’s a `FinalizationRegistry` at `intern.finalizationRegistry` that cleans up interned values that have been garbage collected.
58
56
  *
59
57
  * **Related Work**
60
58
  *
61
59
  * **[JavaScript Records & Tuples Proposal](https://github.com/tc39/proposal-record-tuple)**
62
60
  *
63
- * A proposal to include immutable objects (Records) and immutable arrays (Tuples) in JavaScript. This subsumes most of the need for `intern()` even though it doesn’t cover `Map`s, `Set`s, regular expressions, and so forth.
61
+ * A proposal to include immutable objects (Records) and immutable arrays (Tuples) in JavaScript. This subsumes most of the need for `intern()`.
64
62
  *
65
63
  * It includes a [polyfill](https://github.com/bloomberg/record-tuple-polyfill) which works very similarly to `intern()` but requires different functions for different data types.
66
64
  *
@@ -101,29 +99,50 @@ import deepFreeze from "deep-freeze-es6";
101
99
  * - <https://medium.com/@modernserf/the-tyranny-of-triple-equals-de46cc0c5723>
102
100
  * - <https://twitter.com/swannodette/status/1067962983924539392>
103
101
  * - <https://gist.github.com/modernserf/c000e62d40f678cf395e3f360b9b0e43>
104
- *
105
- * **Implementation Notes**
106
- *
107
- * - Besides [Lodash’s `isEqual()`](https://lodash.com/docs/4.17.15#isEqual) we also considered defaulting to [Node.js’s notion of deep equality](https://nodejs.org/dist/latest-v21.x/docs/api/util.html#utilisdeepstrictequalval1-val2) with the [`deep-equal`](https://npm.im/package/deep-equal) polyfill for the browser.
108
- *
109
- * - Besides [`deep-freeze-es6`](https://npm.im/deep-freeze-es6) we also considered doing the deep freezing with [`deep-freeze-strict`](https://npm.im/deep-freeze-strict), [`deep-freeze-node`](https://npm.im/deep-freeze-node), and [`deep-freeze`](https://npm.im/deep-freeze).
110
102
  */
111
103
  export function intern(value) {
112
- for (const internWeakRef of intern.pool.values()) {
104
+ const type = Array.isArray(value)
105
+ ? "tuple"
106
+ : typeof value === "object" && value !== null
107
+ ? "record"
108
+ : (() => {
109
+ throw new Error(`Failed to intern value.`);
110
+ })();
111
+ const keys = Object.keys(value);
112
+ for (const internWeakRef of intern.pool[type].values()) {
113
113
  const internValue = internWeakRef.deref();
114
- if (intern.isEqual(value, internValue))
114
+ if (internValue === undefined ||
115
+ keys.length !== Object.keys(internValue).length)
116
+ continue;
117
+ if (keys.every((key) => value[key] === internValue[key]))
115
118
  return internValue;
116
119
  }
120
+ for (const innerValue of Object.values(value))
121
+ if (!([
122
+ "string",
123
+ "number",
124
+ "bigint",
125
+ "boolean",
126
+ "symbol",
127
+ "undefined",
128
+ ].includes(typeof innerValue) ||
129
+ innerValue === null ||
130
+ innerValue[internSymbol] === true))
131
+ throw new Error(`Failed to intern value because of non-interned inner value.`);
117
132
  const key = Symbol();
118
- deepFreeze(value);
119
- intern.pool.set(key, new WeakRef(value));
120
- intern.finalizationRegistry.register(value, key);
133
+ value[internSymbol] = true;
134
+ Object.freeze(value);
135
+ intern.pool[type].set(key, new WeakRef(value));
136
+ intern.finalizationRegistry.register(value, { type, key });
121
137
  return value;
122
138
  }
123
- intern.isEqual = lodash.isEqual;
124
- intern.pool = new Map();
125
- intern.finalizationRegistry = new FinalizationRegistry((key) => {
126
- intern.pool.delete(key);
139
+ export const internSymbol = Symbol("intern");
140
+ intern.pool = {
141
+ tuple: new Map(),
142
+ record: new Map(),
143
+ };
144
+ intern.finalizationRegistry = new FinalizationRegistry(({ type, key }) => {
145
+ intern.pool[type].delete(key);
127
146
  });
128
147
  /*
129
148
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../source/index.mts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,UAAU,MAAM,iBAAiB,CAAC;AAEzC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2GG;AACH,MAAM,UAAU,MAAM,CAAoB,KAAQ;IAChD,KAAK,MAAM,aAAa,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;QACjD,MAAM,WAAW,GAAG,aAAa,CAAC,KAAK,EAAE,CAAC;QAC1C,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,WAAW,CAAC;YAAE,OAAO,WAAW,CAAC;IAC7D,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;IACrB,UAAU,CAAC,KAAK,CAAC,CAAC;IAClB,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IACzC,MAAM,CAAC,oBAAoB,CAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACjD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;AAEhC,MAAM,CAAC,IAAI,GAAG,IAAI,GAAG,EAAwB,CAAC;AAE9C,MAAM,CAAC,oBAAoB,GAAG,IAAI,oBAAoB,CAAS,CAAC,GAAG,EAAE,EAAE;IACrE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AAC1B,CAAC,CAAC,CAAC;AAEH;;;;;;;;;;;;;;;;;;EAkBE;AACF,MAAM;AACN,+CAA+C;AAC/C,KAAK;AACL,+KAA+K;AAC/K,uBAAuB;AACvB,iDAAiD;AACjD,8FAA8F;AAC9F,oJAAoJ;AACpJ,KAAK;AACL,8DAA8D;AAC9D,KAAK;AACL,sFAAsF;AACtF,KAAK;AACL,yFAAyF;AACzF,KAAK;AACL,oEAAoE;AACpE,KAAK;AACL,ySAAyS;AACzS,KAAK;AACL,2HAA2H;AAC3H,KAAK;AACL,iBAAiB;AACjB,KAAK;AACL,mBAAmB;AACnB,8DAA8D;AAC9D,KAAK;AACL,8EAA8E;AAC9E,kEAAkE;AAClE,SAAS;AACT,mCAAmC;AACnC,4BAA4B;AAC5B,SAAS;AACT,kBAAkB;AAClB,yFAAyF;AACzF,QAAQ;AACR,mCAAmC;AACnC,2BAA2B;AAC3B,SAAS;AACT,MAAM;AACN,iCAAiC;AACjC,MAAM;AACN,gBAAgB;AAChB,8BAA8B;AAC9B,wDAAwD;AACxD,2CAA2C;AAC3C,6CAA6C;AAC7C,+BAA+B;AAC/B,iDAAiD;AACjD,mBAAmB;AACnB,+BAA+B;AAC/B,2BAA2B;AAC3B,qBAAqB;AACrB,uBAAuB;AACvB,oEAAoE;AACpE,uBAAuB;AACvB,gDAAgD;AAChD,YAAY;AACZ,4BAA4B;AAC5B,iDAAiD;AACjD,QAAQ;AACR,UAAU;AACV,aAAa;AACb,mBAAmB;AACnB,iCAAiC;AACjC,SAAS;AACT,oBAAoB;AACpB,gCAAgC;AAChC,iCAAiC;AACjC,SAAS;AACT,OAAO;AACP,IAAI"}
1
+ {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../source/index.mts"],"names":[],"mappings":"AAkBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqGG;AACH,MAAM,UAAU,MAAM,CAEpB,KAAQ;IACR,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAC/B,CAAC,CAAC,OAAO;QACT,CAAC,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;YAC3C,CAAC,CAAC,QAAQ;YACV,CAAC,CAAC,CAAC,GAAG,EAAE;gBACJ,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;YAC7C,CAAC,CAAC,EAAE,CAAC;IACX,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChC,KAAK,MAAM,aAAa,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;QACvD,MAAM,WAAW,GAAG,aAAa,CAAC,KAAK,EAAE,CAAC;QAC1C,IACE,WAAW,KAAK,SAAS;YACzB,IAAI,CAAC,MAAM,KAAK,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,MAAM;YAE/C,SAAS;QACX,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAE,KAAa,CAAC,GAAG,CAAC,KAAM,WAAmB,CAAC,GAAG,CAAC,CAAC;YACxE,OAAO,WAAkB,CAAC;IAC9B,CAAC;IACD,KAAK,MAAM,UAAU,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;QAC3C,IACE,CAAC,CACC;YACE,QAAQ;YACR,QAAQ;YACR,QAAQ;YACR,SAAS;YACT,QAAQ;YACR,WAAW;SACZ,CAAC,QAAQ,CAAC,OAAO,UAAU,CAAC;YAC7B,UAAU,KAAK,IAAI;YAClB,UAAkB,CAAC,YAAY,CAAC,KAAK,IAAI,CAC3C;YAED,MAAM,IAAI,KAAK,CACb,6DAA6D,CAC9D,CAAC;IACN,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;IACpB,KAAa,CAAC,YAAY,CAAC,GAAG,IAAI,CAAC;IACpC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACrB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,OAAO,CAAC,KAAY,CAAC,CAAC,CAAC;IACtD,MAAM,CAAC,oBAAoB,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;IAC3D,OAAO,KAAY,CAAC;AACtB,CAAC;AAED,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;AAE7C,MAAM,CAAC,IAAI,GAAG;IACZ,KAAK,EAAE,IAAI,GAAG,EAA+C;IAC7D,MAAM,EAAE,IAAI,GAAG,EAGZ;CACJ,CAAC;AAEF,MAAM,CAAC,oBAAoB,GAAG,IAAI,oBAAoB,CAGnD,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE;IACnB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AAChC,CAAC,CAAC,CAAC;AAEH;;;;;;;;;;;;;;;;;;EAkBE;AACF,MAAM;AACN,+CAA+C;AAC/C,KAAK;AACL,+KAA+K;AAC/K,uBAAuB;AACvB,iDAAiD;AACjD,8FAA8F;AAC9F,oJAAoJ;AACpJ,KAAK;AACL,8DAA8D;AAC9D,KAAK;AACL,sFAAsF;AACtF,KAAK;AACL,yFAAyF;AACzF,KAAK;AACL,oEAAoE;AACpE,KAAK;AACL,ySAAyS;AACzS,KAAK;AACL,2HAA2H;AAC3H,KAAK;AACL,iBAAiB;AACjB,KAAK;AACL,mBAAmB;AACnB,8DAA8D;AAC9D,KAAK;AACL,8EAA8E;AAC9E,kEAAkE;AAClE,SAAS;AACT,mCAAmC;AACnC,4BAA4B;AAC5B,SAAS;AACT,kBAAkB;AAClB,yFAAyF;AACzF,QAAQ;AACR,mCAAmC;AACnC,2BAA2B;AAC3B,SAAS;AACT,MAAM;AACN,iCAAiC;AACjC,MAAM;AACN,gBAAgB;AAChB,8BAA8B;AAC9B,wDAAwD;AACxD,2CAA2C;AAC3C,6CAA6C;AAC7C,+BAA+B;AAC/B,iDAAiD;AACjD,mBAAmB;AACnB,+BAA+B;AAC/B,2BAA2B;AAC3B,qBAAqB;AACrB,uBAAuB;AACvB,oEAAoE;AACpE,uBAAuB;AACvB,gDAAgD;AAChD,YAAY;AACZ,4BAA4B;AAC5B,iDAAiD;AACjD,QAAQ;AACR,UAAU;AACV,aAAa;AACb,mBAAmB;AACnB,iCAAiC;AACjC,SAAS;AACT,oBAAoB;AACpB,gCAAgC;AAChC,iCAAiC;AACjC,SAAS;AACT,OAAO;AACP,IAAI"}
@@ -2,9 +2,11 @@ import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
3
  import { intern as $ } from "./index.mjs";
4
4
  test("intern()", () => {
5
- // @ts-ignore
5
+ // @ts-expect-error
6
6
  assert(([1] === [1]) === false);
7
7
  assert($([1]) === $([1]));
8
+ assert($({ a: 1, b: 2 }) === $({ b: 2, a: 1 }));
9
+ assert($([1]) !== $([2]));
8
10
  {
9
11
  const map = new Map();
10
12
  map.set([1], 1);
@@ -33,8 +35,13 @@ test("intern()", () => {
33
35
  assert.equal(set.size, 1);
34
36
  assert(set.has($([1])));
35
37
  }
36
- assert.notEqual($([1]), $([2]));
37
38
  assert.throws(() => {
39
+ // @ts-expect-error
40
+ $([1, {}]);
41
+ });
42
+ assert($([1, $({})]) === $([1, $({})]));
43
+ assert.throws(() => {
44
+ // @ts-expect-error
38
45
  $([1])[0] = 2;
39
46
  });
40
47
  });
@@ -1 +1 @@
1
- {"version":3,"file":"index.test.mjs","sourceRoot":"","sources":["../source/index.test.mts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.test.mjs","sourceRoot":"","sources":["../source/index.test.mts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@radically-straightforward/utilities",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "🛠️ Utilities for Node.js and the browser",
5
5
  "keywords": [
6
6
  "node",
@@ -27,14 +27,9 @@
27
27
  "prepare": "tsc && documentation",
28
28
  "test": "npm run prepare && node --test && prettier --check \"./README.md\" --check \"./package.json\" --check \"./tsconfig.json\" --check \"./source/**/*.mts\""
29
29
  },
30
- "dependencies": {
31
- "deep-freeze-es6": "^3.0.2",
32
- "lodash": "^4.17.21"
33
- },
34
30
  "devDependencies": {
35
31
  "@radically-straightforward/documentation": "^1.0.1",
36
32
  "@radically-straightforward/tsconfig": "^1.0.0",
37
- "@types/lodash": "^4.14.202",
38
33
  "@types/node": "^20.10.6",
39
34
  "prettier": "^3.1.1",
40
35
  "typescript": "^5.3.3"
package/source/index.mts CHANGED
@@ -1,17 +1,32 @@
1
- import lodash from "lodash";
2
- import deepFreeze from "deep-freeze-es6";
1
+ /**
2
+ * Utility type for `intern()`.
3
+ */
4
+ export type Intern<Type> = Readonly<Type & { [internSymbol]: true }>;
5
+
6
+ /**
7
+ * Utility type for `intern()`.
8
+ */
9
+ export type InternInnerValue =
10
+ | string
11
+ | number
12
+ | bigint
13
+ | boolean
14
+ | symbol
15
+ | undefined
16
+ | null
17
+ | Intern<unknown>;
3
18
 
4
19
  /**
5
20
  * [Interning](<https://en.wikipedia.org/wiki/Interning_(computer_science)>) a value makes it unique across the program, which is useful for checking equality with `===` (reference equality), using it as a key in a `Map`, adding it to a `Set`, and so forth:
6
21
  *
7
- * ```javascript
22
+ * ```typescript
8
23
  * import { intern as $ } from "@radically-straightforward/utilities";
9
24
  *
10
25
  * [1] === [1]; // => false
11
26
  * $([1]) === $([1]); // => true
12
27
  *
13
28
  * {
14
- * const map = new Map();
29
+ * const map = new Map<number[], number>();
15
30
  * map.set([1], 1);
16
31
  * map.set([1], 2);
17
32
  * map.size; // => 2
@@ -19,7 +34,7 @@ import deepFreeze from "deep-freeze-es6";
19
34
  * }
20
35
  *
21
36
  * {
22
- * const map = new Map();
37
+ * const map = new Map<utilities.Intern<number[]>, number>();
23
38
  * map.set($([1]), 1);
24
39
  * map.set($([1]), 2);
25
40
  * map.size; // => 1
@@ -27,7 +42,7 @@ import deepFreeze from "deep-freeze-es6";
27
42
  * }
28
43
  *
29
44
  * {
30
- * const set = new Set();
45
+ * const set = new Set<number[]>();
31
46
  * set.add([1]);
32
47
  * set.add([1]);
33
48
  * set.size; // => 2
@@ -35,7 +50,7 @@ import deepFreeze from "deep-freeze-es6";
35
50
  * }
36
51
  *
37
52
  * {
38
- * const set = new Set();
53
+ * const set = new Set<utilities.Intern<number[]>>();
39
54
  * set.add($([1]));
40
55
  * set.add($([1]));
41
56
  * set.size; // => 1
@@ -45,23 +60,23 @@ import deepFreeze from "deep-freeze-es6";
45
60
  *
46
61
  * > **Note:** We recommend that you alias `intern as $` when importing it to make your code less noisy.
47
62
  *
48
- * > **Note:** The default notion of equality used to intern values is [Lodash’s `isEqual()`](https://lodash.com/docs/4.17.15#isEqual). You may change that by overriding `intern.isEqual: (value: any, other: any) => boolean` before using `intern()` for the first time.
49
- * >
50
- * > In particular, note that `intern()` uses a notion of equality that is deep: it compares, for example, objects within objects by value. This is more ergonomic, because it means that you only have to call `intern()` on the outer object, for example, `$({ a: { b: 2 } })` instead of `$({ a: $({ b: 2 }) })`. But this is slower.
51
- * >
52
- * > You may replace the notion of equality with shallow equality and use the `$({ a: $({ b: 2 }) })` pattern to speed things up. That is, for example, [what React does](https://legacy.reactjs.org/docs/react-api.html#reactpurecomponent). It’s also what the [**JavaScript Records & Tuples Proposal**](https://github.com/tc39/proposal-record-tuple) includes as of January 2024, so it may make your code easier to adapt in the future.
63
+ * > **Node:** Inner values must be either primitives or interned values themselves, for example, `$([1, $({})])` is valid, but `$([1, {}])` is not.
53
64
  *
54
- * > **Note:** You must not mutate an interned value. Interned values are deeply [frozen](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) with [`deep-freeze-es6`](https://npm.im/deep-freeze-es6) to prevent you from mutating them.
65
+ * > **Node:** Currently only arrays (tuples) and objects (records) may be interned. In the future we may support more types, for example, `Map`, `Set`, regular expressions, and so forth.
66
+ *
67
+ * > **Note:** You must not mutate an interned value. Interned values are [frozen](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) to prevent mutation.
55
68
  *
56
69
  * > **Note:** Interning a value is a costly operation which grows more expensive as you intern more values. Only intern values when really necessary.
57
70
  *
58
- * > **Note:** The pool of interned values is available as `intern.pool: Map<Symbol, WeakRef<any>>`. There’s a `FinalizationRegistry` at `intern.finalizationRegistry` that cleans up values that have been garbage collected.
71
+ * > **Note:** Interned objects do not preserve the order of the attributes: `$({ a: 1, b: 2 }) === $({ b: 2, a: 1 })`.
72
+ *
73
+ * > **Note:** The pool of interned values is available as `intern.pool`. There’s a `FinalizationRegistry` at `intern.finalizationRegistry` that cleans up interned values that have been garbage collected.
59
74
  *
60
75
  * **Related Work**
61
76
  *
62
77
  * **[JavaScript Records & Tuples Proposal](https://github.com/tc39/proposal-record-tuple)**
63
78
  *
64
- * A proposal to include immutable objects (Records) and immutable arrays (Tuples) in JavaScript. This subsumes most of the need for `intern()` even though it doesn’t cover `Map`s, `Set`s, regular expressions, and so forth.
79
+ * A proposal to include immutable objects (Records) and immutable arrays (Tuples) in JavaScript. This subsumes most of the need for `intern()`.
65
80
  *
66
81
  * It includes a [polyfill](https://github.com/bloomberg/record-tuple-polyfill) which works very similarly to `intern()` but requires different functions for different data types.
67
82
  *
@@ -102,31 +117,69 @@ import deepFreeze from "deep-freeze-es6";
102
117
  * - <https://medium.com/@modernserf/the-tyranny-of-triple-equals-de46cc0c5723>
103
118
  * - <https://twitter.com/swannodette/status/1067962983924539392>
104
119
  * - <https://gist.github.com/modernserf/c000e62d40f678cf395e3f360b9b0e43>
105
- *
106
- * **Implementation Notes**
107
- *
108
- * - Besides [Lodash’s `isEqual()`](https://lodash.com/docs/4.17.15#isEqual) we also considered defaulting to [Node.js’s notion of deep equality](https://nodejs.org/dist/latest-v21.x/docs/api/util.html#utilisdeepstrictequalval1-val2) with the [`deep-equal`](https://npm.im/package/deep-equal) polyfill for the browser.
109
- *
110
- * - Besides [`deep-freeze-es6`](https://npm.im/deep-freeze-es6) we also considered doing the deep freezing with [`deep-freeze-strict`](https://npm.im/deep-freeze-strict), [`deep-freeze-node`](https://npm.im/deep-freeze-node), and [`deep-freeze`](https://npm.im/deep-freeze).
111
120
  */
112
- export function intern<T extends WeakKey>(value: T): T {
113
- for (const internWeakRef of intern.pool.values()) {
121
+ export function intern<
122
+ T extends Array<InternInnerValue> | { [key: string]: InternInnerValue },
123
+ >(value: T): Intern<T> {
124
+ const type = Array.isArray(value)
125
+ ? "tuple"
126
+ : typeof value === "object" && value !== null
127
+ ? "record"
128
+ : (() => {
129
+ throw new Error(`Failed to intern value.`);
130
+ })();
131
+ const keys = Object.keys(value);
132
+ for (const internWeakRef of intern.pool[type].values()) {
114
133
  const internValue = internWeakRef.deref();
115
- if (intern.isEqual(value, internValue)) return internValue;
134
+ if (
135
+ internValue === undefined ||
136
+ keys.length !== Object.keys(internValue).length
137
+ )
138
+ continue;
139
+ if (keys.every((key) => (value as any)[key] === (internValue as any)[key]))
140
+ return internValue as any;
116
141
  }
142
+ for (const innerValue of Object.values(value))
143
+ if (
144
+ !(
145
+ [
146
+ "string",
147
+ "number",
148
+ "bigint",
149
+ "boolean",
150
+ "symbol",
151
+ "undefined",
152
+ ].includes(typeof innerValue) ||
153
+ innerValue === null ||
154
+ (innerValue as any)[internSymbol] === true
155
+ )
156
+ )
157
+ throw new Error(
158
+ `Failed to intern value because of non-interned inner value.`,
159
+ );
117
160
  const key = Symbol();
118
- deepFreeze(value);
119
- intern.pool.set(key, new WeakRef(value));
120
- intern.finalizationRegistry.register(value, key);
121
- return value;
161
+ (value as any)[internSymbol] = true;
162
+ Object.freeze(value);
163
+ intern.pool[type].set(key, new WeakRef(value as any));
164
+ intern.finalizationRegistry.register(value, { type, key });
165
+ return value as any;
122
166
  }
123
167
 
124
- intern.isEqual = lodash.isEqual;
168
+ export const internSymbol = Symbol("intern");
125
169
 
126
- intern.pool = new Map<Symbol, WeakRef<any>>();
170
+ intern.pool = {
171
+ tuple: new Map<Symbol, WeakRef<Intern<InternInnerValue[]>>>(),
172
+ record: new Map<
173
+ Symbol,
174
+ WeakRef<Intern<{ [key: string]: InternInnerValue }>>
175
+ >(),
176
+ };
127
177
 
128
- intern.finalizationRegistry = new FinalizationRegistry<Symbol>((key) => {
129
- intern.pool.delete(key);
178
+ intern.finalizationRegistry = new FinalizationRegistry<{
179
+ type: "tuple" | "record";
180
+ key: Symbol;
181
+ }>(({ type, key }) => {
182
+ intern.pool[type].delete(key);
130
183
  });
131
184
 
132
185
  /*
@@ -4,12 +4,15 @@ import * as utilities from "./index.mjs";
4
4
  import { intern as $ } from "./index.mjs";
5
5
 
6
6
  test("intern()", () => {
7
- // @ts-ignore
7
+ // @ts-expect-error
8
8
  assert(([1] === [1]) === false);
9
9
  assert($([1]) === $([1]));
10
+ assert($({ a: 1, b: 2 }) === $({ b: 2, a: 1 }));
11
+
12
+ assert($([1]) !== $([2]));
10
13
 
11
14
  {
12
- const map = new Map();
15
+ const map = new Map<number[], number>();
13
16
  map.set([1], 1);
14
17
  map.set([1], 2);
15
18
  assert.equal(map.size, 2);
@@ -17,7 +20,7 @@ test("intern()", () => {
17
20
  }
18
21
 
19
22
  {
20
- const map = new Map();
23
+ const map = new Map<utilities.Intern<number[]>, number>();
21
24
  map.set($([1]), 1);
22
25
  map.set($([1]), 2);
23
26
  assert.equal(map.size, 1);
@@ -25,7 +28,7 @@ test("intern()", () => {
25
28
  }
26
29
 
27
30
  {
28
- const set = new Set();
31
+ const set = new Set<number[]>();
29
32
  set.add([1]);
30
33
  set.add([1]);
31
34
  assert.equal(set.size, 2);
@@ -33,16 +36,21 @@ test("intern()", () => {
33
36
  }
34
37
 
35
38
  {
36
- const set = new Set();
39
+ const set = new Set<utilities.Intern<number[]>>();
37
40
  set.add($([1]));
38
41
  set.add($([1]));
39
42
  assert.equal(set.size, 1);
40
43
  assert(set.has($([1])));
41
44
  }
42
45
 
43
- assert.notEqual($([1]), $([2]));
46
+ assert.throws(() => {
47
+ // @ts-expect-error
48
+ $([1, {}]);
49
+ });
50
+ assert($([1, $({})]) === $([1, $({})]));
44
51
 
45
52
  assert.throws(() => {
53
+ // @ts-expect-error
46
54
  $([1])[0] = 2;
47
55
  });
48
56
  });