added ability to granularly modify objects in simple storage

This commit is contained in:
Henry Jameson 2025-03-20 19:32:52 +02:00
commit 5e880ed54f
2 changed files with 143 additions and 6 deletions

View file

@ -3,6 +3,7 @@ import {
isEqual, isEqual,
cloneDeep, cloneDeep,
set, set,
unset,
get, get,
clamp, clamp,
flatten, flatten,
@ -35,7 +36,8 @@ export const defaultState = {
_journal: [], _journal: [],
simple: { simple: {
dontShowUpdateNotifs: false, dontShowUpdateNotifs: false,
collapseNav: false collapseNav: false,
filters: {}
}, },
collections: { collections: {
pinnedStatusActions: ['reply', 'retweet', 'favorite', 'emoji'], pinnedStatusActions: ['reply', 'retweet', 'favorite', 'emoji'],
@ -78,11 +80,16 @@ const _verifyPrefs = (state) => {
simple: {}, simple: {},
collections: {} collections: {}
} }
// Simple
Object.entries(defaultState.prefsStorage.simple).forEach(([k, v]) => { Object.entries(defaultState.prefsStorage.simple).forEach(([k, v]) => {
if (typeof v === 'number' || typeof v === 'boolean') return if (typeof v === 'number' || typeof v === 'boolean') return
if (typeof v === 'object' && v != null) return
console.warn(`Preference simple.${k} as invalid type, reinitializing`) console.warn(`Preference simple.${k} as invalid type, reinitializing`)
set(state.prefsStorage.simple, k, defaultState.prefsStorage.simple[k]) set(state.prefsStorage.simple, k, defaultState.prefsStorage.simple[k])
}) })
// Collections
Object.entries(defaultState.prefsStorage.collections).forEach(([k, v]) => { Object.entries(defaultState.prefsStorage.collections).forEach(([k, v]) => {
if (Array.isArray(v)) return if (Array.isArray(v)) return
console.warn(`Preference collections.${k} as invalid type, reinitializing`) console.warn(`Preference collections.${k} as invalid type, reinitializing`)
@ -224,8 +231,27 @@ export const _mergePrefs = (recent, stale) => {
} }
switch (operation) { switch (operation) {
case 'set': case 'set':
if (path.startsWith('collections') || path.startsWith('objectCollections')) {
console.error('Illegal operation "set" on a collection')
return
}
if (path.split(/\./g).length <= 1) {
console.error(`Calling set on depth <= 1 (path: ${path}) is not allowed`)
return
}
set(resultOutput, path, args[0]) set(resultOutput, path, args[0])
break break
case 'unset':
if (path.startsWith('collections') || path.startsWith('objectCollections')) {
console.error('Illegal operation "unset" on a collection')
return
}
if (path.split(/\./g).length <= 2) {
console.error(`Calling unset on depth <= 2 (path: ${path}) is not allowed`)
return
}
unset(resultOutput, path)
break
case 'addToCollection': case 'addToCollection':
set(resultOutput, path, Array.from(new Set(get(resultOutput, path)).add(args[0]))) set(resultOutput, path, Array.from(new Set(get(resultOutput, path)).add(args[0])))
break break
@ -380,7 +406,15 @@ export const mutations = {
}, },
setPreference (state, { path, value }) { setPreference (state, { path, value }) {
if (path.startsWith('_')) { if (path.startsWith('_')) {
console.error(`tried to edit internal (starts with _) field '${path}', ignoring.`) console.error(`Tried to edit internal (starts with _) field '${path}', ignoring.`)
return
}
if (path.startsWith('collections') || path.startsWith('objectCollections')) {
console.error(`Invalid operation 'set' for collection field '${path}', ignoring.`)
return
}
if (path.split(/\./g).length <= 1) {
console.error(`Calling set on depth <= 1 (path: ${path}) is not allowed`)
return return
} }
set(state.prefsStorage, path, value) set(state.prefsStorage, path, value)
@ -390,14 +424,46 @@ export const mutations = {
] ]
state.dirty = true state.dirty = true
}, },
unsetPreference (state, { path, value }) {
if (path.startsWith('_')) {
console.error(`Tried to edit internal (starts with _) field '${path}', ignoring.`)
return
}
if (path.startsWith('collections') || path.startsWith('objectCollections')) {
console.error(`Invalid operation 'unset' for collection field '${path}', ignoring.`)
return
}
if (path.split(/\./g).length <= 2) {
console.error(`Calling unset on depth <= 2 (path: ${path}) is not allowed`)
return
}
unset(state.prefsStorage, path, value)
state.prefsStorage._journal = [
...state.prefsStorage._journal,
{ operation: 'unset', path, args: [], timestamp: Date.now() }
]
state.dirty = true
},
addCollectionPreference (state, { path, value }) { addCollectionPreference (state, { path, value }) {
if (path.startsWith('_')) { if (path.startsWith('_')) {
console.error(`tried to edit internal (starts with _) field '${path}', ignoring.`) console.error(`tried to edit internal (starts with _) field '${path}', ignoring.`)
return return
} }
if (path.startsWith('collections')) {
const collection = new Set(get(state.prefsStorage, path)) const collection = new Set(get(state.prefsStorage, path))
collection.add(value) collection.add(value)
set(state.prefsStorage, path, [...collection]) set(state.prefsStorage, path, [...collection])
} else if (path.startsWith('objectCollections')) {
const { _key } = value
if (!_key && typeof _key !== 'string') {
console.error('Object for storage is missing _key field! ignoring')
return
}
const collection = new Set(get(state.prefsStorage, path + '.index'))
collection.add(_key)
set(state.prefsStorage, path + '.index', [...collection])
set(state.prefsStorage, path + '.data.' + _key, value)
}
state.prefsStorage._journal = [ state.prefsStorage._journal = [
...state.prefsStorage._journal, ...state.prefsStorage._journal,
{ operation: 'addToCollection', path, args: [value], timestamp: Date.now() } { operation: 'addToCollection', path, args: [value], timestamp: Date.now() }

View file

@ -107,7 +107,7 @@ describe('The serverSideStorage module', () => {
}) })
}) })
describe('setPreference', () => { describe('setPreference', () => {
const { setPreference, updateCache, addCollectionPreference, removeCollectionPreference } = mutations const { setPreference, unsetPreference, updateCache, addCollectionPreference, removeCollectionPreference } = mutations
it('should set preference and update journal log accordingly', () => { it('should set preference and update journal log accordingly', () => {
const state = cloneDeep(defaultState) const state = cloneDeep(defaultState)
@ -160,6 +160,25 @@ describe('The serverSideStorage module', () => {
expect(state.prefsStorage.collections.testing).to.eql([2]) expect(state.prefsStorage.collections.testing).to.eql([2])
expect(state.prefsStorage._journal.length).to.eql(2) expect(state.prefsStorage._journal.length).to.eql(2)
}) })
it('should remove depth = 3 set/unset entries from journal', () => {
const state = cloneDeep(defaultState)
setPreference(state, { path: 'simple.object.foo', value: 1 })
unsetPreference(state, { path: 'simple.object.foo' })
updateCache(state, { username: 'test' })
expect(state.prefsStorage.simple.object).to.not.have.property('foo')
expect(state.prefsStorage._journal.length).to.eql(1)
})
it('should not allow unsetting depth <= 2', () => {
const state = cloneDeep(defaultState)
setPreference(state, { path: 'simple.object.foo', value: 1 })
unsetPreference(state, { path: 'simple.object' })
unsetPreference(state, { path: 'simple' })
updateCache(state, { username: 'test' })
expect(state.prefsStorage.simple.object).to.have.property('foo')
expect(state.prefsStorage._journal.length).to.eql(1)
})
}) })
}) })
@ -315,6 +334,58 @@ describe('The serverSideStorage module', () => {
] ]
}) })
}) })
it('should work with objects', () => {
expect(
_mergePrefs(
// RECENT
{
simple: { lv2: { lv3: 'foo' } },
_journal: [
{ path: 'simple.lv2.lv3', operation: 'set', args: ['foo'], timestamp: 2 }
]
},
// STALE
{
simple: { lv2: { lv3: 'bar' } },
_journal: [
{ path: 'simple.lv2.lv3', operation: 'set', args: ['bar'], timestamp: 4 }
]
}
)
).to.eql({
simple: { lv2: { lv3: 'bar' } },
_journal: [
{ path: 'simple.lv2.lv3', operation: 'set', args: ['bar'], timestamp: 4 }
]
})
})
it('should work with unset', () => {
expect(
_mergePrefs(
// RECENT
{
simple: { lv2: { lv3: 'foo' } },
_journal: [
{ path: 'simple.lv2.lv3', operation: 'set', args: ['foo'], timestamp: 2 }
]
},
// STALE
{
simple: { lv2: {} },
_journal: [
{ path: 'simple.lv2.lv3', operation: 'unset', args: [], timestamp: 4 }
]
}
)
).to.eql({
simple: { lv2: {} },
_journal: [
{ path: 'simple.lv2.lv3', operation: 'unset', args: [], timestamp: 4 }
]
})
})
}) })
describe('_resetFlags', () => { describe('_resetFlags', () => {