@@ -27,9 +27,11 @@ type RecursivePartial<T> = {
27
27
? RecursivePartial < U > [ ]
28
28
: T [ P ] extends Primitive
29
29
? T [ P ]
30
- : RecursivePartial < T > ;
30
+ : RecursivePartial < T [ P ] > ;
31
31
} ;
32
32
33
+ type NotArrayLike < T > = T extends [ ] ? never : T ;
34
+
33
35
/**
34
36
* An anonymizing function
35
37
*
@@ -50,7 +52,7 @@ export type Anonymizer<T> = (value: T) => T extends Primitive ? T : RecursivePar
50
52
* get an anonymized representation of the state.
51
53
*/
52
54
export type StateMetadata < T > = {
53
- [ P in keyof T ] : StatePropertyMetadata < T [ P ] > ;
55
+ [ P in keyof T ] : T [ P ] extends Primitive ? StatePropertyMetadata < T [ P ] > : StateMetadata < T [ P ] > | StatePropertyMetadata < T [ P ] > ;
54
56
} ;
55
57
56
58
/**
@@ -63,9 +65,9 @@ export type StateMetadata<T> = {
63
65
* identifiable), or is set to a function that returns an anonymized
64
66
* representation of this state.
65
67
*/
66
- export interface StatePropertyMetadata < P > {
68
+ export interface StatePropertyMetadata < T > {
67
69
persist : boolean ;
68
- anonymous : boolean | Anonymizer < P > ;
70
+ anonymous : boolean | Anonymizer < T > ;
69
71
}
70
72
71
73
/**
@@ -153,10 +155,26 @@ export class BaseController<S extends Record<string, any>> {
153
155
}
154
156
155
157
// This function acts as a type guard. Using a `typeof` conditional didn't seem to work.
156
- function isAnonymizingFunction < T > ( x : boolean | Anonymizer < T > ) : x is Anonymizer < T > {
158
+ function isAnonymizingFunction < T > ( x : boolean | Anonymizer < T > | StateMetadata < T > ) : x is Anonymizer < T > {
157
159
return typeof x === 'function' ;
158
160
}
159
161
162
+ function isStateMetadata < T > ( x : StatePropertyMetadata < T > | StateMetadata < T > ) : x is StateMetadata < T > {
163
+ return Object . keys ( x ) . sort ( ) !== [ 'anonymous' , 'persist' ] ;
164
+ }
165
+
166
+ function isStatePropertyMetadata < T > ( x : StatePropertyMetadata < T > | StateMetadata < T > ) : x is StatePropertyMetadata < T > {
167
+ return Object . keys ( x ) . sort ( ) === [ 'anonymous' , 'persist' ] ;
168
+ }
169
+
170
+ function isPrimitive ( x : any ) : x is Primitive {
171
+ return [ 'boolean' , 'string' , 'number' , 'null' ] . includes ( typeof x ) ;
172
+ }
173
+
174
+ function isNotArrayLike < T > ( x : any ) : x is NotArrayLike < T > {
175
+ return ! Array . isArray ( x ) ;
176
+ }
177
+
160
178
/**
161
179
* Returns an anonymized representation of the controller state.
162
180
*
@@ -174,11 +192,20 @@ export function getAnonymizedState<S extends Record<string, any>>(
174
192
) : RecursivePartial < S > {
175
193
return Object . keys ( state ) . reduce ( ( anonymizedState , _key ) => {
176
194
const key : keyof S = _key ; // https://stackoverflow.com/questions/63893394/string-cannot-be-used-to-index-type-t
177
- const metadataValue = metadata [ key ] . anonymous ;
178
- if ( isAnonymizingFunction ( metadataValue ) ) {
179
- anonymizedState [ key ] = metadataValue ( state [ key ] ) ;
180
- } else if ( metadataValue ) {
181
- anonymizedState [ key ] = state [ key ] ;
195
+ const propertyMetadata = metadata [ key ] ;
196
+ const propertyValue = state [ key ] ;
197
+ if ( isStateMetadata ( propertyMetadata ) ) {
198
+ if ( isPrimitive ( propertyValue ) || ! isNotArrayLike ( propertyValue ) ) {
199
+ throw new Error ( 'Invalid metadata' ) ;
200
+ }
201
+ anonymizedState [ key ] = getAnonymizedState ( propertyValue , propertyMetadata ) ;
202
+ } else if ( isStatePropertyMetadata ( propertyMetadata ) ) {
203
+ const metadataValue = propertyMetadata . anonymous ;
204
+ if ( isAnonymizingFunction ( metadataValue ) ) {
205
+ anonymizedState [ key ] = metadataValue ( state [ key ] ) ;
206
+ } else if ( metadataValue ) {
207
+ anonymizedState [ key ] = state [ key ] ;
208
+ }
182
209
}
183
210
return anonymizedState ;
184
211
} , { } as RecursivePartial < S > ) ;
@@ -191,12 +218,25 @@ export function getAnonymizedState<S extends Record<string, any>>(
191
218
* @param metadata - The controller state metadata, which describes which pieces of state should be persisted
192
219
* @returns The subset of controller state that should be persisted
193
220
*/
194
- export function getPersistentState < S extends Record < string , any > > ( state : S , metadata : StateMetadata < S > ) : Partial < S > {
221
+ export function getPersistentState < S extends Record < string , any > > (
222
+ state : S ,
223
+ metadata : StateMetadata < S > ,
224
+ ) : RecursivePartial < S > {
195
225
return Object . keys ( state ) . reduce ( ( persistedState , _key ) => {
196
226
const key : keyof S = _key ; // https://stackoverflow.com/questions/63893394/string-cannot-be-used-to-index-type-t
197
- if ( metadata [ key ] . persist ) {
198
- persistedState [ key ] = state [ key ] ;
227
+ const propertyMetadata = metadata [ key ] ;
228
+ const propertyValue = state [ key ] ;
229
+ if ( isStateMetadata ( propertyMetadata ) ) {
230
+ if ( isPrimitive ( propertyValue ) || ! isNotArrayLike ( propertyValue ) ) {
231
+ throw new Error ( 'Invalid metadata' ) ;
232
+ }
233
+ persistedState [ key ] = getPersistentState ( propertyValue , propertyMetadata ) ;
234
+ } else if ( isStatePropertyMetadata ( propertyMetadata ) ) {
235
+ if ( metadata [ key ] . persist ) {
236
+ persistedState [ key ] = state [ key ] ;
237
+ }
199
238
}
239
+
200
240
return persistedState ;
201
- } , { } as Partial < S > ) ;
241
+ } , { } as RecursivePartial < S > ) ;
202
242
}
0 commit comments