Skip to content

Commit b54eee6

Browse files
committed
Allow nested metadata
1 parent 2458c4e commit b54eee6

File tree

1 file changed

+54
-14
lines changed

1 file changed

+54
-14
lines changed

src/BaseControllerV2.ts

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@ type RecursivePartial<T> = {
2727
? RecursivePartial<U>[]
2828
: T[P] extends Primitive
2929
? T[P]
30-
: RecursivePartial<T>;
30+
: RecursivePartial<T[P]>;
3131
};
3232

33+
type NotArrayLike<T> = T extends [] ? never : T;
34+
3335
/**
3436
* An anonymizing function
3537
*
@@ -50,7 +52,7 @@ export type Anonymizer<T> = (value: T) => T extends Primitive ? T : RecursivePar
5052
* get an anonymized representation of the state.
5153
*/
5254
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]>;
5456
};
5557

5658
/**
@@ -63,9 +65,9 @@ export type StateMetadata<T> = {
6365
* identifiable), or is set to a function that returns an anonymized
6466
* representation of this state.
6567
*/
66-
export interface StatePropertyMetadata<P> {
68+
export interface StatePropertyMetadata<T> {
6769
persist: boolean;
68-
anonymous: boolean | Anonymizer<P>;
70+
anonymous: boolean | Anonymizer<T>;
6971
}
7072

7173
/**
@@ -153,10 +155,26 @@ export class BaseController<S extends Record<string, any>> {
153155
}
154156

155157
// 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> {
157159
return typeof x === 'function';
158160
}
159161

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+
160178
/**
161179
* Returns an anonymized representation of the controller state.
162180
*
@@ -174,11 +192,20 @@ export function getAnonymizedState<S extends Record<string, any>>(
174192
): RecursivePartial<S> {
175193
return Object.keys(state).reduce((anonymizedState, _key) => {
176194
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+
}
182209
}
183210
return anonymizedState;
184211
}, {} as RecursivePartial<S>);
@@ -191,12 +218,25 @@ export function getAnonymizedState<S extends Record<string, any>>(
191218
* @param metadata - The controller state metadata, which describes which pieces of state should be persisted
192219
* @returns The subset of controller state that should be persisted
193220
*/
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> {
195225
return Object.keys(state).reduce((persistedState, _key) => {
196226
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+
}
199238
}
239+
200240
return persistedState;
201-
}, {} as Partial<S>);
241+
}, {} as RecursivePartial<S>);
202242
}

0 commit comments

Comments
 (0)