Repository navigation
Preferences Persistence: Address TypeScript conversion follow-ups #84375
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
manzoorwanijk
merged 16 commits into
trunk
from
refactor/preferences-persistence-follow-ups
Oct 13, 2026
+403
−92
Merged
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
08995e6
Preferences Persistence: Read local storage through a helper that tol…
manzoorwanijk 4ae358c
Preferences Persistence: Ignore persisted data that is not an object
manzoorwanijk 04760b9
Preferences Persistence: Return a new empty object instead of a share…
manzoorwanijk c37f1a6
Preferences Persistence: Stop the migrations mutating their input
manzoorwanijk 6363273
Preferences: Use the PersistenceLayer type from preferences-persistence
manzoorwanijk e98131c
Preferences Persistence: Cover blocked storage and invalid local data
manzoorwanijk 806dc52
Add CHANGELOG entries
manzoorwanijk ec2fad1
Preferences Persistence: Preload an empty object for empty user meta
manzoorwanijk d733e9c
Preferences: Keep its own persistence layer type to avoid a runtime d…
manzoorwanijk e9bff56
Preferences Persistence: Ignore legacy preferences that are not an ob…
manzoorwanijk 8324647
Preferences Persistence: Always preload the server data
manzoorwanijk 467514b
Preferences Persistence: Leave scopes that are not objects untouched …
manzoorwanijk da70576
Preferences Persistence: Exercise get and set with blocked storage in…
manzoorwanijk f26dc46
Preferences Persistence: Share the logic that picks the newest prefer…
manzoorwanijk ea33889
Preferences Persistence: Remove invalid scopes in the editor settings…
manzoorwanijk ae371f8
Preferences Persistence: Validate and migrate the data create() loads
manzoorwanijk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| import isPreferencesData from './is-preferences-data'; | ||
| import { readStoredJSON } from './local-storage'; | ||
| import convertPreferencesPackageData from './migrations/preferences-package-data'; | ||
| import type { PreferencesData, ScopedPreferences } from './types'; | ||
|
|
||
| /** | ||
| * Picks the most recently modified preferences from the server data and the | ||
| * local storage backup, ignoring either when it is not a preferences object. | ||
| * | ||
| * @param serverData Preferences data from the server. | ||
| * @param localStorageRestoreKey The key of the local storage backup. | ||
| * | ||
| * @return The newest preferences data, or `null` when neither is valid. | ||
| */ | ||
| function pickNewestData( | ||
| serverData: unknown, | ||
| localStorageRestoreKey: string | ||
| ): PreferencesData | null { | ||
| const storedData = readStoredJSON( localStorageRestoreKey ); | ||
| const localData = isPreferencesData( storedData ) ? storedData : null; | ||
| if ( ! isPreferencesData( serverData ) ) { | ||
| return localData; | ||
| } | ||
|
|
||
| // Date parse returns NaN for invalid input. Coerce anything invalid | ||
| // into a conveniently comparable zero. | ||
| const serverModified = Date.parse( serverData._modified ?? '' ) || 0; | ||
| const localModified = Date.parse( localData?._modified ?? '' ) || 0; | ||
|
|
||
| // Prefer server data when it is at least as recent. | ||
| return localData && localModified > serverModified ? localData : serverData; | ||
| } | ||
|
|
||
| /** | ||
| * Gets the newest persisted preferences, migrated to the current format. | ||
| * | ||
| * @param serverData Preferences data from the server. | ||
| * @param localStorageRestoreKey The key of the local storage backup. | ||
| * | ||
| * @return The migrated preferences, or `undefined` when there are none. | ||
| */ | ||
| export default function getNewestData( | ||
| serverData: unknown, | ||
| localStorageRestoreKey: string | ||
| ): ScopedPreferences | undefined { | ||
| const newestData = pickNewestData( serverData, localStorageRestoreKey ); | ||
| return newestData | ||
| ? convertPreferencesPackageData( newestData as ScopedPreferences ) | ||
| : undefined; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
15 changes: 15 additions & 0 deletions
15
packages/preferences-persistence/src/is-preferences-data.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| import type { PreferencesData } from './types'; | ||
|
|
||
| /** | ||
| * Checks whether persisted data is a preferences object. Empty user meta | ||
| * reaches the client as `[]` or `''`, which must not be used as preferences. | ||
| * | ||
| * @param data The persisted data. | ||
| * | ||
| * @return Whether the data is a preferences object. | ||
| */ | ||
| export default function isPreferencesData( | ||
| data: unknown | ||
| ): data is PreferencesData { | ||
| return !! data && typeof data === 'object' && ! Array.isArray( data ); | ||
|
manzoorwanijk marked this conversation as resolved.
|
||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| /** | ||
| * Reads and parses a JSON value from local storage. | ||
| * | ||
| * @param key The local storage key. | ||
| * | ||
| * @return The parsed value, or `null` when the value is missing or invalid, | ||
| * or local storage is unavailable. | ||
| */ | ||
| export function readStoredJSON( key: string ): unknown { | ||
| try { | ||
| const value = window.localStorage.getItem( key ); | ||
| return value === null ? null : JSON.parse( value ); | ||
| } catch { | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Stores a value in local storage as JSON. | ||
| * | ||
| * Failures, such as blocked storage or an exceeded quota, are ignored. | ||
| * | ||
| * @param key The local storage key. | ||
| * @param value The value to store. | ||
| */ | ||
| export function writeStoredJSON( key: string, value: unknown ) { | ||
| try { | ||
| window.localStorage.setItem( key, JSON.stringify( value ) ); | ||
| } catch {} | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
27 changes: 15 additions & 12 deletions
27
...rences-persistence/src/migrations/preferences-package-data/convert-complementary-areas.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,18 +1,21 @@ | ||
| import type { ScopedPreferences } from '../../types'; | ||
|
|
||
| export default function convertComplementaryAreas( state: ScopedPreferences ) { | ||
| return Object.keys( state ).reduce( ( stateAccumulator, scope ) => { | ||
| const scopeData = state[ scope ]; | ||
| return Object.keys( state ).reduce( | ||
| ( stateAccumulator, scope ) => { | ||
| const scopeData = state[ scope ]; | ||
|
|
||
| // If a complementary area is truthy, convert it to the `isComplementaryAreaVisible` boolean. | ||
| if ( scopeData?.complementaryArea ) { | ||
| const updatedScopeData = { ...scopeData }; | ||
| delete updatedScopeData.complementaryArea; | ||
| updatedScopeData.isComplementaryAreaVisible = true; | ||
| stateAccumulator[ scope ] = updatedScopeData; | ||
| return stateAccumulator; | ||
| } | ||
| // If a complementary area is truthy, convert it to the `isComplementaryAreaVisible` boolean. | ||
| if ( scopeData?.complementaryArea ) { | ||
| const updatedScopeData = { ...scopeData }; | ||
| delete updatedScopeData.complementaryArea; | ||
| updatedScopeData.isComplementaryAreaVisible = true; | ||
| stateAccumulator[ scope ] = updatedScopeData; | ||
| return stateAccumulator; | ||
| } | ||
|
|
||
| return stateAccumulator; | ||
| }, state ); | ||
| return stateAccumulator; | ||
| }, | ||
| { ...state } | ||
| ); | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.