Skip to content

Commit 43c7c5c

Browse files
Remove custom TypeScript ESLint rules and adapt code, fix HttpRequests issue where timeout errors sometimes weren't properly thrown (#1749)
* const comments ans unsafe calls removed * cast some any's * removed and fixed no-unsafe-member-access * removed no unsafe args, and cast some unsafe ones * adjusted all no-unsafe-member-access * removed floating promises * removed and adjusted unsafe returns and unsafe assignenments * fixed up unused vars * removed explicit any's * changed void to promise.all for unawaited promises * correctly casting error and put back the eslint exception so the client builds with ts * updated node-ts test * Merge with main, and reset * Remove all unnecessary ts eslint settings, adapt code * Convert Array types to simple [] type * Fix assertion being in the wrong place * Fix unhandled rejection * Revert api error changes * Apply fix for abort errors not beign caught from response.text() --------- Co-authored-by: F. Levi <[email protected]>
1 parent 1b6660d commit 43c7c5c

17 files changed

+612
-317
lines changed

eslint.config.js

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -29,30 +29,12 @@ export default tseslint.config([
2929
},
3030
},
3131
rules: {
32-
// TODO: Remove the ones between "~~", adapt code
33-
// ~~
34-
"@typescript-eslint/prefer-as-const": "off",
35-
"@typescript-eslint/ban-ts-comment": "off",
36-
"@typescript-eslint/no-unsafe-call": "off",
37-
"@typescript-eslint/no-unsafe-member-access": "off",
38-
"@typescript-eslint/no-unsafe-return": "off",
39-
"@typescript-eslint/no-unsafe-assignment": "off",
40-
"@typescript-eslint/no-unsafe-argument": "off",
41-
"@typescript-eslint/no-floating-promises": "off",
42-
// ~~
43-
"@typescript-eslint/array-type": ["warn", { default: "array-simple" }],
44-
// TODO: Should be careful with this rule, should leave it be and disable
45-
// it within files where necessary with explanations
46-
"@typescript-eslint/no-explicit-any": "off",
4732
"@typescript-eslint/no-unused-vars": [
4833
"error",
4934
// argsIgnorePattern: https://eslint.org/docs/latest/rules/no-unused-vars#argsignorepattern
5035
// varsIgnorePattern: https://eslint.org/docs/latest/rules/no-unused-vars#varsignorepattern
5136
{ args: "all", argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
5237
],
53-
// TODO: Not recommended to disable rule, should instead disable locally
54-
// with explanation
55-
"@typescript-eslint/ban-ts-ignore": "off",
5638
},
5739
},
5840
// Vitest

src/http-requests.ts

Lines changed: 35 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type {
44
RequestOptions,
55
MainRequestOptions,
66
URLSearchParamsRecord,
7+
MeiliSearchErrorResponse,
78
} from "./types.js";
89
import { PACKAGE_VERSION } from "./package-version.js";
910
import {
@@ -67,10 +68,9 @@ function getHeaders(config: Config, headersInit?: HeadersInit): Headers {
6768
return headers;
6869
}
6970

70-
// This could be a symbol, but Node.js 18 fetch doesn't support that yet
71-
// and it might just go EOL before it ever does.
72-
// https://github.com/nodejs/node/issues/49557
73-
const TIMEOUT_OBJECT = {};
71+
// TODO: Convert to Symbol("timeout id") when Node.js 18 is dropped
72+
/** Used to identify whether an error is a timeout error after fetch request. */
73+
const TIMEOUT_ID = {};
7474

7575
/**
7676
* Attach a timeout signal to a {@link RequestInit}, while preserving original
@@ -109,7 +109,7 @@ function getTimeoutFn(
109109
return;
110110
}
111111

112-
const to = setTimeout(() => ac.abort(TIMEOUT_OBJECT), ms);
112+
const to = setTimeout(() => ac.abort(TIMEOUT_ID), ms);
113113
const fn = () => {
114114
clearTimeout(to);
115115

@@ -130,7 +130,7 @@ function getTimeoutFn(
130130
requestInit.signal = ac.signal;
131131

132132
return () => {
133-
const to = setTimeout(() => ac.abort(TIMEOUT_OBJECT), ms);
133+
const to = setTimeout(() => ac.abort(TIMEOUT_ID), ms);
134134
return () => clearTimeout(to);
135135
};
136136
}
@@ -208,7 +208,7 @@ export class HttpRequests {
208208
appendRecordToURLSearchParams(url.searchParams, params);
209209
}
210210

211-
const requestInit: RequestInit = {
211+
const init: RequestInit = {
212212
method,
213213
body:
214214
contentType === undefined || typeof body !== "string"
@@ -221,51 +221,44 @@ export class HttpRequests {
221221

222222
const startTimeout =
223223
this.#requestTimeout !== undefined
224-
? getTimeoutFn(requestInit, this.#requestTimeout)
224+
? getTimeoutFn(init, this.#requestTimeout)
225225
: null;
226226

227-
const getResponseAndHandleErrorAndTimeout = <
228-
U extends ReturnType<NonNullable<Config["httpClient"]> | typeof fetch>,
229-
>(
230-
responsePromise: U,
231-
stopTimeout?: ReturnType<NonNullable<typeof startTimeout>>,
232-
) =>
233-
responsePromise
234-
.catch((error: unknown) => {
235-
throw new MeiliSearchRequestError(
236-
url.toString(),
237-
Object.is(error, TIMEOUT_OBJECT)
238-
? new Error(`request timed out after ${this.#requestTimeout}ms`, {
239-
cause: requestInit,
240-
})
241-
: error,
242-
);
243-
})
244-
.finally(() => stopTimeout?.()) as U;
245-
246227
const stopTimeout = startTimeout?.();
247228

248-
if (this.#customRequestFn !== undefined) {
249-
const response = await getResponseAndHandleErrorAndTimeout(
250-
this.#customRequestFn(url, requestInit),
251-
stopTimeout,
252-
);
229+
let response: Response;
230+
let responseBody: string;
231+
try {
232+
if (this.#customRequestFn !== undefined) {
233+
// When using a custom HTTP client, the response should already be handled and ready to be returned
234+
return (await this.#customRequestFn(url, init)) as T;
235+
}
253236

254-
// When using a custom HTTP client, the response should already be handled and ready to be returned
255-
return response as T;
237+
response = await fetch(url, init);
238+
responseBody = await response.text();
239+
} catch (error) {
240+
throw new MeiliSearchRequestError(
241+
url.toString(),
242+
Object.is(error, TIMEOUT_ID)
243+
? new Error(`request timed out after ${this.#requestTimeout}ms`, {
244+
cause: init,
245+
})
246+
: error,
247+
);
248+
} finally {
249+
stopTimeout?.();
256250
}
257251

258-
const response = await getResponseAndHandleErrorAndTimeout(
259-
fetch(url, requestInit),
260-
stopTimeout,
261-
);
262-
263-
const responseBody = await response.text();
264252
const parsedResponse =
265-
responseBody === "" ? undefined : JSON.parse(responseBody);
253+
responseBody === ""
254+
? undefined
255+
: (JSON.parse(responseBody) as T | MeiliSearchErrorResponse);
266256

267257
if (!response.ok) {
268-
throw new MeiliSearchApiError(response, parsedResponse);
258+
throw new MeiliSearchApiError(
259+
response,
260+
parsedResponse as MeiliSearchErrorResponse | undefined,
261+
);
269262
}
270263

271264
return parsedResponse as T;

src/indexes.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,13 @@ import type {
5252
EnqueuedTaskObject,
5353
ExtraRequestInit,
5454
PrefixSearch,
55+
RecordAny,
5556
} from "./types.js";
5657
import { HttpRequests } from "./http-requests.js";
5758
import { Task, TaskClient } from "./task.js";
5859
import { EnqueuedTask } from "./enqueued-task.js";
5960

60-
class Index<T extends Record<string, any> = Record<string, any>> {
61+
class Index<T extends RecordAny = RecordAny> {
6162
uid: string;
6263
primaryKey: string | undefined;
6364
createdAt: Date | undefined;
@@ -89,10 +90,7 @@ class Index<T extends Record<string, any> = Record<string, any>> {
8990
* @param config - Additional request configuration options
9091
* @returns Promise containing the search response
9192
*/
92-
async search<
93-
D extends Record<string, any> = T,
94-
S extends SearchParams = SearchParams,
95-
>(
93+
async search<D extends RecordAny = T, S extends SearchParams = SearchParams>(
9694
query?: string | null,
9795
options?: S,
9896
extraRequestInit?: ExtraRequestInit,
@@ -113,7 +111,7 @@ class Index<T extends Record<string, any> = Record<string, any>> {
113111
* @returns Promise containing the search response
114112
*/
115113
async searchGet<
116-
D extends Record<string, any> = T,
114+
D extends RecordAny = T,
117115
S extends SearchParams = SearchParams,
118116
>(
119117
query?: string | null,
@@ -175,7 +173,7 @@ class Index<T extends Record<string, any> = Record<string, any>> {
175173
* @returns Promise containing the search response
176174
*/
177175
async searchSimilarDocuments<
178-
D extends Record<string, any> = T,
176+
D extends RecordAny = T,
179177
S extends SearchParams = SearchParams,
180178
>(params: SearchSimilarDocumentsParams): Promise<SearchResponse<D, S>> {
181179
return await this.httpRequest.post<SearchResponse<D, S>>({
@@ -357,7 +355,7 @@ class Index<T extends Record<string, any> = Record<string, any>> {
357355
* the `filter` field only available in Meilisearch v1.2 and newer
358356
* @returns Promise containing the returned documents
359357
*/
360-
async getDocuments<D extends Record<string, any> = T>(
358+
async getDocuments<D extends RecordAny = T>(
361359
params?: DocumentsQuery<D>,
362360
): Promise<ResourceResults<D[]>> {
363361
const relativeBaseURL = `indexes/${this.uid}/documents`;
@@ -384,7 +382,7 @@ class Index<T extends Record<string, any> = Record<string, any>> {
384382
* @param parameters - Parameters applied on a document
385383
* @returns Promise containing Document response
386384
*/
387-
async getDocument<D extends Record<string, any> = T>(
385+
async getDocument<D extends RecordAny = T>(
388386
documentId: string | number,
389387
parameters?: DocumentQuery<T>,
390388
): Promise<D> {
@@ -476,7 +474,7 @@ class Index<T extends Record<string, any> = Record<string, any>> {
476474
* @returns Promise containing an EnqueuedTask
477475
*/
478476
async updateDocuments(
479-
documents: Array<Partial<T>>,
477+
documents: Partial<T>[],
480478
options?: DocumentOptions,
481479
): Promise<EnqueuedTask> {
482480
const task = await this.httpRequest.put<EnqueuedTaskObject>({
@@ -497,7 +495,7 @@ class Index<T extends Record<string, any> = Record<string, any>> {
497495
* @returns Promise containing array of enqueued task objects for each batch
498496
*/
499497
async updateDocumentsInBatches(
500-
documents: Array<Partial<T>>,
498+
documents: Partial<T>[],
501499
batchSize = 1000,
502500
options?: DocumentOptions,
503501
): Promise<EnqueuedTask[]> {

src/meilisearch.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,14 @@ import type {
2929
BatchesQuery,
3030
MultiSearchResponseOrSearchResponse,
3131
Network,
32+
RecordAny,
3233
} from "./types.js";
3334
import { ErrorStatusCode } from "./types.js";
3435
import { HttpRequests } from "./http-requests.js";
3536
import { TaskClient } from "./task.js";
3637
import { EnqueuedTask } from "./enqueued-task.js";
3738
import { type Batch, BatchClient } from "./batch.js";
39+
import type { MeiliSearchApiError } from "./errors/meilisearch-api-error.js";
3840

3941
export class MeiliSearch {
4042
config: Config;
@@ -60,9 +62,7 @@ export class MeiliSearch {
6062
* @param indexUid - The index UID
6163
* @returns Instance of Index
6264
*/
63-
index<T extends Record<string, any> = Record<string, any>>(
64-
indexUid: string,
65-
): Index<T> {
65+
index<T extends RecordAny = RecordAny>(indexUid: string): Index<T> {
6666
return new Index<T>(this.config, indexUid);
6767
}
6868

@@ -73,7 +73,7 @@ export class MeiliSearch {
7373
* @param indexUid - The index UID
7474
* @returns Promise returning Index instance
7575
*/
76-
async getIndex<T extends Record<string, any> = Record<string, any>>(
76+
async getIndex<T extends RecordAny = RecordAny>(
7777
indexUid: string,
7878
): Promise<Index<T>> {
7979
return new Index<T>(this.config, indexUid).fetchInfo();
@@ -170,10 +170,14 @@ export class MeiliSearch {
170170
try {
171171
await this.deleteIndex(uid);
172172
return true;
173-
} catch (e: any) {
174-
if (e.code === ErrorStatusCode.INDEX_NOT_FOUND) {
173+
} catch (e) {
174+
if (
175+
(e as MeiliSearchApiError)?.cause?.code ===
176+
ErrorStatusCode.INDEX_NOT_FOUND
177+
) {
175178
return false;
176179
}
180+
177181
throw e;
178182
}
179183
}
@@ -244,7 +248,7 @@ export class MeiliSearch {
244248
*/
245249
async multiSearch<
246250
T1 extends MultiSearchParams | FederatedMultiSearchParams,
247-
T2 extends Record<string, any> = Record<string, any>,
251+
T2 extends RecordAny = RecordAny,
248252
>(
249253
queries: T1,
250254
extraRequestInit?: ExtraRequestInit,

src/token.ts

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import type { webcrypto } from "node:crypto";
2-
import type { TenantTokenGeneratorOptions, TokenSearchRules } from "./types.js";
2+
import type {
3+
TenantTokenGeneratorOptions,
4+
TenantTokenHeader,
5+
TokenClaims,
6+
} from "./types.js";
37

48
function getOptionsWithDefaults(options: TenantTokenGeneratorOptions) {
59
const {
@@ -80,20 +84,10 @@ async function sign(
8084
function getHeader({
8185
algorithm: alg,
8286
}: TenantTokenGeneratorOptionsWithDefaults): string {
83-
const header = { alg, typ: "JWT" };
87+
const header: TenantTokenHeader = { alg, typ: "JWT" };
8488
return encodeToBase64(header).replace(/=/g, "");
8589
}
8690

87-
/**
88-
* @see {@link https://www.meilisearch.com/docs/learn/security/tenant_token_reference | Tenant token payload reference}
89-
* @see {@link https://github.com/meilisearch/meilisearch/blob/b21d7aedf9096539041362d438e973a18170f3fc/crates/meilisearch/src/extractors/authentication/mod.rs#L334-L340 | GitHub source code}
90-
*/
91-
type TokenClaims = {
92-
searchRules: TokenSearchRules;
93-
exp?: number;
94-
apiKeyUid: string;
95-
};
96-
9791
/** Create the payload of the token. */
9892
function getPayload({
9993
searchRules,
@@ -124,8 +118,8 @@ function getPayload({
124118
* is the recommended way according to
125119
* {@link https://min-common-api.proposal.wintercg.org/#navigator-useragent-requirements | WinterCG specs}.
126120
* {@link https://developer.mozilla.org/en-US/docs/Web/API/Navigator/userAgent | User agent }
127-
* can be spoofed, `process` can be patched. It should prevent misuse for the
128-
* overwhelming majority of cases.
121+
* can be spoofed, `process` can be patched. Even so it should prevent misuse
122+
* for the overwhelming majority of cases.
129123
*/
130124
function tryDetectEnvironment(): void {
131125
if (typeof navigator !== "undefined" && "userAgent" in navigator) {

0 commit comments

Comments
 (0)