Skip to content

Commit

Permalink
refactor: Make ts/eslint happy
Browse files Browse the repository at this point in the history
  • Loading branch information
paultranvan committed Oct 24, 2024
1 parent 2732e70 commit 6d4fc17
Show file tree
Hide file tree
Showing 10 changed files with 167 additions and 79 deletions.
12 changes: 6 additions & 6 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,13 @@ const config = [
'@typescript-eslint': tseslint.plugin
},
rules: {
'@typescript-eslint/no-unsafe-argument': 'warn',
'@typescript-eslint/no-unsafe-assignment': 'warn',
'@typescript-eslint/no-unsafe-call': 'warn',
'@typescript-eslint/no-unsafe-member-access': 'warn',
'@typescript-eslint/no-unsafe-return': 'warn',
'@typescript-eslint/no-unsafe-argument': 'error',
'@typescript-eslint/no-unsafe-assignment': 'error',
'@typescript-eslint/no-unsafe-call': 'error',
'@typescript-eslint/no-unsafe-member-access': 'error',
'@typescript-eslint/no-unsafe-return': 'error',
'@typescript-eslint/explicit-function-return-type': 'error',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-unused-vars': [
'error',
{ ignoreRestSiblings: true }
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"dependencies": {
"comlink": "^4.4.1",
"cozy-app-publish": "^0.34.0",
"cozy-client": "^49.0.0",
"cozy-client": "^49.8.0",
"cozy-device-helper": "^3.1.0",
"cozy-flags": "^4.0.0",
"cozy-logger": "^1.10.4",
Expand All @@ -37,6 +37,7 @@
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/react": "^14.2.1",
"@types/jest": "^29.5.13",
"@types/pouchdb-browser": "^6.1.5",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"@types/sharedworker": "^0.0.126",
Expand Down
4 changes: 2 additions & 2 deletions src/@types/cozy-client.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,14 +170,14 @@ declare module 'cozy-client' {
}

export default class CozyClient {
plugins: any
plugins: unknown
constructor(rawOptions?: ClientOptions)
getStackClient(): StackClient
getInstanceOptions(): InstanceOptions
instanceOptions: InstanceOptions
collection(doctype: string): Collection
isLogged: boolean
on: (event: string, callback: () => void) => void
on: (event: string, callback: (doctype: string) => void) => void
removeListener: (event: string, callback: () => void) => void
logout: () => Promise<void>
query: (
Expand Down
3 changes: 3 additions & 0 deletions src/@types/cozy-realtime.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
declare module 'cozy-realtime' {
export const RealtimePlugin = (): null => null
}
6 changes: 4 additions & 2 deletions src/dataproxy/worker/platformWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,15 @@ const openDB = (): Promise<IDBDatabase> => {
}

const storage = {
getItem: async (key: string): Promise<any> => {
getItem: async (key: string): Promise<unknown> => {
const db = await openDB()
return new Promise((resolve, reject) => {
const transaction = db.transaction('store', 'readonly')
const store = transaction.objectStore('store')
const request = store.get(key)

request.onsuccess = (): void => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
resolve(request.result ? request.result.value : null)
}

Expand All @@ -48,7 +49,7 @@ const storage = {
})
},

setItem: async (key: string, value: any): Promise<void> => {
setItem: async (key: string, value: unknown): Promise<void> => {
const db = await openDB()
return new Promise((resolve, reject) => {
const transaction = db.transaction('store', 'readwrite')
Expand Down Expand Up @@ -105,6 +106,7 @@ const isOnline = async (): Promise<boolean> => {
export const platformWorker = {
storage,
events,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
pouchAdapter: PouchDB,
isOnline
}
70 changes: 48 additions & 22 deletions src/search/SearchEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import {
LIMIT_DOCTYPE_SEARCH,
REPLICATION_DEBOUNCE,
ROOT_DIR_ID,
SHARED_DRIVES_DIR_ID
SHARED_DRIVES_DIR_ID,
SearchedDoctype
} from '@/search/consts'
import { getPouchLink } from '@/search/helpers/client'
import { getSearchEncoder } from '@/search/helpers/getSearchEncoder'
Expand All @@ -30,10 +31,10 @@ import {
isIOCozyApp,
isIOCozyContact,
isIOCozyFile,
SearchedDoctype,
SearchIndex,
SearchIndexes,
SearchResult
SearchResult,
isSearchedDoctype
} from '@/search/types'

const log = Minilog('🗂️ [Indexing]')
Expand All @@ -50,7 +51,7 @@ class SearchEngine {

constructor(client: CozyClient) {
this.client = client
this.searchIndexes = {}
this.searchIndexes = {} as SearchIndexes

this.indexOnChanges()
this.debouncedReplication = startReplicationWithDebounce(
Expand All @@ -64,7 +65,9 @@ class SearchEngine {
return
}
this.client.on('pouchlink:doctypesync:end', async (doctype: string) => {
await this.indexDocsForSearch(doctype)
if (isSearchedDoctype(doctype)) {
await this.indexDocsForSearch(doctype as keyof typeof SEARCH_SCHEMA)
}
})
this.client.on('login', () => {
// Ensure login is done before plugin register
Expand All @@ -79,15 +82,17 @@ class SearchEngine {
}

subscribeDoctype(client: CozyClient, doctype: string): void {
/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call */
const realtime = this.client.plugins.realtime
realtime.subscribe('created', doctype, this.handleUpdatedOrCreatedDoc)
realtime.subscribe('updated', doctype, this.handleUpdatedOrCreatedDoc)
realtime.subscribe('deleted', doctype, this.handleDeletedDoc)
/* eslint-enable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call */
}

handleUpdatedOrCreatedDoc(doc: CozyDoc): void {
const doctype: string | undefined = doc._type
if (!doctype) {
const doctype = doc._type
if (!isSearchedDoctype(doctype)) {
return
}
const searchIndex = this.searchIndexes?.[doctype]
Expand All @@ -102,8 +107,8 @@ class SearchEngine {
}

handleDeletedDoc(doc: CozyDoc): void {
const doctype: string | undefined = doc._type
if (!doctype) {
const doctype = doc._type
if (!isSearchedDoctype(doctype)) {
return
}
const searchIndex = this.searchIndexes?.[doctype]
Expand All @@ -112,7 +117,7 @@ class SearchEngine {
return
}
log.debug('[REALTIME] remove doc from index after update : ', doc)
this.searchIndexes[doctype].index.remove(doc._id)
this.searchIndexes[doctype].index.remove(doc._id!)

this.debouncedReplication()
}
Expand All @@ -126,6 +131,7 @@ class SearchEngine {
const flexsearchIndex = new FlexSearch.Document<CozyDoc, true>({
tokenize: 'forward',
encode: getSearchEncoder(),
// @ts-ignore
minlength: 2,
document: {
id: '_id',
Expand All @@ -152,7 +158,7 @@ class SearchEngine {

shouldIndexDoc(doc: CozyDoc): boolean {
if (isIOCozyFile(doc)) {
const notInTrash = !doc.trashed && !/^\/\.cozy_trash/.test(doc.path)
const notInTrash = !doc.trashed && !/^\/\.cozy_trash/.test(doc.path ?? '')
const notRootDir = doc._id !== ROOT_DIR_ID
// Shared drives folder to be hidden in search.
// The files inside it though must appear. Thus only the file with the folder ID is filtered out.
Expand All @@ -163,7 +169,9 @@ class SearchEngine {
return true
}

async indexDocsForSearch(doctype: string): Promise<SearchIndex | null> {
async indexDocsForSearch(
doctype: keyof typeof SEARCH_SCHEMA
): Promise<SearchIndex | null> {
const searchIndex = this.searchIndexes[doctype]
const pouchLink = getPouchLink(this.client)

Expand All @@ -173,7 +181,9 @@ class SearchEngine {

if (!searchIndex) {
// First creation of search index
const docs = await this.client.queryAll(Q(doctype).limitBy(null))
const docs = await this.client.queryAll<CozyDoc[]>(
Q(doctype).limitBy(null)
)
const index = this.buildSearchIndex(doctype, docs)
const info = await pouchLink.getDbInfo(doctype)

Expand All @@ -199,7 +209,7 @@ class SearchEngine {
if (change.deleted) {
searchIndex.index.remove(change.id)
} else {
const normalizedDoc = { ...change.doc, _type: doctype }
const normalizedDoc = { ...change.doc, _type: doctype } as CozyDoc
this.addDocToIndex(searchIndex.index, normalizedDoc)
}
}
Expand All @@ -222,10 +232,19 @@ class SearchEngine {
const appsIndex = this.buildSearchIndex('io.cozy.apps', apps)

log.debug('Finished initializing indexes')
const currentDate = new Date().toISOString()
this.searchIndexes = {
[FILES_DOCTYPE]: { index: filesIndex, lastSeq: 0 },
[CONTACTS_DOCTYPE]: { index: contactsIndex, lastSeq: 0 },
[APPS_DOCTYPE]: { index: appsIndex, lastSeq: 0 }
[FILES_DOCTYPE]: {
index: filesIndex,
lastSeq: 0,
lastUpdated: currentDate
},
[CONTACTS_DOCTYPE]: {
index: contactsIndex,
lastSeq: 0,
lastUpdated: currentDate
},
[APPS_DOCTYPE]: { index: appsIndex, lastSeq: 0, lastUpdated: currentDate }
}
return this.searchIndexes
}
Expand All @@ -250,21 +269,25 @@ class SearchEngine {

searchOnIndexes(query: string): FlexSearchResultWithDoctype[] {
let searchResults: FlexSearchResultWithDoctype[] = []
for (const doctype in this.searchIndexes) {
for (const key in this.searchIndexes) {
const doctype = key as SearchedDoctype // XXX - Should not be necessary
const index = this.searchIndexes[doctype]
if (!index) {
log.warn('[SEARCH] No search index available for ', doctype)
continue
}
// TODO: do not use flexsearch store and rely on pouch storage?
// It's better for memory, but might slow down search queries
const indexResults = index.index.search(query, {
// XXX - The limit is specified twice because of a flexsearch inconstency
// that does not enforce the limit if only given in second argument, and
// does not return the correct type is only given in third options
const indexResults = index.index.search(query, LIMIT_DOCTYPE_SEARCH, {
limit: LIMIT_DOCTYPE_SEARCH,
enrich: true
})
const newResults = indexResults.map(res => ({
...res,
doctype
doctype: doctype
}))
searchResults = searchResults.concat(newResults)
}
Expand All @@ -278,11 +301,14 @@ class SearchEngine {
item.result.map(r => ({ ...r, field: item.field, doctype: item.doctype }))
)

const resultMap = new Map<FlexSearch.Id[], any>()
type MapItem = Omit<(typeof combinedResults)[number], 'field'> & {
fields: string[]
}
const resultMap = new Map<FlexSearch.Id[], MapItem>()

combinedResults.forEach(({ id, field, ...rest }) => {
if (resultMap.has(id)) {
resultMap.get(id).fields.push(field)
resultMap.get(id)?.fields.push(field)
} else {
resultMap.set(id, { id, fields: [field], ...rest })
}
Expand Down
9 changes: 8 additions & 1 deletion src/search/consts.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
const SEARCHABLE_DOCTYPES = [
'io.cozy.files',
'io.cozy.contacts',
'io.cozy.apps'
] as const
export type SearchedDoctype = (typeof SEARCHABLE_DOCTYPES)[number]

// Attribute order matters to apply priority on matching results
export const SEARCH_SCHEMA = {
export const SEARCH_SCHEMA: Record<SearchedDoctype, string[]> = {
'io.cozy.files': ['name', 'path'],
'io.cozy.contacts': [
'displayName',
Expand Down
6 changes: 5 additions & 1 deletion src/search/helpers/replication.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ jest.mock('@/search/helpers/client', () => ({
getPouchLink: jest.fn()
}))

interface PouchLink {
startReplication: Function
}

describe('startReplicationWithDebounce', () => {
let client: CozyClient
let pouchLink: any
let pouchLink: PouchLink

beforeEach(() => {
client = new CozyClient()
Expand Down
24 changes: 14 additions & 10 deletions src/search/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ import FlexSearch from 'flexsearch'

import { IOCozyFile, IOCozyContact, IOCozyApp } from 'cozy-client/types/types'

import { APPS_DOCTYPE, CONTACTS_DOCTYPE, FILES_DOCTYPE } from '@/search/consts'
import {
APPS_DOCTYPE,
CONTACTS_DOCTYPE,
FILES_DOCTYPE,
SEARCH_SCHEMA,
SearchedDoctype
} from '@/search/consts'

export type CozyDoc = IOCozyFile | IOCozyContact | IOCozyApp

Expand All @@ -18,17 +24,15 @@ export const isIOCozyApp = (doc: CozyDoc): doc is IOCozyApp => {
return doc._type === APPS_DOCTYPE
}

const searchedDoctypes = [
APPS_DOCTYPE,
CONTACTS_DOCTYPE,
FILES_DOCTYPE
] as const
export type SearchedDoctype = (typeof searchedDoctypes)[number]
const searchedDoctypes = Object.keys(SEARCH_SCHEMA)

export const isSearchedDoctype = (
doctype: string
doctype: string | undefined
): doctype is SearchedDoctype => {
return true
if (!doctype) {
return false
}
return searchedDoctypes.includes(doctype)
}

export interface RawSearchResult
Expand All @@ -52,5 +56,5 @@ export interface SearchIndex {
}

export type SearchIndexes = {
[key: string]: SearchIndex
[key in SearchedDoctype]: SearchIndex
}
Loading

0 comments on commit 6d4fc17

Please sign in to comment.