@radically-straightforward/utilities 0.0.2 → 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 +4 -0
- package/README.md +39 -9
- package/build/index.d.mts +30 -12
- package/build/index.d.mts.map +1 -1
- package/build/index.mjs +14 -12
- package/build/index.mjs.map +1 -1
- package/build/index.test.mjs +4 -1
- package/build/index.test.mjs.map +1 -1
- package/package.json +1 -1
- package/source/index.mts +43 -20
- package/source/index.test.mts +9 -5
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
@@ -11,33 +11,61 @@ $ npm install @radically-straightforward/utilities
|
|
11
11
|
## Usage
|
12
12
|
|
13
13
|
```typescript
|
14
|
-
import * as
|
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
50
|
export function intern<
|
23
51
|
T extends
|
24
|
-
| Array<
|
52
|
+
| Array<InternInnerValue>
|
25
53
|
| {
|
26
|
-
[key: string]:
|
54
|
+
[key: string]: InternInnerValue;
|
27
55
|
},
|
28
|
-
>(value: T): T
|
56
|
+
>(value: T): Intern<T>;
|
29
57
|
```
|
30
58
|
|
31
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:
|
32
60
|
|
33
|
-
```
|
61
|
+
```typescript
|
34
62
|
import { intern as $ } from "@radically-straightforward/utilities";
|
35
63
|
|
36
64
|
[1] === [1]; // => false
|
37
65
|
$([1]) === $([1]); // => true
|
38
66
|
|
39
67
|
{
|
40
|
-
const map = new Map();
|
68
|
+
const map = new Map<number[], number>();
|
41
69
|
map.set([1], 1);
|
42
70
|
map.set([1], 2);
|
43
71
|
map.size; // => 2
|
@@ -45,7 +73,7 @@ $([1]) === $([1]); // => true
|
|
45
73
|
}
|
46
74
|
|
47
75
|
{
|
48
|
-
const map = new Map();
|
76
|
+
const map = new Map<utilities.Intern<number[]>, number>();
|
49
77
|
map.set($([1]), 1);
|
50
78
|
map.set($([1]), 2);
|
51
79
|
map.size; // => 1
|
@@ -53,7 +81,7 @@ $([1]) === $([1]); // => true
|
|
53
81
|
}
|
54
82
|
|
55
83
|
{
|
56
|
-
const set = new Set();
|
84
|
+
const set = new Set<number[]>();
|
57
85
|
set.add([1]);
|
58
86
|
set.add([1]);
|
59
87
|
set.size; // => 2
|
@@ -61,7 +89,7 @@ $([1]) === $([1]); // => true
|
|
61
89
|
}
|
62
90
|
|
63
91
|
{
|
64
|
-
const set = new Set();
|
92
|
+
const set = new Set<utilities.Intern<number[]>>();
|
65
93
|
set.add($([1]));
|
66
94
|
set.add($([1]));
|
67
95
|
set.size; // => 1
|
@@ -79,6 +107,8 @@ $([1]) === $([1]); // => true
|
|
79
107
|
|
80
108
|
> **Note:** Interning a value is a costly operation which grows more expensive as you intern more values. Only intern values when really necessary.
|
81
109
|
|
110
|
+
> **Note:** Interned objects do not preserve the order of the attributes: `$({ a: 1, b: 2 }) === $({ b: 2, a: 1 })`.
|
111
|
+
|
82
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.
|
83
113
|
|
84
114
|
**Related Work**
|
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
|
-
* ```
|
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
|
@@ -50,6 +60,8 @@
|
|
50
60
|
*
|
51
61
|
* > **Note:** Interning a value is a costly operation which grows more expensive as you intern more values. Only intern values when really necessary.
|
52
62
|
*
|
63
|
+
* > **Note:** Interned objects do not preserve the order of the attributes: `$({ a: 1, b: 2 }) === $({ b: 2, a: 1 })`.
|
64
|
+
*
|
53
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.
|
54
66
|
*
|
55
67
|
* **Related Work**
|
@@ -98,18 +110,24 @@
|
|
98
110
|
* - <https://twitter.com/swannodette/status/1067962983924539392>
|
99
111
|
* - <https://gist.github.com/modernserf/c000e62d40f678cf395e3f360b9b0e43>
|
100
112
|
*/
|
101
|
-
export declare function intern<T extends Array<
|
102
|
-
[key: string]:
|
103
|
-
}>(value: T): T
|
113
|
+
export declare function intern<T extends Array<InternInnerValue> | {
|
114
|
+
[key: string]: InternInnerValue;
|
115
|
+
}>(value: T): Intern<T>;
|
104
116
|
export declare namespace intern {
|
105
|
-
var
|
106
|
-
|
107
|
-
|
108
|
-
|
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
|
+
}>>>;
|
109
126
|
};
|
110
127
|
var finalizationRegistry: FinalizationRegistry<{
|
111
128
|
type: "tuple" | "record";
|
112
129
|
key: Symbol;
|
113
130
|
}>;
|
114
131
|
}
|
132
|
+
export declare const internSymbol: unique symbol;
|
115
133
|
//# 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":"AAAA
|
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,14 +1,14 @@
|
|
1
1
|
/**
|
2
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
3
|
*
|
4
|
-
* ```
|
4
|
+
* ```typescript
|
5
5
|
* import { intern as $ } from "@radically-straightforward/utilities";
|
6
6
|
*
|
7
7
|
* [1] === [1]; // => false
|
8
8
|
* $([1]) === $([1]); // => true
|
9
9
|
*
|
10
10
|
* {
|
11
|
-
* const map = new Map();
|
11
|
+
* const map = new Map<number[], number>();
|
12
12
|
* map.set([1], 1);
|
13
13
|
* map.set([1], 2);
|
14
14
|
* map.size; // => 2
|
@@ -16,7 +16,7 @@
|
|
16
16
|
* }
|
17
17
|
*
|
18
18
|
* {
|
19
|
-
* const map = new Map();
|
19
|
+
* const map = new Map<utilities.Intern<number[]>, number>();
|
20
20
|
* map.set($([1]), 1);
|
21
21
|
* map.set($([1]), 2);
|
22
22
|
* map.size; // => 1
|
@@ -24,7 +24,7 @@
|
|
24
24
|
* }
|
25
25
|
*
|
26
26
|
* {
|
27
|
-
* const set = new Set();
|
27
|
+
* const set = new Set<number[]>();
|
28
28
|
* set.add([1]);
|
29
29
|
* set.add([1]);
|
30
30
|
* set.size; // => 2
|
@@ -32,7 +32,7 @@
|
|
32
32
|
* }
|
33
33
|
*
|
34
34
|
* {
|
35
|
-
* const set = new Set();
|
35
|
+
* const set = new Set<utilities.Intern<number[]>>();
|
36
36
|
* set.add($([1]));
|
37
37
|
* set.add($([1]));
|
38
38
|
* set.size; // => 1
|
@@ -50,6 +50,8 @@
|
|
50
50
|
*
|
51
51
|
* > **Note:** Interning a value is a costly operation which grows more expensive as you intern more values. Only intern values when really necessary.
|
52
52
|
*
|
53
|
+
* > **Note:** Interned objects do not preserve the order of the attributes: `$({ a: 1, b: 2 }) === $({ b: 2, a: 1 })`.
|
54
|
+
*
|
53
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.
|
54
56
|
*
|
55
57
|
* **Related Work**
|
@@ -107,7 +109,7 @@ export function intern(value) {
|
|
107
109
|
throw new Error(`Failed to intern value.`);
|
108
110
|
})();
|
109
111
|
const keys = Object.keys(value);
|
110
|
-
for (const internWeakRef of intern.
|
112
|
+
for (const internWeakRef of intern.pool[type].values()) {
|
111
113
|
const internValue = internWeakRef.deref();
|
112
114
|
if (internValue === undefined ||
|
113
115
|
keys.length !== Object.keys(internValue).length)
|
@@ -125,22 +127,22 @@ export function intern(value) {
|
|
125
127
|
"undefined",
|
126
128
|
].includes(typeof innerValue) ||
|
127
129
|
innerValue === null ||
|
128
|
-
innerValue[
|
130
|
+
innerValue[internSymbol] === true))
|
129
131
|
throw new Error(`Failed to intern value because of non-interned inner value.`);
|
130
132
|
const key = Symbol();
|
131
|
-
value[
|
133
|
+
value[internSymbol] = true;
|
132
134
|
Object.freeze(value);
|
133
|
-
intern.
|
135
|
+
intern.pool[type].set(key, new WeakRef(value));
|
134
136
|
intern.finalizationRegistry.register(value, { type, key });
|
135
137
|
return value;
|
136
138
|
}
|
137
|
-
|
138
|
-
intern.
|
139
|
+
export const internSymbol = Symbol("intern");
|
140
|
+
intern.pool = {
|
139
141
|
tuple: new Map(),
|
140
142
|
record: new Map(),
|
141
143
|
};
|
142
144
|
intern.finalizationRegistry = new FinalizationRegistry(({ type, key }) => {
|
143
|
-
intern.
|
145
|
+
intern.pool[type].delete(key);
|
144
146
|
});
|
145
147
|
/*
|
146
148
|
|
package/build/index.mjs.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"index.mjs","sourceRoot":"","sources":["../source/index.mts"],"names":[],"mappings":"
|
1
|
+
{"version":3,"file":"index.mjs","sourceRoot":"","sources":["../source/index.mts"],"names":[],"mappings":"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"}
|
package/build/index.test.mjs
CHANGED
@@ -2,9 +2,10 @@ 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-
|
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 }));
|
8
9
|
assert($([1]) !== $([2]));
|
9
10
|
{
|
10
11
|
const map = new Map();
|
@@ -35,10 +36,12 @@ test("intern()", () => {
|
|
35
36
|
assert(set.has($([1])));
|
36
37
|
}
|
37
38
|
assert.throws(() => {
|
39
|
+
// @ts-expect-error
|
38
40
|
$([1, {}]);
|
39
41
|
});
|
40
42
|
assert($([1, $({})]) === $([1, $({})]));
|
41
43
|
assert.throws(() => {
|
44
|
+
// @ts-expect-error
|
42
45
|
$([1])[0] = 2;
|
43
46
|
});
|
44
47
|
});
|
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,
|
1
|
+
{"version":3,"file":"index.test.mjs","sourceRoot":"","sources":["../source/index.test.mts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/source/index.mts
CHANGED
@@ -1,14 +1,32 @@
|
|
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>;
|
18
|
+
|
1
19
|
/**
|
2
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:
|
3
21
|
*
|
4
|
-
* ```
|
22
|
+
* ```typescript
|
5
23
|
* import { intern as $ } from "@radically-straightforward/utilities";
|
6
24
|
*
|
7
25
|
* [1] === [1]; // => false
|
8
26
|
* $([1]) === $([1]); // => true
|
9
27
|
*
|
10
28
|
* {
|
11
|
-
* const map = new Map();
|
29
|
+
* const map = new Map<number[], number>();
|
12
30
|
* map.set([1], 1);
|
13
31
|
* map.set([1], 2);
|
14
32
|
* map.size; // => 2
|
@@ -16,7 +34,7 @@
|
|
16
34
|
* }
|
17
35
|
*
|
18
36
|
* {
|
19
|
-
* const map = new Map();
|
37
|
+
* const map = new Map<utilities.Intern<number[]>, number>();
|
20
38
|
* map.set($([1]), 1);
|
21
39
|
* map.set($([1]), 2);
|
22
40
|
* map.size; // => 1
|
@@ -24,7 +42,7 @@
|
|
24
42
|
* }
|
25
43
|
*
|
26
44
|
* {
|
27
|
-
* const set = new Set();
|
45
|
+
* const set = new Set<number[]>();
|
28
46
|
* set.add([1]);
|
29
47
|
* set.add([1]);
|
30
48
|
* set.size; // => 2
|
@@ -32,7 +50,7 @@
|
|
32
50
|
* }
|
33
51
|
*
|
34
52
|
* {
|
35
|
-
* const set = new Set();
|
53
|
+
* const set = new Set<utilities.Intern<number[]>>();
|
36
54
|
* set.add($([1]));
|
37
55
|
* set.add($([1]));
|
38
56
|
* set.size; // => 1
|
@@ -50,6 +68,8 @@
|
|
50
68
|
*
|
51
69
|
* > **Note:** Interning a value is a costly operation which grows more expensive as you intern more values. Only intern values when really necessary.
|
52
70
|
*
|
71
|
+
* > **Note:** Interned objects do not preserve the order of the attributes: `$({ a: 1, b: 2 }) === $({ b: 2, a: 1 })`.
|
72
|
+
*
|
53
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.
|
54
74
|
*
|
55
75
|
* **Related Work**
|
@@ -98,9 +118,9 @@
|
|
98
118
|
* - <https://twitter.com/swannodette/status/1067962983924539392>
|
99
119
|
* - <https://gist.github.com/modernserf/c000e62d40f678cf395e3f360b9b0e43>
|
100
120
|
*/
|
101
|
-
export function intern<
|
102
|
-
|
103
|
-
): T {
|
121
|
+
export function intern<
|
122
|
+
T extends Array<InternInnerValue> | { [key: string]: InternInnerValue },
|
123
|
+
>(value: T): Intern<T> {
|
104
124
|
const type = Array.isArray(value)
|
105
125
|
? "tuple"
|
106
126
|
: typeof value === "object" && value !== null
|
@@ -109,15 +129,15 @@ export function intern<T extends Array<unknown> | { [key: string]: unknown }>(
|
|
109
129
|
throw new Error(`Failed to intern value.`);
|
110
130
|
})();
|
111
131
|
const keys = Object.keys(value);
|
112
|
-
for (const internWeakRef of intern.
|
132
|
+
for (const internWeakRef of intern.pool[type].values()) {
|
113
133
|
const internValue = internWeakRef.deref();
|
114
134
|
if (
|
115
135
|
internValue === undefined ||
|
116
136
|
keys.length !== Object.keys(internValue).length
|
117
137
|
)
|
118
138
|
continue;
|
119
|
-
if (keys.every((key) => (value as any)[key] === internValue[key]))
|
120
|
-
return internValue;
|
139
|
+
if (keys.every((key) => (value as any)[key] === (internValue as any)[key]))
|
140
|
+
return internValue as any;
|
121
141
|
}
|
122
142
|
for (const innerValue of Object.values(value))
|
123
143
|
if (
|
@@ -131,32 +151,35 @@ export function intern<T extends Array<unknown> | { [key: string]: unknown }>(
|
|
131
151
|
"undefined",
|
132
152
|
].includes(typeof innerValue) ||
|
133
153
|
innerValue === null ||
|
134
|
-
(innerValue as any)[
|
154
|
+
(innerValue as any)[internSymbol] === true
|
135
155
|
)
|
136
156
|
)
|
137
157
|
throw new Error(
|
138
158
|
`Failed to intern value because of non-interned inner value.`,
|
139
159
|
);
|
140
160
|
const key = Symbol();
|
141
|
-
(value as any)[
|
161
|
+
(value as any)[internSymbol] = true;
|
142
162
|
Object.freeze(value);
|
143
|
-
intern.
|
163
|
+
intern.pool[type].set(key, new WeakRef(value as any));
|
144
164
|
intern.finalizationRegistry.register(value, { type, key });
|
145
|
-
return value;
|
165
|
+
return value as any;
|
146
166
|
}
|
147
167
|
|
148
|
-
|
168
|
+
export const internSymbol = Symbol("intern");
|
149
169
|
|
150
|
-
intern.
|
151
|
-
tuple: new Map<Symbol, WeakRef<
|
152
|
-
record: new Map<
|
170
|
+
intern.pool = {
|
171
|
+
tuple: new Map<Symbol, WeakRef<Intern<InternInnerValue[]>>>(),
|
172
|
+
record: new Map<
|
173
|
+
Symbol,
|
174
|
+
WeakRef<Intern<{ [key: string]: InternInnerValue }>>
|
175
|
+
>(),
|
153
176
|
};
|
154
177
|
|
155
178
|
intern.finalizationRegistry = new FinalizationRegistry<{
|
156
179
|
type: "tuple" | "record";
|
157
180
|
key: Symbol;
|
158
181
|
}>(({ type, key }) => {
|
159
|
-
intern.
|
182
|
+
intern.pool[type].delete(key);
|
160
183
|
});
|
161
184
|
|
162
185
|
/*
|
package/source/index.test.mts
CHANGED
@@ -4,13 +4,15 @@ import * as utilities from "./index.mjs";
|
|
4
4
|
import { intern as $ } from "./index.mjs";
|
5
5
|
|
6
6
|
test("intern()", () => {
|
7
|
-
// @ts-
|
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
|
+
|
10
12
|
assert($([1]) !== $([2]));
|
11
13
|
|
12
14
|
{
|
13
|
-
const map = new Map();
|
15
|
+
const map = new Map<number[], number>();
|
14
16
|
map.set([1], 1);
|
15
17
|
map.set([1], 2);
|
16
18
|
assert.equal(map.size, 2);
|
@@ -18,7 +20,7 @@ test("intern()", () => {
|
|
18
20
|
}
|
19
21
|
|
20
22
|
{
|
21
|
-
const map = new Map();
|
23
|
+
const map = new Map<utilities.Intern<number[]>, number>();
|
22
24
|
map.set($([1]), 1);
|
23
25
|
map.set($([1]), 2);
|
24
26
|
assert.equal(map.size, 1);
|
@@ -26,7 +28,7 @@ test("intern()", () => {
|
|
26
28
|
}
|
27
29
|
|
28
30
|
{
|
29
|
-
const set = new Set();
|
31
|
+
const set = new Set<number[]>();
|
30
32
|
set.add([1]);
|
31
33
|
set.add([1]);
|
32
34
|
assert.equal(set.size, 2);
|
@@ -34,7 +36,7 @@ test("intern()", () => {
|
|
34
36
|
}
|
35
37
|
|
36
38
|
{
|
37
|
-
const set = new Set();
|
39
|
+
const set = new Set<utilities.Intern<number[]>>();
|
38
40
|
set.add($([1]));
|
39
41
|
set.add($([1]));
|
40
42
|
assert.equal(set.size, 1);
|
@@ -42,11 +44,13 @@ test("intern()", () => {
|
|
42
44
|
}
|
43
45
|
|
44
46
|
assert.throws(() => {
|
47
|
+
// @ts-expect-error
|
45
48
|
$([1, {}]);
|
46
49
|
});
|
47
50
|
assert($([1, $({})]) === $([1, $({})]));
|
48
51
|
|
49
52
|
assert.throws(() => {
|
53
|
+
// @ts-expect-error
|
50
54
|
$([1])[0] = 2;
|
51
55
|
});
|
52
56
|
});
|