Skip to content

refactor: support Fuse.js 6.x.x #15

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

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@vuex-orm/plugin-search",
"version": "0.25.0",
"description": "Vuex ORM plugin for adding fuzzy search feature through model entities.",
"main": "dist/vuex-orm.cjs.js",
"main": "dist/vuex-orm-search.cjs.js",
"browser": "dist/vuex-orm-search.esm-browser.js",
"module": "dist/vuex-orm-search.esm-bundler.js",
"unpkg": "dist/vuex-orm-search.global.js",
Expand Down
8 changes: 4 additions & 4 deletions src/VuexORMSearch.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Query } from '@vuex-orm/core'
import Components from './contracts/Components'
import Options from './contracts/Options'
import Collection from './contracts/Collection'
import { Components } from './contracts/Components'
import { Options } from './contracts/Options'
import { Collection } from './contracts/Collection'
import DefaultOptions from './config/DefaultOptions'
import QueryMixin from './mixins/Query'

Expand All @@ -25,7 +25,7 @@ export default class VuexORMSearch {
}

/**
* Plug in features.
* Plugin features.
*/
plugin(): void {
this.mixQuery()
Expand Down
43 changes: 31 additions & 12 deletions src/config/DefaultOptions.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,35 @@
import Options from '../contracts/Options'
import * as Options from '../contracts/Options'

export const DefaultOptions: Options = {
distance: 100,
location: 0,
maxPatternLength: 32,
minMatchCharLength: 1,
searchPrimaryKey: false,
shouldSort: false,
threshold: 0.3,
tokenize: false,
export const matchOptions: Options.MatchOptions = {
includeMatches: false,
findAllMatches: false,
minMatchCharLength: 1
}

export const basicOptions: Options.BasicOptions = {
isCaseSensitive: false,
includeScore: false,
keys: [],
verbose: false
shouldSort: false
}

export const fuzzyOptions: Options.FuzzyOptions = {
location: 0,
threshold: 0.5,
distance: 100
}

export const advancedOptions: Options.AdvancedOptions = {
useExtendedSearch: false,
ignoreLocation: false,
ignoreFieldNorm: false
}

export const defaultOptions: Options.Options = {
...matchOptions,
...basicOptions,
...fuzzyOptions,
...advancedOptions
}

export default DefaultOptions
export default defaultOptions
4 changes: 1 addition & 3 deletions src/contracts/Collection.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { Model } from '@vuex-orm/core'

export type Collection = Model[]

export default Collection
export type Collection<M extends Model = Model> = M[]
2 changes: 0 additions & 2 deletions src/contracts/Components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,3 @@ import { Query } from '@vuex-orm/core'
export interface Components {
Query: typeof Query
}

export default Components
37 changes: 27 additions & 10 deletions src/contracts/Options.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,31 @@
export interface Options {
distance?: number
location?: number
maxPatternLength?: number
minMatchCharLength?: number
searchPrimaryKey?: boolean
import Fuse from 'fuse.js'

export interface BasicOptions {
isCaseSensitive?: boolean
includeScore?: boolean
keys?: Fuse.FuseOptionKey[]
shouldSort?: boolean
}

export interface MatchOptions {
includeMatches?: boolean
findAllMatches?: boolean
minMatchCharLength?: number
}

export interface FuzzyOptions {
location?: number
threshold?: number
tokenize?: boolean
keys?: string[]
verbose?: boolean
distance?: number
}

export interface AdvancedOptions {
useExtendedSearch?: boolean
ignoreLocation?: boolean
ignoreFieldNorm?: boolean
}

export default Options
export type Options = BasicOptions &
MatchOptions &
FuzzyOptions &
AdvancedOptions
7 changes: 7 additions & 0 deletions src/contracts/Query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Fuse from 'fuse.js'

export type SearchExpression = string | Fuse.Expression

export type SearchPattern = string | string[] | Fuse.Expression

export type SearchResults<T> = Fuse.FuseResult<T>[]
4 changes: 2 additions & 2 deletions src/index.cjs.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import './types/vuex-orm'

import Components from './contracts/Components'
import Options from './contracts/Options'
import { Components } from './contracts/Components'
import { Options } from './contracts/Options'
import VuexORMSearch from './VuexORMSearch'

export default {
Expand Down
7 changes: 4 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import './types/vuex-orm'

import Components from './contracts/Components'
import Options from './contracts/Options'
import { Components } from './contracts/Components'
import { Options } from './contracts/Options'
import VuexORMSearch from './VuexORMSearch'

export { Options }
export * from './contracts/Query'
export * from './contracts/Options'

export default {
install(components: Components, installOptions: Options): void {
Expand Down
45 changes: 20 additions & 25 deletions src/mixins/Query.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import Fuse from 'fuse.js'
import { Query as BaseQuery } from '@vuex-orm/core'
import Options from '../contracts/Options'
import Collection from '../contracts/Collection'
import { Options } from '../contracts/Options'

export default function Query(query: typeof BaseQuery, options: Options): void {
/**
Expand All @@ -14,25 +13,26 @@ export default function Query(query: typeof BaseQuery, options: Options): void {
*/
query.prototype.searchOptions = options

/**
* The raw search results.
*/
query.prototype.searchResults = []

/**
* Add search configurations.
*/
query.prototype.search = function (
terms: string | string[],
options: Options = {}
): BaseQuery {
// If `terms` is single string, convert it to an array so we can use it
// consistently afterward.
this.searchTerms = Array.isArray(terms) ? terms : [terms]

// If a user didn't provide `keys` option, set all model fields as default.
if ((this.searchOptions.keys as string[]).length === 0) {
this.searchOptions.keys = Object.keys(
this.model.cachedFields[this.model.entity]
)
query.prototype.search = function (pattern, options = {}) {
// For backward-compat, transform Array<string> to string type.
this.searchTerms = Array.isArray(pattern)
? pattern.filter(Boolean).join(' ')
: pattern || null

// If a user didn't provide a `keys` option, set it to all model fields by default.
if (!this.searchOptions.keys || this.searchOptions.keys.length === 0) {
this.searchOptions.keys = Object.keys(this.model.getFields())
}

// Finally, merge default options with users options.
// Merge default options with query options.
this.searchOptions = { ...this.searchOptions, ...options }

return this
Expand All @@ -41,20 +41,15 @@ export default function Query(query: typeof BaseQuery, options: Options): void {
/**
* Filter the given record with fuzzy search by Fuse.js.
*/
query.prototype.filterSearch = function (collection: Collection): Collection {
if (
this.searchTerms === null ||
this.searchTerms.filter(Boolean).length === 0
) {
query.prototype.filterSearch = function (collection) {
if (this.searchTerms === null || this.searchTerms === '') {
return collection
}

const fuse = new Fuse(collection, this.searchOptions)

return this.searchTerms.reduce<Collection>((carry, term) => {
carry.push(...fuse.search(term).map((result) => result.item))
this.searchResults = fuse.search(this.searchTerms)

return carry
}, [])
return this.searchResults.map((result) => result.item)
}
}
22 changes: 16 additions & 6 deletions src/types/vuex-orm.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,36 @@
import Options from '../contracts/Options'
import Collection from '../contracts/Collection'
import { Options } from '../contracts/Options'
import { Collection } from '../contracts/Collection'
import {
SearchResults,
SearchExpression,
SearchPattern
} from '../contracts/Query'

declare module '@vuex-orm/core' {
interface Query {
interface Query<T> {
/**
* The search terms.
*/
searchTerms: string[] | null
searchTerms: SearchExpression | null

/**
* The search options.
*/
searchOptions: Options

/**
* The raw search results.
*/
searchResults: SearchResults<T>

/**
* Add search configurations.
*/
search(terms: string | string[], options?: Options): this
search(terms: SearchPattern, options?: Options): this

/**
* Filter the given record with fuzzy search by Fuse.js.
*/
filterSearch(records: Collection): Collection
filterSearch(records: Collection): Collection<T>
}
}
35 changes: 30 additions & 5 deletions test/feature/Search.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe('Feature – Search', () => {
expect(result[0].id).toBe(1)
})

it('can fuzzy search records by many terms', async () => {
it('can fuzzy search records by many terms (compat)', async () => {
createStore([User])

await User.insert({
Expand Down Expand Up @@ -63,7 +63,7 @@ describe('Feature – Search', () => {
]
})

const result = User.query().search('').orderBy('id').get()
const result = User.query().search('').get()

expect(result.length).toBe(3)
})
Expand Down Expand Up @@ -95,7 +95,7 @@ describe('Feature – Search', () => {
]
})

const result = User.query().search(['rin', 'mail']).orderBy('id').get()
const result = User.query().search('rin mail').get()

expect(result.length).toBe(1)
})
Expand All @@ -112,10 +112,35 @@ describe('Feature – Search', () => {
})

const result = User.query()
.search(['rin', 'mail'], { keys: ['name'] })
.orderBy('id')
.search('rin mail', { keys: ['name'] })
.get()

expect(result.length).toBe(1)
})

it('exposes raw search results on a query instance', async () => {
createStore([User])

await User.insert({
data: [
{ id: 1, name: 'John Walker', email: 'john@example.com' },
{ id: 2, name: 'Bobby Banana', email: 'mail.mail@example.com' },
{ id: 3, name: 'Ringo Looper', email: 'ringo.looper@example.com' }
]
})

const query = User.query().search('walker', {
includeScore: true,
includeMatches: true
})
const result = query.get()

expect(result).toHaveLength(1)
expect(Object.keys(query.searchResults[0]).sort()).toEqual([
'item',
'matches',
'refIndex',
'score'
])
})
})
2 changes: 1 addition & 1 deletion test/support/Helpers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Vue from 'vue'
import Vuex, { Store } from 'vuex'
import VuexORM, { Database, Model } from '@vuex-orm/core'
import Options from '@/contracts/Options'
import { Options } from '@/contracts/Options'
import VuexORMSearch from '@/index'

export function createStore(
Expand Down