Skip to content

Commit dd2d44f

Browse files
emyannbidoubiwa
andauthored
Feat: Make IndexInterface generic. createIndex and getIndex generic support (#442)
* fix: allow to type the search response when using index search query * chore: update documentation * fix: fallback on any type if no type provided * chore: let Typescript infer promise return type * feat: make IndexInterface generic - revert generic on search method in favor of generic on Interface - following methods are now generic: search, getDocuments, getDocument, addDocuments, updateDocuments * feat: update Index Class with generic passed to IndexInterface. * test: let TS infer promise return type * feat: MeiliSearchInterface getIndex and createIndex support generic * fix: incorrect document type. * feat: add more soundness to Document<T> * test: let TS inference * chore: update readme to show generic usage Co-authored-by: cvermand <[email protected]>
1 parent 11f6577 commit dd2d44f

File tree

6 files changed

+112
-91
lines changed

6 files changed

+112
-91
lines changed

README.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ A GitHub Action will be triggered and push the package on [npm](https://www.npmj
315315

316316
- Make a search request:
317317

318-
`client.getIndex('xxx').search<T = any>(query: string, options?: SearchParams): Promise<SearchResponse<T>>`
318+
`client.getIndex<T>('xxx').search(query: string, options?: SearchParams): Promise<SearchResponse<T>>`
319319

320320
### Indexes <!-- omit in toc -->
321321

@@ -325,15 +325,15 @@ A GitHub Action will be triggered and push the package on [npm](https://www.npmj
325325

326326
- Create new index:
327327

328-
`client.createIndex(uid: string, options?: IndexOptions): Promise<Index>`
328+
`client.createIndex<T>(uid: string, options?: IndexOptions): Promise<Index<T>>`
329329

330330
- Get index object:
331331

332-
`client.getIndex(uid: string): Indexes`
332+
`client.getIndex<T>(uid: string): Index<T>`
333333

334334
- Get or create index if it does not exist
335335

336-
`client.getOrCreateIndex(uid: string, options?: IndexOptions): Promise<Index>`
336+
`client.getOrCreateIndex<T>(uid: string, options?: IndexOptions): Promise<Index<T>>`
337337

338338
- Show Index information:
339339

@@ -369,19 +369,19 @@ A GitHub Action will be triggered and push the package on [npm](https://www.npmj
369369

370370
- Add or replace multiple documents:
371371

372-
`index.addDocuments(documents: object[]): Promise<EnqueuedUpdate>`
372+
`index.addDocuments(documents: Document<T>[]): Promise<EnqueuedUpdate>`
373373

374374
- Add or update multiple documents:
375375

376-
`index.updateDocuments(documents: object[]): Promise<EnqueuedUpdate>`
376+
`index.updateDocuments(documents: Document<T>[]): Promise<EnqueuedUpdate>`
377377

378378
- Get Documents:
379379

380-
`index.getDocuments(params: getDocumentsParams): Promise<object[]>`
380+
`index.getDocuments(params: getDocumentsParams): Promise<Document<T>[]>`
381381

382382
- Get one document:
383383

384-
`index.getDocument(documentId: string): Promise<object>`
384+
`index.getDocument(documentId: string): Promise<Document<T>>`
385385

386386
- Delete one document:
387387

src/index.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import MeiliAxiosWrapper from './meili-axios-wrapper'
1212
import * as Types from './types'
1313
import { sleep } from './utils'
1414

15-
class Index extends MeiliAxiosWrapper implements Types.IndexInterface {
15+
class Index<T> extends MeiliAxiosWrapper implements Types.IndexInterface<T> {
1616
uid: string
1717
constructor(config: Types.Config, uid: string) {
1818
super(config)
@@ -72,7 +72,7 @@ class Index extends MeiliAxiosWrapper implements Types.IndexInterface {
7272
* @memberof Index
7373
* @method search
7474
*/
75-
async search<T = any>(
75+
async search(
7676
query: string,
7777
options?: Types.SearchParams
7878
): Promise<Types.SearchResponse<T>> {
@@ -197,7 +197,7 @@ class Index extends MeiliAxiosWrapper implements Types.IndexInterface {
197197
*/
198198
async getDocuments(
199199
options?: Types.GetDocumentsParams
200-
): Promise<Types.Document[]> {
200+
): Promise<Types.Document<T>[]> {
201201
const url = `/indexes/${this.uid}/documents`
202202
let attr
203203
if (options !== undefined && Array.isArray(options.attributesToRetrieve)) {
@@ -217,7 +217,7 @@ class Index extends MeiliAxiosWrapper implements Types.IndexInterface {
217217
* @memberof Index
218218
* @method getDocument
219219
*/
220-
async getDocument(documentId: string | number): Promise<Types.Document> {
220+
async getDocument(documentId: string | number): Promise<Types.Document<T>> {
221221
const url = `/indexes/${this.uid}/documents/${documentId}`
222222

223223
return await this.get(url)
@@ -229,7 +229,7 @@ class Index extends MeiliAxiosWrapper implements Types.IndexInterface {
229229
* @method addDocuments
230230
*/
231231
async addDocuments(
232-
documents: Types.Document[],
232+
documents: Types.Document<T>[],
233233
options?: Types.AddDocumentParams
234234
): Promise<Types.EnqueuedUpdate> {
235235
const url = `/indexes/${this.uid}/documents`
@@ -245,7 +245,7 @@ class Index extends MeiliAxiosWrapper implements Types.IndexInterface {
245245
* @method updateDocuments
246246
*/
247247
async updateDocuments(
248-
documents: Types.Document[],
248+
documents: Types.Document<T>[],
249249
options?: Types.AddDocumentParams
250250
): Promise<Types.EnqueuedUpdate> {
251251
const url = `/indexes/${this.uid}/documents`

src/meilisearch.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,19 @@ class Meilisearch extends MeiliAxiosWrapper
2424
* @memberof Meilisearch
2525
* @method getIndex
2626
*/
27-
getIndex(indexUid: string): Index {
28-
return new Index(this.config, indexUid)
27+
getIndex<T = any>(indexUid: string): Index<T> {
28+
return new Index<T>(this.config, indexUid)
2929
}
3030

3131
/**
3232
* Get an index or create it if it does not exist
3333
* @memberof Meilisearch
3434
* @method getOrCreateIndex
3535
*/
36-
async getOrCreateIndex(
36+
async getOrCreateIndex<T = any>(
3737
uid: string,
3838
options: Types.IndexOptions = {}
39-
): Promise<Index> {
39+
): Promise<Index<T>> {
4040
try {
4141
const index = await this.createIndex(uid, options)
4242
return index
@@ -64,10 +64,10 @@ class Meilisearch extends MeiliAxiosWrapper
6464
* @memberof Meilisearch
6565
* @method createIndex
6666
*/
67-
async createIndex(
67+
async createIndex<T = any>(
6868
uid: string,
6969
options: Types.IndexOptions = {}
70-
): Promise<Index> {
70+
): Promise<Index<T>> {
7171
const url = '/indexes'
7272

7373
const index = await this.post(url, { ...options, uid })

src/types.ts

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export interface AddDocumentParams {
5151
primaryKey?: string
5252
}
5353

54-
export type FacetFilter = string | FacetFilter[];
54+
export type FacetFilter = string | FacetFilter[]
5555

5656
export interface SearchParams {
5757
offset?: number
@@ -105,9 +105,20 @@ export interface GetDocumentsParams {
105105
attributesToRetrieve?: string[]
106106
}
107107

108-
export interface Document<T = any> {
109-
[attribute: string]: T
110-
}
108+
export type DocumentLike = { [Key in string]?: DocumentField }
109+
export interface DocumentArray extends Array<DocumentField> {}
110+
export type DocumentField =
111+
| string
112+
| number
113+
| boolean
114+
| null
115+
| DocumentLike
116+
| DocumentArray
117+
118+
export type Document<T> = DocumentLike &
119+
{
120+
[key in keyof T]: T[key]
121+
}
111122

112123
/*
113124
** Settings
@@ -225,10 +236,13 @@ export interface SysInfoPretty {
225236

226237
export interface MeiliSearchInterface extends MeiliAxiosWrapper {
227238
config: Config
228-
getIndex: (indexUid: string) => Index
229-
getOrCreateIndex: (uid: string, options?: IndexOptions) => Promise<Index>
239+
getIndex: <T>(indexUid: string) => Index<T>
240+
getOrCreateIndex: <T>(
241+
uid: string,
242+
options?: IndexOptions
243+
) => Promise<Index<T>>
230244
listIndexes: () => Promise<IndexResponse[]>
231-
createIndex: (uid: string, options?: IndexOptions) => Promise<Index>
245+
createIndex: <T>(uid: string, options?: IndexOptions) => Promise<Index<T>>
232246
getKeys: () => Promise<Keys>
233247
isHealthy: () => Promise<boolean>
234248
setHealthy: () => Promise<void>
@@ -240,26 +254,23 @@ export interface MeiliSearchInterface extends MeiliAxiosWrapper {
240254
prettySysInfo: () => Promise<SysInfoPretty>
241255
}
242256

243-
export interface IndexInterface extends MeiliAxiosWrapperInterface {
257+
export interface IndexInterface<T = any> extends MeiliAxiosWrapperInterface {
244258
uid: string
245259
getUpdateStatus: (updateId: number) => Promise<Update>
246260
getAllUpdateStatus: () => Promise<Update[]>
247-
search: <T = any>(
248-
query: string,
249-
options?: SearchParams
250-
) => Promise<SearchResponse<T>>
261+
search: (query: string, options?: SearchParams) => Promise<SearchResponse<T>>
251262
show: () => Promise<IndexResponse>
252263
updateIndex: (indexData: IndexOptions) => Promise<IndexResponse>
253264
deleteIndex: () => Promise<string>
254265
getStats: () => Promise<IndexStats>
255-
getDocuments: (options?: GetDocumentsParams) => Promise<Document[]>
256-
getDocument: (documentId: string | number) => Promise<Document>
266+
getDocuments: (options?: GetDocumentsParams) => Promise<Document<T>[]>
267+
getDocument: (documentId: string | number) => Promise<Document<T>>
257268
addDocuments: (
258-
documents: Document[],
269+
documents: Document<T>[],
259270
options?: AddDocumentParams
260271
) => Promise<EnqueuedUpdate>
261272
updateDocuments: (
262-
documents: Document[],
273+
documents: Document<T>[],
263274
options?: AddDocumentParams
264275
) => Promise<EnqueuedUpdate>
265276
deleteDocument: (documentId: string | number) => Promise<EnqueuedUpdate>
@@ -310,16 +321,16 @@ export interface MeiliAxiosWrapperInterface {
310321
url: string,
311322
config?: AxiosRequestConfig
312323
) => Promise<R>
313-
post: ((
324+
post: (<T = any>(
314325
url: string,
315326
data: IndexRequest,
316327
config?: AxiosRequestConfig
317-
) => Promise<Index>) &
318-
(<T = any, R = AxiosResponse<EnqueuedUpdate>>(
319-
url: string,
320-
data?: T,
321-
config?: AxiosRequestConfig
322-
) => Promise<R>)
328+
) => Promise<Index<T>>) &
329+
(<T = any, R = AxiosResponse<EnqueuedUpdate>>(
330+
url: string,
331+
data?: T,
332+
config?: AxiosRequestConfig
333+
) => Promise<R>)
323334
put: <T = any, R = AxiosResponse<T>>(
324335
url: string,
325336
data?: any,

tests/client_tests.ts

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,9 @@ describe.each([
3737
await expect(client.listIndexes()).resolves.toHaveLength(0)
3838
})
3939
test(`${permission} key: create with no primary key`, async () => {
40-
await client
41-
.createIndex(uidNoPrimaryKey.uid)
42-
.then((response: Types.Index) => {
43-
expect(response).toHaveProperty('uid', uidNoPrimaryKey.uid)
44-
})
40+
await client.createIndex(uidNoPrimaryKey.uid).then((response) => {
41+
expect(response).toHaveProperty('uid', uidNoPrimaryKey.uid)
42+
})
4543

4644
await client
4745
.getIndex(uidNoPrimaryKey.uid)
@@ -55,8 +53,10 @@ describe.each([
5553
})
5654
test(`${permission} key: create with primary key`, async () => {
5755
await client
58-
.createIndex(uidAndPrimaryKey.uid, { primaryKey: uidAndPrimaryKey.primaryKey })
59-
.then((response: Types.Index) => {
56+
.createIndex(uidAndPrimaryKey.uid, {
57+
primaryKey: uidAndPrimaryKey.primaryKey,
58+
})
59+
.then((response) => {
6060
expect(response).toHaveProperty('uid', uidAndPrimaryKey.uid)
6161
})
6262
await client
@@ -138,9 +138,11 @@ describe.each([
138138
})
139139

140140
test(`${permission} key: create index with already existing uid should fail`, async () => {
141-
await expect(client.createIndex(uidAndPrimaryKey.uid, { primaryKey: uidAndPrimaryKey.primaryKey })).rejects.toThrowError(
142-
`index already exists`
143-
)
141+
await expect(
142+
client.createIndex(uidAndPrimaryKey.uid, {
143+
primaryKey: uidAndPrimaryKey.primaryKey,
144+
})
145+
).rejects.toThrowError(`index already exists`)
144146
})
145147

146148
test(`${permission} key: create index with missing uid should fail`, async () => {
@@ -224,14 +226,16 @@ describe.each([{ client: publicClient, permission: 'Public' }])(
224226
)
225227
})
226228
test(`${permission} key: try to create Index with primary key and be denied`, async () => {
227-
await expect(client.createIndex(uidAndPrimaryKey.uid, { primaryKey: uidAndPrimaryKey.primaryKey })).rejects.toThrowError(
228-
`Invalid API key: ${PUBLIC_KEY}`
229-
)
229+
await expect(
230+
client.createIndex(uidAndPrimaryKey.uid, {
231+
primaryKey: uidAndPrimaryKey.primaryKey,
232+
})
233+
).rejects.toThrowError(`Invalid API key: ${PUBLIC_KEY}`)
230234
})
231235
test(`${permission} key: try to create Index with NO primary key and be denied`, async () => {
232-
await expect(client.createIndex(uidNoPrimaryKey.uid)).rejects.toThrowError(
233-
`Invalid API key: ${PUBLIC_KEY}`
234-
)
236+
await expect(
237+
client.createIndex(uidNoPrimaryKey.uid)
238+
).rejects.toThrowError(`Invalid API key: ${PUBLIC_KEY}`)
235239
})
236240
test(`${permission} key: try to get index info and be denied`, async () => {
237241
await expect(
@@ -245,7 +249,9 @@ describe.each([{ client: publicClient, permission: 'Public' }])(
245249
})
246250
test(`${permission} key: try to update index and be denied`, async () => {
247251
await expect(
248-
client.getIndex(uidAndPrimaryKey.uid).updateIndex({ primaryKey: uidAndPrimaryKey.primaryKey })
252+
client
253+
.getIndex(uidAndPrimaryKey.uid)
254+
.updateIndex({ primaryKey: uidAndPrimaryKey.primaryKey })
249255
).rejects.toThrowError(`Invalid API key: ${PUBLIC_KEY}`)
250256
})
251257
})
@@ -284,14 +290,16 @@ describe.each([{ client: anonymousClient, permission: 'No' }])(
284290
)
285291
})
286292
test(`${permission} key: try to create an index with primary key and be denied`, async () => {
287-
await expect(client.createIndex(uidAndPrimaryKey.uid, { primaryKey: uidAndPrimaryKey.primaryKey })).rejects.toThrowError(
288-
`You must have an authorization token`
289-
)
293+
await expect(
294+
client.createIndex(uidAndPrimaryKey.uid, {
295+
primaryKey: uidAndPrimaryKey.primaryKey,
296+
})
297+
).rejects.toThrowError(`You must have an authorization token`)
290298
})
291299
test(`${permission} key: try to create an index with NO primary key and be denied`, async () => {
292-
await expect(client.createIndex(uidNoPrimaryKey.uid)).rejects.toThrowError(
293-
`You must have an authorization token`
294-
)
300+
await expect(
301+
client.createIndex(uidNoPrimaryKey.uid)
302+
).rejects.toThrowError(`You must have an authorization token`)
295303
})
296304
test(`${permission} key: try to get index info and be denied`, async () => {
297305
await expect(
@@ -305,7 +313,9 @@ describe.each([{ client: anonymousClient, permission: 'No' }])(
305313
})
306314
test(`${permission} key: try to update index and be denied`, async () => {
307315
await expect(
308-
client.getIndex(uidAndPrimaryKey.uid).updateIndex({ primaryKey: uidAndPrimaryKey.primaryKey })
316+
client
317+
.getIndex(uidAndPrimaryKey.uid)
318+
.updateIndex({ primaryKey: uidAndPrimaryKey.primaryKey })
309319
).rejects.toThrowError(`You must have an authorization token`)
310320
})
311321
})

0 commit comments

Comments
 (0)