1
1
import type { AsyncLocalStorage } from "node:async_hooks" ;
2
2
import { createScope } from "#scope" ;
3
3
4
+ import type { ReactElement } from "react" ;
5
+
4
6
const MUTABLE_SET_METHODS = new Set < string | symbol > ( [
5
7
"add" ,
6
8
"delete" ,
@@ -26,13 +28,22 @@ const MUTABLE_DATE_METHODS = new Set<string | symbol>([
26
28
"setYear" ,
27
29
] ) ;
28
30
29
- function immutableProxy < T > ( value : T ) : T {
31
+ function isReactObject ( value : unknown ) : boolean {
32
+ return (
33
+ ( typeof value === "object" || typeof value === "function" ) &&
34
+ value !== null &&
35
+ "$$typeof" in value
36
+ ) ;
37
+ }
38
+
39
+ function immutableProxy < T > ( value : T ) : Immutable < T > {
30
40
if (
41
+ isReactObject ( value ) ||
31
42
( typeof value !== "object" && typeof value !== "function" ) ||
32
43
value === null
33
44
) {
34
45
// Primitive values are implicitly immutable.
35
- return value ;
46
+ return value as Immutable < T > ;
36
47
}
37
48
38
49
if ( value instanceof Promise ) {
@@ -41,13 +52,7 @@ function immutableProxy<T>(value: T): T {
41
52
) ;
42
53
}
43
54
44
- // This effectively prevents mutation of arrays and objects.
45
55
Object . preventExtensions ( value ) ;
46
- Reflect . ownKeys ( value ) . forEach ( ( property ) => {
47
- Object . defineProperty ( value , property , {
48
- writable : false ,
49
- } ) ;
50
- } ) ;
51
56
52
57
return new Proxy ( value , {
53
58
get ( target , property , receiver ) {
@@ -69,30 +74,28 @@ function immutableProxy<T>(value: T): T {
69
74
: returnValue ,
70
75
) ;
71
76
} ,
72
- } ) ;
77
+ } ) as Immutable < T > ;
73
78
}
74
79
75
- function makeImmutable < T > ( value : T ) : T {
80
+ function makeImmutable < T > ( value : T ) : Immutable < T > {
76
81
if (
77
82
value instanceof Promise ||
78
83
value instanceof Set ||
79
84
value instanceof Map ||
80
- value instanceof Date
85
+ value instanceof Date ||
86
+ isReactObject ( value )
81
87
) {
82
88
return immutableProxy ( value ) ;
83
89
}
84
90
85
91
if ( typeof value === "object" && value !== null ) {
86
- Object . preventExtensions ( value ) ;
87
92
Reflect . ownKeys ( value ) . forEach ( ( property ) => {
88
- Object . defineProperty ( value , property , {
89
- writable : false ,
90
- } ) ;
91
93
makeImmutable ( value [ property as keyof typeof value ] ) ;
92
94
} ) ;
95
+ Object . freeze ( value ) ;
93
96
}
94
97
95
- return value ;
98
+ return value as Immutable < T > ;
96
99
}
97
100
98
101
export type ReadonlyDate = Readonly <
@@ -124,24 +127,26 @@ export type Immutable<T> = T extends (...args: infer Ks) => infer V
124
127
? ReadonlySet < Immutable < S > >
125
128
: T extends Map < infer K , infer V >
126
129
? ReadonlyMap < Immutable < K > , Immutable < V > >
127
- : {
128
- readonly [ K in keyof T ] : Immutable < T [ K ] > ;
129
- } ;
130
-
131
- function isCallable ( value : unknown ) : value is ( ...args : unknown [ ] ) => unknown {
130
+ : T extends ReactElement < unknown >
131
+ ? T
132
+ : {
133
+ readonly [ K in keyof T ] : Immutable < T [ K ] > ;
134
+ } ;
135
+
136
+ function isCallable < T > (
137
+ value : ReadOnlyInitializer < T > ,
138
+ ) : value is ClosureInitializer < T > {
132
139
return typeof value === "function" ;
133
140
}
134
141
135
- export function readOnly < T > (
136
- initializer : ( ) => T extends Promise < unknown > ? never : T ,
137
- ) : Immutable < T > ;
138
- export function readOnly < T > (
139
- initializer : T extends ( ...args : unknown [ ] ) => unknown
142
+ type ClosureInitializer < T > = ( ) => T extends Promise < unknown > ? never : T ;
143
+ type InlineInitializer < T > = T extends ( ...args : unknown [ ] ) => unknown
144
+ ? never
145
+ : T extends Promise < unknown >
140
146
? never
141
- : T extends Promise < unknown >
142
- ? never
143
- : T ,
144
- ) : Immutable < T > ;
147
+ : T ;
148
+
149
+ type ReadOnlyInitializer < T > = ClosureInitializer < T > | InlineInitializer < T > ;
145
150
146
151
/**
147
152
* Mark the lifetime of a provided block of code as "forever" with safety
@@ -151,7 +156,7 @@ export function readOnly<T>(
151
156
* @return The provided callback's return value, frozen, and wrapped in a
152
157
* readOnly-enforcing Proxy.
153
158
*/
154
- export function readOnly < T > ( initializer : T | ( ( ) => T ) ) : T {
159
+ export function readOnly < T > ( initializer : ReadOnlyInitializer < T > ) : Immutable < T > {
155
160
if ( isCallable ( initializer ) ) {
156
161
return makeImmutable ( initializer ( ) ) ;
157
162
}
0 commit comments