@radically-straightforward/utilities 0.0.1 → 0.0.3

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/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":"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"}
@@ -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
  });