refactor(search): extract shared filter helper

Share filter parsing and provider mapping logic between Stork and upcoming search adapters.

This keeps the existing Stork API stable while adding Pagefind-oriented filter serialization under test.
This commit is contained in:
ThatGuySam 2026-03-15 13:41:33 -05:00
parent e5f28b16ee
commit 727f84e4c2
3 changed files with 135 additions and 120 deletions

119
helpers/search/filters.js Normal file
View file

@ -0,0 +1,119 @@
import { filterSeparator } from '~/helpers/constants.js'
import { isString } from '~/helpers/check-types.js'
export class SearchFilters {
constructor({
initialFilters = {}
} = {}) {
this.initialFilters = initialFilters
this.filters = {
...initialFilters
}
}
get list () {
return Object.entries( this.filters ).map( ([ filterKey, filterValue ]) => {
return `${ filterKey }${ filterSeparator }${ filterValue }`
} )
}
get asQuery () {
return this.list.join(' ')
}
get asPagefindFilters () {
return Object.fromEntries( Object.entries( this.filters ).map( ([ filterKey, filterValue ]) => {
return [ filterKey, [ filterValue ] ]
}) )
}
getByKey ( key ) {
return `${ key }${ filterSeparator }${ this.filters[ key ] }`
}
isQueryValue ( filterNameOrQueryValue ) {
return filterNameOrQueryValue.includes( filterSeparator )
}
getKeyAndValue ( filterQueryValue ) {
const key = filterQueryValue.substring(0, filterQueryValue.indexOf( filterSeparator ))
const value = filterQueryValue.substring(filterQueryValue.indexOf( filterSeparator )+1)
return { key, value }
}
getFilterNameAndValueFromString ( filterNameOrQueryValue ) {
if ( this.isQueryValue( filterNameOrQueryValue ) ) {
return this.getKeyAndValue( filterNameOrQueryValue )
}
return {
key: filterNameOrQueryValue,
value: null
}
}
remove ( filterName ) {
if ( this.isQueryValue( filterName ) ) throw new Error(`${ filterName } is not a valid filter name`)
delete this.filters[ filterName ]
}
setFromStringArray ( filterStringArray ) {
filterStringArray.forEach( filterString => {
const { key, value } = this.getFilterNameAndValueFromString( filterString )
this.filters[ key ] = value
})
}
setFromString ( filterNameOrQueryValue ) {
const {
key,
value = ''
} = this.getFilterNameAndValueFromString( filterNameOrQueryValue )
if ( value.trim().length === 0 ) throw new Error(`${ filterNameOrQueryValue } is not a valid filter value`)
this.set( key, value )
}
set ( filterName, filterValue ) {
if ( this.isQueryValue( filterName ) ) throw new Error(`${ filterName } is not a valid filter name`)
this.filters[ filterName ] = filterValue
}
toggleFilter ( filterNameOrQueryValue, filterValue = null ) {
const fromString = this.getFilterNameAndValueFromString( filterNameOrQueryValue )
const filterName = fromString.key
filterValue = filterValue || fromString.value
if ( this.has( filterNameOrQueryValue ) ) {
this.remove( filterName )
return
}
if ( typeof filterValue !== 'string' ) {
throw new Error(`Filter value must be a string. Got ${ typeof filterValue }`)
}
this.set( filterName, filterValue )
}
has ( filterNameOrQueryValue ) {
const {
key : filterName,
value : filterValue = null
} = this.getFilterNameAndValueFromString( filterNameOrQueryValue )
if ( isString( filterValue ) ) {
return !!this.filters[ filterName ] && this.filters[ filterName ] === filterValue
}
return !!this.filters[ filterName ]
}
}

View file

@ -1,6 +1,5 @@
import { filterSeparator } from '~/helpers/constants.js'
import { isString } from '~/helpers/check-types.js' import { isString } from '~/helpers/check-types.js'
import { SearchFilters } from '~/helpers/search/filters.js'
import { import {
storkIndexRelativeURL, storkIndexRelativeURL,
@ -276,121 +275,4 @@ export class StorkClient {
} }
} }
export class StorkFilters { export class StorkFilters extends SearchFilters {}
constructor({
initialFilters = {}
} = {}) {
this.initialFilters = initialFilters
this.filters = {
...initialFilters
}
}
get list () {
return Object.entries( this.filters ).map( ([ filterKey, filterValue ]) => {
return `${ filterKey }${ filterSeparator }${ filterValue }`
} )
}
get asQuery () {
return this.list.join(' ')
}
getByKey ( key ) {
return `${ key }${ filterSeparator }${ this.filters[ key ] }`
}
isQueryValue ( filterNameOrQueryValue ) {
return filterNameOrQueryValue.includes( filterSeparator )
}
getKeyAndValue ( filterQueryValue ) {
const key = filterQueryValue.substring(0, filterQueryValue.indexOf( filterSeparator ))
const value = filterQueryValue.substring(filterQueryValue.indexOf( filterSeparator )+1)
return { key, value }
}
getFilterNameAndValueFromString ( filterNameOrQueryValue ) {
if ( this.isQueryValue( filterNameOrQueryValue ) ) {
return this.getKeyAndValue( filterNameOrQueryValue )
}
return {
key: filterNameOrQueryValue,
value: null
}
}
remove ( filterName ) {
// Throw error if it's not a valid filter name
if ( this.isQueryValue( filterName ) ) throw new Error(`${ filterName } is not a valid filter name`)
delete this.filters[ filterName ]
}
setFromStringArray ( filterStringArray ) {
filterStringArray.forEach( filterString => {
const { key, value } = this.getFilterNameAndValueFromString( filterString )
this.filters[ key ] = value
})
}
setFromString ( filterNameOrQueryValue ) {
const {
key,
value = ''
} = this.getFilterNameAndValueFromString( filterNameOrQueryValue )
// Throw for empty values
if ( value.trim().length === 0 ) throw new Error(`${ filterNameOrQueryValue } is not a valid filter value`)
this.set( key, value )
}
set ( filterName, filterValue ) {
// Throw error if it's not a valid filter name
if ( this.isQueryValue( filterName ) ) throw new Error(`${ filterName } is not a valid filter name`)
this.filters[ filterName ] = filterValue
}
toggleFilter ( filterNameOrQueryValue, filterValue = null ) {
const fromString = this.getFilterNameAndValueFromString( filterNameOrQueryValue )
const filterName = fromString.key
filterValue = filterValue || fromString.value
// If the filter is already set, remove it
if ( this.has( filterNameOrQueryValue ) ) {
this.remove( filterName )
return
}
// Throw error if filter value is not a string
if ( typeof filterValue !== 'string' ) {
throw new Error(`Filter value must be a string. Got ${ typeof filterValue }`)
}
this.set( filterName, filterValue )
}
has ( filterNameOrQueryValue ) {
const {
key : filterName,
value : filterValue = null
} = this.getFilterNameAndValueFromString( filterNameOrQueryValue )
// If this filter is a name and value, check if it's set
if ( isString( filterValue ) ) {
return !!this.filters[ filterName ] && this.filters[ filterName ] === filterValue
}
return !!this.filters[ filterName ]
}
}

View file

@ -84,5 +84,19 @@ describe('StorkFilters', () => {
filters.setFromString('test_works_yes') filters.setFromString('test_works_yes')
expect(filters.asQuery).toBe('test_works_yes') expect(filters.asQuery).toBe('test_works_yes')
}) })
it('should map filters for Pagefind', () => {
const filters = new StorkFilters()
filters.setFromStringArray([
'status_native',
'category_system_tools'
])
expect(filters.asPagefindFilters).toEqual({
status: [ 'native' ],
category: [ 'system_tools' ]
})
})
}) })
}) })