@radically-straightforward/utilities 0.0.1 → 0.0.2
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 +8 -0
- package/README.md +13 -15
- package/build/index.d.mts +18 -18
- package/build/index.d.mts.map +1 -1
- package/build/index.mjs +42 -25
- package/build/index.mjs.map +1 -1
- package/build/index.test.mjs +5 -1
- package/build/index.test.mjs.map +1 -1
- package/package.json +1 -6
- package/source/index.mts +57 -27
- package/source/index.test.mts +5 -1
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -19,7 +19,13 @@ import * as node from "@radically-straightforward/utilities";
|
|
|
19
19
|
### `intern()`
|
|
20
20
|
|
|
21
21
|
```typescript
|
|
22
|
-
export function intern<
|
|
22
|
+
export function intern<
|
|
23
|
+
T extends
|
|
24
|
+
| Array<unknown>
|
|
25
|
+
| {
|
|
26
|
+
[key: string]: unknown;
|
|
27
|
+
},
|
|
28
|
+
>(value: T): T;
|
|
23
29
|
```
|
|
24
30
|
|
|
25
31
|
[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:
|
|
@@ -65,23 +71,21 @@ $([1]) === $([1]); // => true
|
|
|
65
71
|
|
|
66
72
|
> **Note:** We recommend that you alias `intern as $` when importing it to make your code less noisy.
|
|
67
73
|
|
|
68
|
-
> **
|
|
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.
|
|
74
|
+
> **Node:** Inner values must be either primitives or interned values themselves, for example, `$([1, $({})])` is valid, but `$([1, {}])` is not.
|
|
73
75
|
|
|
74
|
-
> **
|
|
76
|
+
> **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.
|
|
77
|
+
|
|
78
|
+
> **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
79
|
|
|
76
80
|
> **Note:** Interning a value is a costly operation which grows more expensive as you intern more values. Only intern values when really necessary.
|
|
77
81
|
|
|
78
|
-
> **Note:** The pool of interned values is available as `intern.pool
|
|
82
|
+
> **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
83
|
|
|
80
84
|
**Related Work**
|
|
81
85
|
|
|
82
86
|
**[JavaScript Records & Tuples Proposal](https://github.com/tc39/proposal-record-tuple)**
|
|
83
87
|
|
|
84
|
-
A proposal to include immutable objects (Records) and immutable arrays (Tuples) in JavaScript. This subsumes most of the need for `intern()
|
|
88
|
+
A proposal to include immutable objects (Records) and immutable arrays (Tuples) in JavaScript. This subsumes most of the need for `intern()`.
|
|
85
89
|
|
|
86
90
|
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
91
|
|
|
@@ -123,10 +127,4 @@ Similar to `collections-deep-equal` but either incomplete, or lacking type defin
|
|
|
123
127
|
- <https://twitter.com/swannodette/status/1067962983924539392>
|
|
124
128
|
- <https://gist.github.com/modernserf/c000e62d40f678cf395e3f360b9b0e43>
|
|
125
129
|
|
|
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
130
|
<!-- DOCUMENTATION END: ./source/index.mts -->
|
package/build/index.d.mts
CHANGED
|
@@ -42,23 +42,21 @@
|
|
|
42
42
|
*
|
|
43
43
|
* > **Note:** We recommend that you alias `intern as $` when importing it to make your code less noisy.
|
|
44
44
|
*
|
|
45
|
-
* > **
|
|
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.
|
|
45
|
+
* > **Node:** Inner values must be either primitives or interned values themselves, for example, `$([1, $({})])` is valid, but `$([1, {}])` is not.
|
|
50
46
|
*
|
|
51
|
-
* > **
|
|
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.
|
|
52
50
|
*
|
|
53
51
|
* > **Note:** Interning a value is a costly operation which grows more expensive as you intern more values. Only intern values when really necessary.
|
|
54
52
|
*
|
|
55
|
-
* > **Note:** The pool of interned values is available as `intern.pool
|
|
53
|
+
* > **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
54
|
*
|
|
57
55
|
* **Related Work**
|
|
58
56
|
*
|
|
59
57
|
* **[JavaScript Records & Tuples Proposal](https://github.com/tc39/proposal-record-tuple)**
|
|
60
58
|
*
|
|
61
|
-
* A proposal to include immutable objects (Records) and immutable arrays (Tuples) in JavaScript. This subsumes most of the need for `intern()
|
|
59
|
+
* A proposal to include immutable objects (Records) and immutable arrays (Tuples) in JavaScript. This subsumes most of the need for `intern()`.
|
|
62
60
|
*
|
|
63
61
|
* 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
62
|
*
|
|
@@ -99,17 +97,19 @@
|
|
|
99
97
|
* - <https://medium.com/@modernserf/the-tyranny-of-triple-equals-de46cc0c5723>
|
|
100
98
|
* - <https://twitter.com/swannodette/status/1067962983924539392>
|
|
101
99
|
* - <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
100
|
*/
|
|
109
|
-
export declare function intern<T extends
|
|
101
|
+
export declare function intern<T extends Array<unknown> | {
|
|
102
|
+
[key: string]: unknown;
|
|
103
|
+
}>(value: T): T;
|
|
110
104
|
export declare namespace intern {
|
|
111
|
-
var
|
|
112
|
-
var
|
|
113
|
-
|
|
105
|
+
var interned: symbol;
|
|
106
|
+
var pools: {
|
|
107
|
+
tuple: Map<Symbol, WeakRef<any>>;
|
|
108
|
+
record: Map<Symbol, WeakRef<any>>;
|
|
109
|
+
};
|
|
110
|
+
var finalizationRegistry: FinalizationRegistry<{
|
|
111
|
+
type: "tuple" | "record";
|
|
112
|
+
key: Symbol;
|
|
113
|
+
}>;
|
|
114
114
|
}
|
|
115
115
|
//# sourceMappingURL=index.d.mts.map
|
package/build/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../source/index.mts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../source/index.mts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmGG;AACH,wBAAgB,MAAM,CAAC,CAAC,SAAS,KAAK,CAAC,OAAO,CAAC,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,EAC1E,KAAK,EAAE,CAAC,GACP,CAAC,CA2CH;yBA7Ce,MAAM"}
|
package/build/index.mjs
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
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
|
*
|
|
@@ -44,23 +42,21 @@ 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
|
-
* > **
|
|
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
|
-
* > **
|
|
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
|
|
53
|
+
* > **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
54
|
*
|
|
59
55
|
* **Related Work**
|
|
60
56
|
*
|
|
61
57
|
* **[JavaScript Records & Tuples Proposal](https://github.com/tc39/proposal-record-tuple)**
|
|
62
58
|
*
|
|
63
|
-
* A proposal to include immutable objects (Records) and immutable arrays (Tuples) in JavaScript. This subsumes most of the need for `intern()
|
|
59
|
+
* A proposal to include immutable objects (Records) and immutable arrays (Tuples) in JavaScript. This subsumes most of the need for `intern()`.
|
|
64
60
|
*
|
|
65
61
|
* 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
62
|
*
|
|
@@ -101,29 +97,50 @@ import deepFreeze from "deep-freeze-es6";
|
|
|
101
97
|
* - <https://medium.com/@modernserf/the-tyranny-of-triple-equals-de46cc0c5723>
|
|
102
98
|
* - <https://twitter.com/swannodette/status/1067962983924539392>
|
|
103
99
|
* - <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
100
|
*/
|
|
111
101
|
export function intern(value) {
|
|
112
|
-
|
|
102
|
+
const type = Array.isArray(value)
|
|
103
|
+
? "tuple"
|
|
104
|
+
: typeof value === "object" && value !== null
|
|
105
|
+
? "record"
|
|
106
|
+
: (() => {
|
|
107
|
+
throw new Error(`Failed to intern value.`);
|
|
108
|
+
})();
|
|
109
|
+
const keys = Object.keys(value);
|
|
110
|
+
for (const internWeakRef of intern.pools[type].values()) {
|
|
113
111
|
const internValue = internWeakRef.deref();
|
|
114
|
-
if (
|
|
112
|
+
if (internValue === undefined ||
|
|
113
|
+
keys.length !== Object.keys(internValue).length)
|
|
114
|
+
continue;
|
|
115
|
+
if (keys.every((key) => value[key] === internValue[key]))
|
|
115
116
|
return internValue;
|
|
116
117
|
}
|
|
118
|
+
for (const innerValue of Object.values(value))
|
|
119
|
+
if (!([
|
|
120
|
+
"string",
|
|
121
|
+
"number",
|
|
122
|
+
"bigint",
|
|
123
|
+
"boolean",
|
|
124
|
+
"symbol",
|
|
125
|
+
"undefined",
|
|
126
|
+
].includes(typeof innerValue) ||
|
|
127
|
+
innerValue === null ||
|
|
128
|
+
innerValue[intern.interned] === true))
|
|
129
|
+
throw new Error(`Failed to intern value because of non-interned inner value.`);
|
|
117
130
|
const key = Symbol();
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
intern.
|
|
131
|
+
value[intern.interned] = true;
|
|
132
|
+
Object.freeze(value);
|
|
133
|
+
intern.pools[type].set(key, new WeakRef(value));
|
|
134
|
+
intern.finalizationRegistry.register(value, { type, key });
|
|
121
135
|
return value;
|
|
122
136
|
}
|
|
123
|
-
intern.
|
|
124
|
-
intern.
|
|
125
|
-
|
|
126
|
-
|
|
137
|
+
intern.interned = Symbol("interned");
|
|
138
|
+
intern.pools = {
|
|
139
|
+
tuple: new Map(),
|
|
140
|
+
record: new Map(),
|
|
141
|
+
};
|
|
142
|
+
intern.finalizationRegistry = new FinalizationRegistry(({ type, key }) => {
|
|
143
|
+
intern.pools[type].delete(key);
|
|
127
144
|
});
|
|
128
145
|
/*
|
|
129
146
|
|
package/build/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sourceRoot":"","sources":["../source/index.mts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"index.mjs","sourceRoot":"","sources":["../source/index.mts"],"names":[],"mappings":"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"}
|
package/build/index.test.mjs
CHANGED
|
@@ -5,6 +5,7 @@ test("intern()", () => {
|
|
|
5
5
|
// @ts-ignore
|
|
6
6
|
assert(([1] === [1]) === false);
|
|
7
7
|
assert($([1]) === $([1]));
|
|
8
|
+
assert($([1]) !== $([2]));
|
|
8
9
|
{
|
|
9
10
|
const map = new Map();
|
|
10
11
|
map.set([1], 1);
|
|
@@ -33,7 +34,10 @@ test("intern()", () => {
|
|
|
33
34
|
assert.equal(set.size, 1);
|
|
34
35
|
assert(set.has($([1])));
|
|
35
36
|
}
|
|
36
|
-
assert.
|
|
37
|
+
assert.throws(() => {
|
|
38
|
+
$([1, {}]);
|
|
39
|
+
});
|
|
40
|
+
assert($([1, $({})]) === $([1, $({})]));
|
|
37
41
|
assert.throws(() => {
|
|
38
42
|
$([1])[0] = 2;
|
|
39
43
|
});
|
package/build/index.test.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.test.mjs","sourceRoot":"","sources":["../source/index.test.mts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,oBAAoB,CAAC;AAExC,OAAO,EAAE,MAAM,IAAI,CAAC,EAAE,MAAM,aAAa,CAAC;AAE1C,IAAI,CAAC,UAAU,EAAE,GAAG,EAAE;IACpB,aAAa;IACb,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC;IAChC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAE1B,CAAC;QACC,MAAM,GAAG,GAAG,IAAI,GAAG,EAAE,CAAC;QACtB,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAChB,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAChB,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QAC1B,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;IACxC,CAAC;IAED,CAAC;QACC,MAAM,GAAG,GAAG,IAAI,GAAG,EAAE,CAAC;QACtB,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACnB,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACnB,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QAC1B,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACnC,CAAC;IAED,CAAC;QACC,MAAM,GAAG,GAAG,IAAI,GAAG,EAAE,CAAC;QACtB,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACb,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACb,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QAC1B,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC;IACjC,CAAC;IAED,CAAC;QACC,MAAM,GAAG,GAAG,IAAI,GAAG,EAAE,CAAC;QACtB,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAChB,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAChB,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QAC1B,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1B,CAAC;IAED,MAAM,CAAC,
|
|
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.
|
|
3
|
+
"version": "0.0.2",
|
|
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,6 +1,3 @@
|
|
|
1
|
-
import lodash from "lodash";
|
|
2
|
-
import deepFreeze from "deep-freeze-es6";
|
|
3
|
-
|
|
4
1
|
/**
|
|
5
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:
|
|
6
3
|
*
|
|
@@ -45,23 +42,21 @@ import deepFreeze from "deep-freeze-es6";
|
|
|
45
42
|
*
|
|
46
43
|
* > **Note:** We recommend that you alias `intern as $` when importing it to make your code less noisy.
|
|
47
44
|
*
|
|
48
|
-
* > **
|
|
49
|
-
*
|
|
50
|
-
* >
|
|
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.
|
|
45
|
+
* > **Node:** Inner values must be either primitives or interned values themselves, for example, `$([1, $({})])` is valid, but `$([1, {}])` is not.
|
|
46
|
+
*
|
|
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.
|
|
53
48
|
*
|
|
54
|
-
* > **Note:** You must not mutate an interned value. Interned values are
|
|
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.
|
|
55
50
|
*
|
|
56
51
|
* > **Note:** Interning a value is a costly operation which grows more expensive as you intern more values. Only intern values when really necessary.
|
|
57
52
|
*
|
|
58
|
-
* > **Note:** The pool of interned values is available as `intern.pool
|
|
53
|
+
* > **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
54
|
*
|
|
60
55
|
* **Related Work**
|
|
61
56
|
*
|
|
62
57
|
* **[JavaScript Records & Tuples Proposal](https://github.com/tc39/proposal-record-tuple)**
|
|
63
58
|
*
|
|
64
|
-
* A proposal to include immutable objects (Records) and immutable arrays (Tuples) in JavaScript. This subsumes most of the need for `intern()
|
|
59
|
+
* A proposal to include immutable objects (Records) and immutable arrays (Tuples) in JavaScript. This subsumes most of the need for `intern()`.
|
|
65
60
|
*
|
|
66
61
|
* 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
62
|
*
|
|
@@ -102,31 +97,66 @@ import deepFreeze from "deep-freeze-es6";
|
|
|
102
97
|
* - <https://medium.com/@modernserf/the-tyranny-of-triple-equals-de46cc0c5723>
|
|
103
98
|
* - <https://twitter.com/swannodette/status/1067962983924539392>
|
|
104
99
|
* - <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
100
|
*/
|
|
112
|
-
export function intern<T extends
|
|
113
|
-
|
|
101
|
+
export function intern<T extends Array<unknown> | { [key: string]: unknown }>(
|
|
102
|
+
value: T,
|
|
103
|
+
): T {
|
|
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.pools[type].values()) {
|
|
114
113
|
const internValue = internWeakRef.deref();
|
|
115
|
-
if (
|
|
114
|
+
if (
|
|
115
|
+
internValue === undefined ||
|
|
116
|
+
keys.length !== Object.keys(internValue).length
|
|
117
|
+
)
|
|
118
|
+
continue;
|
|
119
|
+
if (keys.every((key) => (value as any)[key] === internValue[key]))
|
|
120
|
+
return internValue;
|
|
116
121
|
}
|
|
122
|
+
for (const innerValue of Object.values(value))
|
|
123
|
+
if (
|
|
124
|
+
!(
|
|
125
|
+
[
|
|
126
|
+
"string",
|
|
127
|
+
"number",
|
|
128
|
+
"bigint",
|
|
129
|
+
"boolean",
|
|
130
|
+
"symbol",
|
|
131
|
+
"undefined",
|
|
132
|
+
].includes(typeof innerValue) ||
|
|
133
|
+
innerValue === null ||
|
|
134
|
+
(innerValue as any)[intern.interned] === true
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
throw new Error(
|
|
138
|
+
`Failed to intern value because of non-interned inner value.`,
|
|
139
|
+
);
|
|
117
140
|
const key = Symbol();
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
intern.
|
|
141
|
+
(value as any)[intern.interned] = true;
|
|
142
|
+
Object.freeze(value);
|
|
143
|
+
intern.pools[type].set(key, new WeakRef(value));
|
|
144
|
+
intern.finalizationRegistry.register(value, { type, key });
|
|
121
145
|
return value;
|
|
122
146
|
}
|
|
123
147
|
|
|
124
|
-
intern.
|
|
148
|
+
intern.interned = Symbol("interned");
|
|
125
149
|
|
|
126
|
-
intern.
|
|
150
|
+
intern.pools = {
|
|
151
|
+
tuple: new Map<Symbol, WeakRef<any>>(),
|
|
152
|
+
record: new Map<Symbol, WeakRef<any>>(),
|
|
153
|
+
};
|
|
127
154
|
|
|
128
|
-
intern.finalizationRegistry = new FinalizationRegistry<
|
|
129
|
-
|
|
155
|
+
intern.finalizationRegistry = new FinalizationRegistry<{
|
|
156
|
+
type: "tuple" | "record";
|
|
157
|
+
key: Symbol;
|
|
158
|
+
}>(({ type, key }) => {
|
|
159
|
+
intern.pools[type].delete(key);
|
|
130
160
|
});
|
|
131
161
|
|
|
132
162
|
/*
|
package/source/index.test.mts
CHANGED
|
@@ -7,6 +7,7 @@ test("intern()", () => {
|
|
|
7
7
|
// @ts-ignore
|
|
8
8
|
assert(([1] === [1]) === false);
|
|
9
9
|
assert($([1]) === $([1]));
|
|
10
|
+
assert($([1]) !== $([2]));
|
|
10
11
|
|
|
11
12
|
{
|
|
12
13
|
const map = new Map();
|
|
@@ -40,7 +41,10 @@ test("intern()", () => {
|
|
|
40
41
|
assert(set.has($([1])));
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
assert.
|
|
44
|
+
assert.throws(() => {
|
|
45
|
+
$([1, {}]);
|
|
46
|
+
});
|
|
47
|
+
assert($([1, $({})]) === $([1, $({})]));
|
|
44
48
|
|
|
45
49
|
assert.throws(() => {
|
|
46
50
|
$([1])[0] = 2;
|