@radically-straightforward/utilities 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
package/CHANGELOG.md ADDED
@@ -0,0 +1 @@
1
+ # Changelog
package/README.md ADDED
@@ -0,0 +1,132 @@
1
+ # Radically Straightforward · Utilities
2
+
3
+ **🛠️ Utilities for Node.js and the browser**
4
+
5
+ ## Installation
6
+
7
+ ```console
8
+ $ npm install @radically-straightforward/utilities
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```typescript
14
+ import * as node from "@radically-straightforward/utilities";
15
+ ```
16
+
17
+ <!-- DOCUMENTATION START: ./source/index.mts -->
18
+
19
+ ### `intern()`
20
+
21
+ ```typescript
22
+ export function intern<T extends WeakKey>(value: T): T;
23
+ ```
24
+
25
+ [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
+
27
+ ```javascript
28
+ import { intern as $ } from "@radically-straightforward/utilities";
29
+
30
+ [1] === [1]; // => false
31
+ $([1]) === $([1]); // => true
32
+
33
+ {
34
+ const map = new Map();
35
+ map.set([1], 1);
36
+ map.set([1], 2);
37
+ map.size; // => 2
38
+ map.get([1]); // => undefined
39
+ }
40
+
41
+ {
42
+ const map = new Map();
43
+ map.set($([1]), 1);
44
+ map.set($([1]), 2);
45
+ map.size; // => 1
46
+ map.get($([1])); // => 2
47
+ }
48
+
49
+ {
50
+ const set = new Set();
51
+ set.add([1]);
52
+ set.add([1]);
53
+ set.size; // => 2
54
+ set.has([1]); // => false
55
+ }
56
+
57
+ {
58
+ const set = new Set();
59
+ set.add($([1]));
60
+ set.add($([1]));
61
+ set.size; // => 1
62
+ set.has($([1])); // => true
63
+ }
64
+ ```
65
+
66
+ > **Note:** We recommend that you alias `intern as $` when importing it to make your code less noisy.
67
+
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.
73
+
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.
75
+
76
+ > **Note:** Interning a value is a costly operation which grows more expensive as you intern more values. Only intern values when really necessary.
77
+
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.
79
+
80
+ **Related Work**
81
+
82
+ **[JavaScript Records & Tuples Proposal](https://github.com/tc39/proposal-record-tuple)**
83
+
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.
85
+
86
+ 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
+
88
+ **[`collections-deep-equal`](https://npm.im/collections-deep-equal)**
89
+
90
+ A previous solution to this problem which took a different approach: Instead of interning the values and allowing you to use JavaScript’s `Map`s and `Set`s, `collections-deep-equal` extends `Map`s and `Set`s with a different notion of equality.
91
+
92
+ `collections-deep-equal` doesn’t address the issue of comparing values with `===` (reference equality).
93
+
94
+ `collections-deep-equal` does more work on every manipulation of the data structure, for example, when looking up a key in a `Map`, so it may be slower.
95
+
96
+ `collections-deep-equal` has different intern pools for each `Map` and `Set` instead of `intern()`’s single global intern pool, which may be advantageous because smaller pools may be faster to traverse.
97
+
98
+ **[Immutable.js](https://npm.im/immutable), [`collections`](https://npm.im/collections), [`mori`](https://npm.im/mori), [TypeScript Collections](https://npm.im/typescript-collections), [`prelude-ts`](https://npm.im/prelude-ts), [`collectable`](https://npm.im/collectable), and so forth**
99
+
100
+ Similar to `collections-deep-equal`, these libraries implement their own data structures instead of relying on JavaScript’s `Map`s and `Set`s. Some of them go a step further and add their own notions of objects and arrays, which requires you to convert your values back and forth, may not show up nicely in the JavaScript inspector, may be less ergonomic to use with TypeScript, and so forth.
101
+
102
+ The advantage of these libraries over interning is that they may be faster.
103
+
104
+ **[`immer`](https://npm.im/immer) and [`icepick`](https://npm.im/icepick)**
105
+
106
+ Introduce a new way to create values based on existing values.
107
+
108
+ **[`seamless-immutable`](https://npm.im/seamless-immutable)**
109
+
110
+ Modifies existing values more profoundly than freezing.
111
+
112
+ **[`es6-array-map`](https://npm.im/es6-array-map), [`valuecollection`](https://npm.im/valuecollection), [`@strong-roots-capital/map-objects`](https://npm.im/@strong-roots-capital/map-objects), and so forth**
113
+
114
+ Similar to `collections-deep-equal` but either incomplete, or lacking type definitions, and so forth.
115
+
116
+ **Other**
117
+
118
+ - <https://2ality.com/2015/01/es6-maps-sets.html#why-can’t-i-configure-how-maps-and-sets-compare-keys-and-values%3F>
119
+ - <https://stackoverflow.com/questions/21838436/map-using-tuples-or-objects>
120
+ - <https://esdiscuss.org/topic/maps-with-object-keys>
121
+ - <https://medium.com/@modernserf/the-tyranny-of-triple-equals-de46cc0c5723>
122
+ - <https://medium.com/@modernserf/the-tyranny-of-triple-equals-de46cc0c5723>
123
+ - <https://twitter.com/swannodette/status/1067962983924539392>
124
+ - <https://gist.github.com/modernserf/c000e62d40f678cf395e3f360b9b0e43>
125
+
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
+ <!-- DOCUMENTATION END: ./source/index.mts -->
@@ -0,0 +1,115 @@
1
+ /**
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:
3
+ *
4
+ * ```javascript
5
+ * import { intern as $ } from "@radically-straightforward/utilities";
6
+ *
7
+ * [1] === [1]; // => false
8
+ * $([1]) === $([1]); // => true
9
+ *
10
+ * {
11
+ * const map = new Map();
12
+ * map.set([1], 1);
13
+ * map.set([1], 2);
14
+ * map.size; // => 2
15
+ * map.get([1]); // => undefined
16
+ * }
17
+ *
18
+ * {
19
+ * const map = new Map();
20
+ * map.set($([1]), 1);
21
+ * map.set($([1]), 2);
22
+ * map.size; // => 1
23
+ * map.get($([1])); // => 2
24
+ * }
25
+ *
26
+ * {
27
+ * const set = new Set();
28
+ * set.add([1]);
29
+ * set.add([1]);
30
+ * set.size; // => 2
31
+ * set.has([1]); // => false
32
+ * }
33
+ *
34
+ * {
35
+ * const set = new Set();
36
+ * set.add($([1]));
37
+ * set.add($([1]));
38
+ * set.size; // => 1
39
+ * set.has($([1])); // => true
40
+ * }
41
+ * ```
42
+ *
43
+ * > **Note:** We recommend that you alias `intern as $` when importing it to make your code less noisy.
44
+ *
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.
50
+ *
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.
52
+ *
53
+ * > **Note:** Interning a value is a costly operation which grows more expensive as you intern more values. Only intern values when really necessary.
54
+ *
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.
56
+ *
57
+ * **Related Work**
58
+ *
59
+ * **[JavaScript Records & Tuples Proposal](https://github.com/tc39/proposal-record-tuple)**
60
+ *
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.
62
+ *
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.
64
+ *
65
+ * **[`collections-deep-equal`](https://npm.im/collections-deep-equal)**
66
+ *
67
+ * A previous solution to this problem which took a different approach: Instead of interning the values and allowing you to use JavaScript’s `Map`s and `Set`s, `collections-deep-equal` extends `Map`s and `Set`s with a different notion of equality.
68
+ *
69
+ * `collections-deep-equal` doesn’t address the issue of comparing values with `===` (reference equality).
70
+ *
71
+ * `collections-deep-equal` does more work on every manipulation of the data structure, for example, when looking up a key in a `Map`, so it may be slower.
72
+ *
73
+ * `collections-deep-equal` has different intern pools for each `Map` and `Set` instead of `intern()`’s single global intern pool, which may be advantageous because smaller pools may be faster to traverse.
74
+ *
75
+ * **[Immutable.js](https://npm.im/immutable), [`collections`](https://npm.im/collections), [`mori`](https://npm.im/mori), [TypeScript Collections](https://npm.im/typescript-collections), [`prelude-ts`](https://npm.im/prelude-ts), [`collectable`](https://npm.im/collectable), and so forth**
76
+ *
77
+ * Similar to `collections-deep-equal`, these libraries implement their own data structures instead of relying on JavaScript’s `Map`s and `Set`s. Some of them go a step further and add their own notions of objects and arrays, which requires you to convert your values back and forth, may not show up nicely in the JavaScript inspector, may be less ergonomic to use with TypeScript, and so forth.
78
+ *
79
+ * The advantage of these libraries over interning is that they may be faster.
80
+ *
81
+ * **[`immer`](https://npm.im/immer) and [`icepick`](https://npm.im/icepick)**
82
+ *
83
+ * Introduce a new way to create values based on existing values.
84
+ *
85
+ * **[`seamless-immutable`](https://npm.im/seamless-immutable)**
86
+ *
87
+ * Modifies existing values more profoundly than freezing.
88
+ *
89
+ * **[`es6-array-map`](https://npm.im/es6-array-map), [`valuecollection`](https://npm.im/valuecollection), [`@strong-roots-capital/map-objects`](https://npm.im/@strong-roots-capital/map-objects), and so forth**
90
+ *
91
+ * Similar to `collections-deep-equal` but either incomplete, or lacking type definitions, and so forth.
92
+ *
93
+ * **Other**
94
+ *
95
+ * - <https://2ality.com/2015/01/es6-maps-sets.html#why-can’t-i-configure-how-maps-and-sets-compare-keys-and-values%3F>
96
+ * - <https://stackoverflow.com/questions/21838436/map-using-tuples-or-objects>
97
+ * - <https://esdiscuss.org/topic/maps-with-object-keys>
98
+ * - <https://medium.com/@modernserf/the-tyranny-of-triple-equals-de46cc0c5723>
99
+ * - <https://medium.com/@modernserf/the-tyranny-of-triple-equals-de46cc0c5723>
100
+ * - <https://twitter.com/swannodette/status/1067962983924539392>
101
+ * - <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
+ */
109
+ export declare function intern<T extends WeakKey>(value: T): T;
110
+ export declare namespace intern {
111
+ var isEqual: (value: any, other: any) => boolean;
112
+ var pool: Map<Symbol, WeakRef<any>>;
113
+ var finalizationRegistry: FinalizationRegistry<Symbol>;
114
+ }
115
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +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"}
@@ -0,0 +1,218 @@
1
+ import lodash from "lodash";
2
+ import deepFreeze from "deep-freeze-es6";
3
+ /**
4
+ * [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
+ *
6
+ * ```javascript
7
+ * import { intern as $ } from "@radically-straightforward/utilities";
8
+ *
9
+ * [1] === [1]; // => false
10
+ * $([1]) === $([1]); // => true
11
+ *
12
+ * {
13
+ * const map = new Map();
14
+ * map.set([1], 1);
15
+ * map.set([1], 2);
16
+ * map.size; // => 2
17
+ * map.get([1]); // => undefined
18
+ * }
19
+ *
20
+ * {
21
+ * const map = new Map();
22
+ * map.set($([1]), 1);
23
+ * map.set($([1]), 2);
24
+ * map.size; // => 1
25
+ * map.get($([1])); // => 2
26
+ * }
27
+ *
28
+ * {
29
+ * const set = new Set();
30
+ * set.add([1]);
31
+ * set.add([1]);
32
+ * set.size; // => 2
33
+ * set.has([1]); // => false
34
+ * }
35
+ *
36
+ * {
37
+ * const set = new Set();
38
+ * set.add($([1]));
39
+ * set.add($([1]));
40
+ * set.size; // => 1
41
+ * set.has($([1])); // => true
42
+ * }
43
+ * ```
44
+ *
45
+ * > **Note:** We recommend that you alias `intern as $` when importing it to make your code less noisy.
46
+ *
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.
52
+ *
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.
54
+ *
55
+ * > **Note:** Interning a value is a costly operation which grows more expensive as you intern more values. Only intern values when really necessary.
56
+ *
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.
58
+ *
59
+ * **Related Work**
60
+ *
61
+ * **[JavaScript Records & Tuples Proposal](https://github.com/tc39/proposal-record-tuple)**
62
+ *
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.
64
+ *
65
+ * 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
+ *
67
+ * **[`collections-deep-equal`](https://npm.im/collections-deep-equal)**
68
+ *
69
+ * A previous solution to this problem which took a different approach: Instead of interning the values and allowing you to use JavaScript’s `Map`s and `Set`s, `collections-deep-equal` extends `Map`s and `Set`s with a different notion of equality.
70
+ *
71
+ * `collections-deep-equal` doesn’t address the issue of comparing values with `===` (reference equality).
72
+ *
73
+ * `collections-deep-equal` does more work on every manipulation of the data structure, for example, when looking up a key in a `Map`, so it may be slower.
74
+ *
75
+ * `collections-deep-equal` has different intern pools for each `Map` and `Set` instead of `intern()`’s single global intern pool, which may be advantageous because smaller pools may be faster to traverse.
76
+ *
77
+ * **[Immutable.js](https://npm.im/immutable), [`collections`](https://npm.im/collections), [`mori`](https://npm.im/mori), [TypeScript Collections](https://npm.im/typescript-collections), [`prelude-ts`](https://npm.im/prelude-ts), [`collectable`](https://npm.im/collectable), and so forth**
78
+ *
79
+ * Similar to `collections-deep-equal`, these libraries implement their own data structures instead of relying on JavaScript’s `Map`s and `Set`s. Some of them go a step further and add their own notions of objects and arrays, which requires you to convert your values back and forth, may not show up nicely in the JavaScript inspector, may be less ergonomic to use with TypeScript, and so forth.
80
+ *
81
+ * The advantage of these libraries over interning is that they may be faster.
82
+ *
83
+ * **[`immer`](https://npm.im/immer) and [`icepick`](https://npm.im/icepick)**
84
+ *
85
+ * Introduce a new way to create values based on existing values.
86
+ *
87
+ * **[`seamless-immutable`](https://npm.im/seamless-immutable)**
88
+ *
89
+ * Modifies existing values more profoundly than freezing.
90
+ *
91
+ * **[`es6-array-map`](https://npm.im/es6-array-map), [`valuecollection`](https://npm.im/valuecollection), [`@strong-roots-capital/map-objects`](https://npm.im/@strong-roots-capital/map-objects), and so forth**
92
+ *
93
+ * Similar to `collections-deep-equal` but either incomplete, or lacking type definitions, and so forth.
94
+ *
95
+ * **Other**
96
+ *
97
+ * - <https://2ality.com/2015/01/es6-maps-sets.html#why-can’t-i-configure-how-maps-and-sets-compare-keys-and-values%3F>
98
+ * - <https://stackoverflow.com/questions/21838436/map-using-tuples-or-objects>
99
+ * - <https://esdiscuss.org/topic/maps-with-object-keys>
100
+ * - <https://medium.com/@modernserf/the-tyranny-of-triple-equals-de46cc0c5723>
101
+ * - <https://medium.com/@modernserf/the-tyranny-of-triple-equals-de46cc0c5723>
102
+ * - <https://twitter.com/swannodette/status/1067962983924539392>
103
+ * - <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
+ */
111
+ export function intern(value) {
112
+ for (const internWeakRef of intern.pool.values()) {
113
+ const internValue = internWeakRef.deref();
114
+ if (intern.isEqual(value, internValue))
115
+ return internValue;
116
+ }
117
+ const key = Symbol();
118
+ deepFreeze(value);
119
+ intern.pool.set(key, new WeakRef(value));
120
+ intern.finalizationRegistry.register(value, key);
121
+ return value;
122
+ }
123
+ intern.isEqual = lodash.isEqual;
124
+ intern.pool = new Map();
125
+ intern.finalizationRegistry = new FinalizationRegistry((key) => {
126
+ intern.pool.delete(key);
127
+ });
128
+ /*
129
+
130
+
131
+ Math.random().toString(36).slice(2)
132
+
133
+
134
+
135
+
136
+ https://npm.im/package/p-timeout
137
+ https://npm.im/package/delay
138
+ https://npm.im/package/sleep-promise
139
+ https://npm.im/package/promise-timeout
140
+ https://npm.im/package/sleep
141
+ https://npm.im/package/timeout-as-promise
142
+ https://npm.im/package/delayed
143
+ https://npm.im/package/sleep-async
144
+ https://npm.im/package/promise.timeout
145
+
146
+ */
147
+ // /**
148
+ // - Remove uses of `node:timers/promises`?
149
+ // *
150
+ // * TODO: In universal JavaScript, implement a way to **canonicalize** objects using deepEquals to be used in Sets and as Map keys (then deprecate `collections-deep-equal`).
151
+ // * - value objects
152
+ // * - https://lodash.com/docs/4.17.15#isEqual
153
+ // * TODO: Implement using setTimeout and let it be usable in client-side JavaScript as well.
154
+ // * TODO: Explain the differences between this and `setInterval()` (wait for completion before setting the next scheduler, and force a job to run)
155
+ // *
156
+ // * Start a background job that runs every given `interval`.
157
+ // *
158
+ // * You may use `backgroundJob.run()` to force the background job to run right away.
159
+ // *
160
+ // * **Note:** If a background job is running when `backgroundJob.continue()` is called.
161
+ // *
162
+ // * You may use `backgroundJob.stop()` to stop the background job.
163
+ // *
164
+ // * **Note:** If a background job is running when `backgroundJob.stop()` is called, then that background job is run to completion, but a future background job run is not scheduled. This is similar to how an HTTP server may stop finish processing existing requests but don’t accept new requests.
165
+ // *
166
+ // * **Note:** The `intervalVariance` prevents many background jobs from starting at the same and overloading the machine.
167
+ // *
168
+ // * **Example**
169
+ // *
170
+ // * ```javascript
171
+ // * import * as node from "@radically-straightforward/node";
172
+ // *
173
+ // * const backgroundJob = node.backgroundJob({ interval: 3 * 1000 }, () => {
174
+ // * console.log("backgroundJob(): Running background job...");
175
+ // * });
176
+ // * process.on("SIGTSTP", () => {
177
+ // * backgroundJob.run();
178
+ // * });
179
+ // * console.log(
180
+ // * "backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...",
181
+ // * );
182
+ // * await node.shouldTerminate();
183
+ // * backgroundJob.stop();
184
+ // * ```
185
+ // */
186
+ // export function backgroundJob(
187
+ // {
188
+ // interval,
189
+ // intervalVariance = 0.1,
190
+ // }: { interval: number; intervalVariance?: number },
191
+ // function_: () => void | Promise<void>,
192
+ // ): { run: () => void; stop: () => void } {
193
+ // let shouldContinue = true;
194
+ // let abortController = new AbortController();
195
+ // (async () => {
196
+ // while (shouldContinue) {
197
+ // await function_();
198
+ // await timers
199
+ // .setTimeout(
200
+ // interval + interval * intervalVariance * Math.random(),
201
+ // undefined,
202
+ // { signal: abortController.signal },
203
+ // )
204
+ // .catch(() => {});
205
+ // abortController = new AbortController();
206
+ // }
207
+ // })();
208
+ // return {
209
+ // run: () => {
210
+ // abortController.abort();
211
+ // },
212
+ // stop: () => {
213
+ // shouldContinue = false;
214
+ // abortController.abort();
215
+ // },
216
+ // };
217
+ // }
218
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.test.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.d.mts","sourceRoot":"","sources":["../source/index.test.mts"],"names":[],"mappings":""}
@@ -0,0 +1,64 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { intern as $ } from "./index.mjs";
4
+ test("intern()", () => {
5
+ // @ts-ignore
6
+ assert(([1] === [1]) === false);
7
+ assert($([1]) === $([1]));
8
+ {
9
+ const map = new Map();
10
+ map.set([1], 1);
11
+ map.set([1], 2);
12
+ assert.equal(map.size, 2);
13
+ assert.equal(map.get([1]), undefined);
14
+ }
15
+ {
16
+ const map = new Map();
17
+ map.set($([1]), 1);
18
+ map.set($([1]), 2);
19
+ assert.equal(map.size, 1);
20
+ assert.equal(map.get($([1])), 2);
21
+ }
22
+ {
23
+ const set = new Set();
24
+ set.add([1]);
25
+ set.add([1]);
26
+ assert.equal(set.size, 2);
27
+ assert(set.has([1]) === false);
28
+ }
29
+ {
30
+ const set = new Set();
31
+ set.add($([1]));
32
+ set.add($([1]));
33
+ assert.equal(set.size, 1);
34
+ assert(set.has($([1])));
35
+ }
36
+ assert.notEqual($([1]), $([2]));
37
+ assert.throws(() => {
38
+ $([1])[0] = 2;
39
+ });
40
+ });
41
+ // test(
42
+ // "backgroundJob()",
43
+ // {
44
+ // ...(!process.stdin.isTTY
45
+ // ? {
46
+ // skip: "Run interactive test with ‘node ./build/index.test.mjs’.",
47
+ // }
48
+ // : {}),
49
+ // },
50
+ // async () => {
51
+ // const backgroundJob = node.backgroundJob({ interval: 3 * 1000 }, () => {
52
+ // console.log("backgroundJob(): Running background job...");
53
+ // });
54
+ // process.on("SIGTSTP", () => {
55
+ // backgroundJob.run();
56
+ // });
57
+ // console.log(
58
+ // "backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...",
59
+ // );
60
+ // await node.shouldTerminate();
61
+ // backgroundJob.stop();
62
+ // },
63
+ // );
64
+ //# sourceMappingURL=index.test.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.mjs","sourceRoot":"","sources":["../source/index.test.mts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@radically-straightforward/utilities",
3
+ "version": "0.0.1",
4
+ "description": "🛠️ Utilities for Node.js and the browser",
5
+ "keywords": [
6
+ "node",
7
+ "browser",
8
+ "utilities"
9
+ ],
10
+ "homepage": "https://github.com/leafac/radically-straightforward",
11
+ "repository": "https://github.com/leafac/radically-straightforward",
12
+ "bugs": "https://github.com/leafac/radically-straightforward/issues",
13
+ "funding": [
14
+ "https://patreon.com/leafac",
15
+ "https://github.com/sponsors/leafac",
16
+ "https://paypal.me/LeandroFacchinettiEU",
17
+ "https://btc.com/34KJBgtaFYMtDqpSgMayw9qiKWg2GQXA9M"
18
+ ],
19
+ "author": "Leandro Facchinetti <radically-straightforward@leafac.com> (https://leafac.com)",
20
+ "license": "MIT",
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "exports": "./build/index.mjs",
25
+ "types": "./build/index.d.mts",
26
+ "scripts": {
27
+ "prepare": "tsc && documentation",
28
+ "test": "npm run prepare && node --test && prettier --check \"./README.md\" --check \"./package.json\" --check \"./tsconfig.json\" --check \"./source/**/*.mts\""
29
+ },
30
+ "dependencies": {
31
+ "deep-freeze-es6": "^3.0.2",
32
+ "lodash": "^4.17.21"
33
+ },
34
+ "devDependencies": {
35
+ "@radically-straightforward/documentation": "^1.0.1",
36
+ "@radically-straightforward/tsconfig": "^1.0.0",
37
+ "@types/lodash": "^4.14.202",
38
+ "@types/node": "^20.10.6",
39
+ "prettier": "^3.1.1",
40
+ "typescript": "^5.3.3"
41
+ },
42
+ "prettier": {}
43
+ }
@@ -0,0 +1,221 @@
1
+ import lodash from "lodash";
2
+ import deepFreeze from "deep-freeze-es6";
3
+
4
+ /**
5
+ * [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
+ *
7
+ * ```javascript
8
+ * import { intern as $ } from "@radically-straightforward/utilities";
9
+ *
10
+ * [1] === [1]; // => false
11
+ * $([1]) === $([1]); // => true
12
+ *
13
+ * {
14
+ * const map = new Map();
15
+ * map.set([1], 1);
16
+ * map.set([1], 2);
17
+ * map.size; // => 2
18
+ * map.get([1]); // => undefined
19
+ * }
20
+ *
21
+ * {
22
+ * const map = new Map();
23
+ * map.set($([1]), 1);
24
+ * map.set($([1]), 2);
25
+ * map.size; // => 1
26
+ * map.get($([1])); // => 2
27
+ * }
28
+ *
29
+ * {
30
+ * const set = new Set();
31
+ * set.add([1]);
32
+ * set.add([1]);
33
+ * set.size; // => 2
34
+ * set.has([1]); // => false
35
+ * }
36
+ *
37
+ * {
38
+ * const set = new Set();
39
+ * set.add($([1]));
40
+ * set.add($([1]));
41
+ * set.size; // => 1
42
+ * set.has($([1])); // => true
43
+ * }
44
+ * ```
45
+ *
46
+ * > **Note:** We recommend that you alias `intern as $` when importing it to make your code less noisy.
47
+ *
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.
53
+ *
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.
55
+ *
56
+ * > **Note:** Interning a value is a costly operation which grows more expensive as you intern more values. Only intern values when really necessary.
57
+ *
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.
59
+ *
60
+ * **Related Work**
61
+ *
62
+ * **[JavaScript Records & Tuples Proposal](https://github.com/tc39/proposal-record-tuple)**
63
+ *
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.
65
+ *
66
+ * 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
+ *
68
+ * **[`collections-deep-equal`](https://npm.im/collections-deep-equal)**
69
+ *
70
+ * A previous solution to this problem which took a different approach: Instead of interning the values and allowing you to use JavaScript’s `Map`s and `Set`s, `collections-deep-equal` extends `Map`s and `Set`s with a different notion of equality.
71
+ *
72
+ * `collections-deep-equal` doesn’t address the issue of comparing values with `===` (reference equality).
73
+ *
74
+ * `collections-deep-equal` does more work on every manipulation of the data structure, for example, when looking up a key in a `Map`, so it may be slower.
75
+ *
76
+ * `collections-deep-equal` has different intern pools for each `Map` and `Set` instead of `intern()`’s single global intern pool, which may be advantageous because smaller pools may be faster to traverse.
77
+ *
78
+ * **[Immutable.js](https://npm.im/immutable), [`collections`](https://npm.im/collections), [`mori`](https://npm.im/mori), [TypeScript Collections](https://npm.im/typescript-collections), [`prelude-ts`](https://npm.im/prelude-ts), [`collectable`](https://npm.im/collectable), and so forth**
79
+ *
80
+ * Similar to `collections-deep-equal`, these libraries implement their own data structures instead of relying on JavaScript’s `Map`s and `Set`s. Some of them go a step further and add their own notions of objects and arrays, which requires you to convert your values back and forth, may not show up nicely in the JavaScript inspector, may be less ergonomic to use with TypeScript, and so forth.
81
+ *
82
+ * The advantage of these libraries over interning is that they may be faster.
83
+ *
84
+ * **[`immer`](https://npm.im/immer) and [`icepick`](https://npm.im/icepick)**
85
+ *
86
+ * Introduce a new way to create values based on existing values.
87
+ *
88
+ * **[`seamless-immutable`](https://npm.im/seamless-immutable)**
89
+ *
90
+ * Modifies existing values more profoundly than freezing.
91
+ *
92
+ * **[`es6-array-map`](https://npm.im/es6-array-map), [`valuecollection`](https://npm.im/valuecollection), [`@strong-roots-capital/map-objects`](https://npm.im/@strong-roots-capital/map-objects), and so forth**
93
+ *
94
+ * Similar to `collections-deep-equal` but either incomplete, or lacking type definitions, and so forth.
95
+ *
96
+ * **Other**
97
+ *
98
+ * - <https://2ality.com/2015/01/es6-maps-sets.html#why-can’t-i-configure-how-maps-and-sets-compare-keys-and-values%3F>
99
+ * - <https://stackoverflow.com/questions/21838436/map-using-tuples-or-objects>
100
+ * - <https://esdiscuss.org/topic/maps-with-object-keys>
101
+ * - <https://medium.com/@modernserf/the-tyranny-of-triple-equals-de46cc0c5723>
102
+ * - <https://medium.com/@modernserf/the-tyranny-of-triple-equals-de46cc0c5723>
103
+ * - <https://twitter.com/swannodette/status/1067962983924539392>
104
+ * - <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
+ */
112
+ export function intern<T extends WeakKey>(value: T): T {
113
+ for (const internWeakRef of intern.pool.values()) {
114
+ const internValue = internWeakRef.deref();
115
+ if (intern.isEqual(value, internValue)) return internValue;
116
+ }
117
+ const key = Symbol();
118
+ deepFreeze(value);
119
+ intern.pool.set(key, new WeakRef(value));
120
+ intern.finalizationRegistry.register(value, key);
121
+ return value;
122
+ }
123
+
124
+ intern.isEqual = lodash.isEqual;
125
+
126
+ intern.pool = new Map<Symbol, WeakRef<any>>();
127
+
128
+ intern.finalizationRegistry = new FinalizationRegistry<Symbol>((key) => {
129
+ intern.pool.delete(key);
130
+ });
131
+
132
+ /*
133
+
134
+
135
+ Math.random().toString(36).slice(2)
136
+
137
+
138
+
139
+
140
+ https://npm.im/package/p-timeout
141
+ https://npm.im/package/delay
142
+ https://npm.im/package/sleep-promise
143
+ https://npm.im/package/promise-timeout
144
+ https://npm.im/package/sleep
145
+ https://npm.im/package/timeout-as-promise
146
+ https://npm.im/package/delayed
147
+ https://npm.im/package/sleep-async
148
+ https://npm.im/package/promise.timeout
149
+
150
+ */
151
+ // /**
152
+ // - Remove uses of `node:timers/promises`?
153
+ // *
154
+ // * TODO: In universal JavaScript, implement a way to **canonicalize** objects using deepEquals to be used in Sets and as Map keys (then deprecate `collections-deep-equal`).
155
+ // * - value objects
156
+ // * - https://lodash.com/docs/4.17.15#isEqual
157
+ // * TODO: Implement using setTimeout and let it be usable in client-side JavaScript as well.
158
+ // * TODO: Explain the differences between this and `setInterval()` (wait for completion before setting the next scheduler, and force a job to run)
159
+ // *
160
+ // * Start a background job that runs every given `interval`.
161
+ // *
162
+ // * You may use `backgroundJob.run()` to force the background job to run right away.
163
+ // *
164
+ // * **Note:** If a background job is running when `backgroundJob.continue()` is called.
165
+ // *
166
+ // * You may use `backgroundJob.stop()` to stop the background job.
167
+ // *
168
+ // * **Note:** If a background job is running when `backgroundJob.stop()` is called, then that background job is run to completion, but a future background job run is not scheduled. This is similar to how an HTTP server may stop finish processing existing requests but don’t accept new requests.
169
+ // *
170
+ // * **Note:** The `intervalVariance` prevents many background jobs from starting at the same and overloading the machine.
171
+ // *
172
+ // * **Example**
173
+ // *
174
+ // * ```javascript
175
+ // * import * as node from "@radically-straightforward/node";
176
+ // *
177
+ // * const backgroundJob = node.backgroundJob({ interval: 3 * 1000 }, () => {
178
+ // * console.log("backgroundJob(): Running background job...");
179
+ // * });
180
+ // * process.on("SIGTSTP", () => {
181
+ // * backgroundJob.run();
182
+ // * });
183
+ // * console.log(
184
+ // * "backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...",
185
+ // * );
186
+ // * await node.shouldTerminate();
187
+ // * backgroundJob.stop();
188
+ // * ```
189
+ // */
190
+ // export function backgroundJob(
191
+ // {
192
+ // interval,
193
+ // intervalVariance = 0.1,
194
+ // }: { interval: number; intervalVariance?: number },
195
+ // function_: () => void | Promise<void>,
196
+ // ): { run: () => void; stop: () => void } {
197
+ // let shouldContinue = true;
198
+ // let abortController = new AbortController();
199
+ // (async () => {
200
+ // while (shouldContinue) {
201
+ // await function_();
202
+ // await timers
203
+ // .setTimeout(
204
+ // interval + interval * intervalVariance * Math.random(),
205
+ // undefined,
206
+ // { signal: abortController.signal },
207
+ // )
208
+ // .catch(() => {});
209
+ // abortController = new AbortController();
210
+ // }
211
+ // })();
212
+ // return {
213
+ // run: () => {
214
+ // abortController.abort();
215
+ // },
216
+ // stop: () => {
217
+ // shouldContinue = false;
218
+ // abortController.abort();
219
+ // },
220
+ // };
221
+ // }
@@ -0,0 +1,72 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import * as utilities from "./index.mjs";
4
+ import { intern as $ } from "./index.mjs";
5
+
6
+ test("intern()", () => {
7
+ // @ts-ignore
8
+ assert(([1] === [1]) === false);
9
+ assert($([1]) === $([1]));
10
+
11
+ {
12
+ const map = new Map();
13
+ map.set([1], 1);
14
+ map.set([1], 2);
15
+ assert.equal(map.size, 2);
16
+ assert.equal(map.get([1]), undefined);
17
+ }
18
+
19
+ {
20
+ const map = new Map();
21
+ map.set($([1]), 1);
22
+ map.set($([1]), 2);
23
+ assert.equal(map.size, 1);
24
+ assert.equal(map.get($([1])), 2);
25
+ }
26
+
27
+ {
28
+ const set = new Set();
29
+ set.add([1]);
30
+ set.add([1]);
31
+ assert.equal(set.size, 2);
32
+ assert(set.has([1]) === false);
33
+ }
34
+
35
+ {
36
+ const set = new Set();
37
+ set.add($([1]));
38
+ set.add($([1]));
39
+ assert.equal(set.size, 1);
40
+ assert(set.has($([1])));
41
+ }
42
+
43
+ assert.notEqual($([1]), $([2]));
44
+
45
+ assert.throws(() => {
46
+ $([1])[0] = 2;
47
+ });
48
+ });
49
+
50
+ // test(
51
+ // "backgroundJob()",
52
+ // {
53
+ // ...(!process.stdin.isTTY
54
+ // ? {
55
+ // skip: "Run interactive test with ‘node ./build/index.test.mjs’.",
56
+ // }
57
+ // : {}),
58
+ // },
59
+ // async () => {
60
+ // const backgroundJob = node.backgroundJob({ interval: 3 * 1000 }, () => {
61
+ // console.log("backgroundJob(): Running background job...");
62
+ // });
63
+ // process.on("SIGTSTP", () => {
64
+ // backgroundJob.run();
65
+ // });
66
+ // console.log(
67
+ // "backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...",
68
+ // );
69
+ // await node.shouldTerminate();
70
+ // backgroundJob.stop();
71
+ // },
72
+ // );
package/tsconfig.json ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "@radically-straightforward/tsconfig",
3
+ "compilerOptions": {
4
+ "rootDir": "./source/",
5
+ "outDir": "./build/"
6
+ }
7
+ }