From 3b5795d48c177f57548f220ba529998570befc61 Mon Sep 17 00:00:00 2001 From: toptobes Date: Sun, 10 Nov 2024 13:48:49 +0530 Subject: [PATCH 01/10] a --- src/lib/timeouts.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/lib/timeouts.ts diff --git a/src/lib/timeouts.ts b/src/lib/timeouts.ts new file mode 100644 index 00000000..5781e369 --- /dev/null +++ b/src/lib/timeouts.ts @@ -0,0 +1,22 @@ +// Copyright DataStax, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +export interface TimeoutDescriptor { + requestTimeout: number, + generalMethodTimeout: number, + collectionAdminTimeout: number, + tableAdminTimeout: number, + databaseAdminTimeout: number, + keyspaceAdminTimeout: number, +} From 0e7cc4dc400654df9f997255ec1ee40dddfd6524 Mon Sep 17 00:00:00 2001 From: toptobes Date: Sun, 17 Nov 2024 11:43:35 +0530 Subject: [PATCH 02/10] keyspace impl & tests --- src/administration/astra-admin.ts | 17 +- src/administration/astra-db-admin.ts | 38 ++- src/administration/data-api-db-admin.ts | 22 +- src/administration/errors.ts | 21 +- src/administration/events.ts | 5 +- .../types/admin/admin-common.ts | 2 +- .../types/admin/list-databases.ts | 2 +- src/client/data-api-client.ts | 4 +- src/client/parsers/http-opts.ts | 12 +- src/client/types/client-opts.ts | 3 +- src/client/types/http-opts.ts | 27 -- src/client/types/internal.ts | 10 +- src/client/types/spawn-admin.ts | 5 +- src/client/types/spawn-db.ts | 5 +- src/db/db.ts | 43 ++- src/db/types/collections/create-collection.ts | 2 +- src/db/types/collections/drop-collection.ts | 2 +- src/db/types/collections/list-collections.ts | 2 +- src/db/types/collections/spawn-collection.ts | 13 +- src/db/types/common.ts | 7 - src/db/types/tables/drop-table.ts | 2 +- src/db/types/tables/list-tables.ts | 2 +- src/db/types/tables/spawn-table.ts | 13 +- src/documents/collections/collection.ts | 3 +- .../collections/types/find/find-one-delete.ts | 2 +- .../types/find/find-one-replace.ts | 2 +- .../collections/types/find/find-one-update.ts | 2 +- src/documents/commands/command-impls.ts | 30 +- src/documents/commands/helpers/insertion.ts | 2 +- .../commands/types/delete/delete-one.ts | 2 +- src/documents/commands/types/find/find.ts | 3 +- .../commands/types/insert/insert-many.ts | 2 +- .../commands/types/update/replace-one.ts | 2 +- .../commands/types/update/update-many.ts | 2 +- .../commands/types/update/update-one.ts | 2 +- src/documents/cursor.ts | 9 +- src/documents/errors.ts | 19 +- src/documents/events.ts | 5 +- src/documents/tables/table.ts | 17 +- src/documents/types/common.ts | 2 +- src/lib/api/clients/data-api-http-client.ts | 31 +-- src/lib/api/clients/devops-api-http-client.ts | 29 +- src/lib/api/clients/http-client.ts | 11 +- src/lib/api/clients/types.ts | 3 +- src/lib/api/constants.ts | 5 - src/lib/api/fetch/types.ts | 1 - src/lib/api/index.ts | 5 + src/lib/api/timeout-managers.ts | 72 ----- src/lib/api/timeouts.ts | 170 ++++++++++++ src/lib/index.ts | 2 +- src/lib/timeouts.ts | 22 -- src/lib/types.ts | 30 +- .../administration/lifecycle.test.ts | 29 +- .../client/data-api-client.test.ts | 19 +- tests/integration/db/db.test.ts | 2 +- .../documents/collections/insert-many.test.ts | 5 +- .../documents/collections/misc.test.ts | 8 +- .../documents/vectorize/vec-test-groups.ts | 2 +- .../api/clients/data-api-http-client.test.ts | 14 +- tests/integration/misc/quickstart.test.ts | 4 +- tests/integration/misc/timeouts.test.ts | 192 ++++++------- tests/prelude.test.ts | 3 +- tests/testlib/fixtures.ts | 2 +- tests/testlib/test-fns/describe.ts | 1 - tests/typing/sort.ts | 19 +- tests/unit/administration/admin.test.ts | 5 +- tests/unit/client/data-api-client.test.ts | 6 +- tests/unit/db/db.test.ts | 5 +- tests/unit/lib/api/timeout-manager.test.ts | 31 --- tests/unit/lib/api/timeouts.test.ts | 260 ++++++++++++++++++ tests/unit/lib/logging/logger.test.ts | 5 +- 71 files changed, 862 insertions(+), 496 deletions(-) delete mode 100644 src/lib/api/timeout-managers.ts create mode 100644 src/lib/api/timeouts.ts delete mode 100644 src/lib/timeouts.ts delete mode 100644 tests/unit/lib/api/timeout-manager.test.ts create mode 100644 tests/unit/lib/api/timeouts.test.ts diff --git a/src/administration/astra-admin.ts b/src/administration/astra-admin.ts index 446ce70e..7a8c8202 100644 --- a/src/administration/astra-admin.ts +++ b/src/administration/astra-admin.ts @@ -34,6 +34,7 @@ import { Logger } from '@/src/lib/logging/logger'; import { DbSpawnOptions } from '@/src/client'; import { $CustomInspect } from '@/src/lib/constants'; import { SomeDoc } from '@/src/documents'; +import { Timeouts } from '@/src/lib/api/timeouts'; /** * An administrative class for managing Astra databases, including creating, listing, and deleting databases. @@ -84,6 +85,7 @@ export class AstraAdmin { logging: Logger.advanceConfig(rootOpts.adminOptions.logging, adminOpts?.logging), additionalHeaders: { ...rootOpts.adminOptions.additionalHeaders, ...adminOpts?.additionalHeaders }, astraEnv: adminOpts?.astraEnv ?? rootOpts.adminOptions.astraEnv, + timeoutDefaults: Timeouts.merge(rootOpts.adminOptions.timeoutDefaults, adminOpts?.timeoutDefaults), }, dbOptions: { ...rootOpts.dbOptions, @@ -101,6 +103,7 @@ export class AstraAdmin { userAgent: rootOpts.userAgent, tokenProvider: this.#defaultOpts.adminOptions.adminToken, additionalHeaders: this.#defaultOpts.adminOptions.additionalHeaders, + timeoutDefaults: Timeouts.merge(rootOpts.adminOptions.timeoutDefaults, this.#defaultOpts.adminOptions.timeoutDefaults), }); Object.defineProperty(this, $CustomInspect, { @@ -284,10 +287,12 @@ export class AstraAdmin { * @returns A promise that resolves to the complete database information. */ public async dbInfo(id: string, options?: WithTimeout): Promise { + const tm = this.#httpClient.tm.single('databaseAdminTimeout', options); + const resp = await this.#httpClient.request({ method: HttpMethods.Get, path: `/databases/${id}`, - }, options); + }, tm); return buildAstraDatabaseAdminInfo(resp.data!, this.#environment); } @@ -337,11 +342,13 @@ export class AstraAdmin { params['starting_after'] = String(options.skip); } + const tm = this.#httpClient.tm.single('databaseAdminTimeout', options); + const resp = await this.#httpClient.request({ method: HttpMethods.Get, path: `/databases`, params: params, - }, options); + }, tm); return resp.data!.map((d: SomeDoc) => buildAstraDatabaseAdminInfo(d, this.#environment)); } @@ -407,6 +414,8 @@ export class AstraAdmin { ...config, }; + const tm = this.#httpClient.tm.multipart('databaseAdminTimeout', options); + const resp = await this.#httpClient.requestLongRunning({ method: HttpMethods.Post, path: '/databases', @@ -416,6 +425,7 @@ export class AstraAdmin { target: 'ACTIVE', legalStates: ['INITIALIZING', 'PENDING'], defaultPollInterval: 10000, + timeoutManager: tm, options, }); @@ -452,6 +462,8 @@ export class AstraAdmin { public async dropDatabase(db: Db | string, options?: AstraAdminBlockingOptions): Promise { const id = typeof db === 'string' ? db : db.id; + const tm = this.#httpClient.tm.multipart('databaseAdminTimeout', options); + await this.#httpClient.requestLongRunning({ method: HttpMethods.Post, path: `/databases/${id}/terminate`, @@ -460,6 +472,7 @@ export class AstraAdmin { target: 'TERMINATED', legalStates: ['TERMINATING'], defaultPollInterval: 10000, + timeoutManager: tm, options, }); } diff --git a/src/administration/astra-db-admin.ts b/src/administration/astra-db-admin.ts index a864e486..e52e5d2c 100644 --- a/src/administration/astra-db-admin.ts +++ b/src/administration/astra-db-admin.ts @@ -19,7 +19,7 @@ import { AstraCreateKeyspaceOptions, } from '@/src/administration/types'; import { DbAdmin } from '@/src/administration/db-admin'; -import { WithTimeout } from '@/src/lib/types'; +import type { nullish, WithTimeout } from '@/src/lib'; import { buildAstraDatabaseAdminInfo, extractAstraEnvironment } from '@/src/administration/utils'; import { FindEmbeddingProvidersResult } from '@/src/administration/types/db-admin/find-embedding-providers'; import { DEFAULT_DEVOPS_API_ENDPOINTS, HttpMethods } from '@/src/lib/api/constants'; @@ -32,6 +32,7 @@ import { InternalRootClientOpts } from '@/src/client/types/internal'; import { $CustomInspect } from '@/src/lib/constants'; import { AstraDbAdminInfo } from '@/src/administration/types/admin/database-info'; import { Logger } from '@/src/lib/logging/logger'; +import { TimeoutManager, Timeouts } from '@/src/lib/api/timeouts'; /** * An administrative class for managing Astra databases, including creating, listing, and deleting keyspaces. @@ -97,6 +98,7 @@ export class AstraDbAdmin extends DbAdmin { userAgent: rootOpts.userAgent, tokenProvider: adminToken, additionalHeaders: { ...rootOpts.adminOptions.additionalHeaders, ...adminOpts?.additionalHeaders }, + timeoutDefaults: Timeouts.merge(rootOpts.adminOptions.timeoutDefaults, adminOpts?.timeoutDefaults), }); this.#db = db; @@ -153,7 +155,10 @@ export class AstraDbAdmin extends DbAdmin { * @returns The available embedding providers. */ public override async findEmbeddingProviders(options?: WithTimeout): Promise { - const resp = await this.#db.command({ findEmbeddingProviders: {} }, { keyspace: null, maxTimeMS: options?.maxTimeMS }); + const resp = await this.#db._httpClient.executeCommand({ findEmbeddingProviders: {} }, { + timeoutManager: this.#httpClient.tm.single('databaseAdminTimeout', options), + keyspace: null, + }); return resp.status as FindEmbeddingProvidersResult; } @@ -173,12 +178,8 @@ export class AstraDbAdmin extends DbAdmin { * @returns A promise that resolves to the complete database information. */ public async info(options?: WithTimeout): Promise { - const resp = await this.#httpClient.request({ - method: HttpMethods.Get, - path: `/databases/${this.#db.id}`, - }, options); - - return buildAstraDatabaseAdminInfo(resp.data!, this.#environment); + const tm = this.#httpClient.tm.single('databaseAdminTimeout', options); + return this.#info(options, tm); } /** @@ -198,7 +199,8 @@ export class AstraDbAdmin extends DbAdmin { * @returns A promise that resolves to list of all the keyspaces in the database. */ public override async listKeyspaces(options?: WithTimeout): Promise { - return this.info(options).then(i => i.keyspaces); + const tm = this.#httpClient.tm.single('keyspaceAdminTimeout', options); + return this.#info(options, tm).then(i => i.keyspaces); } /** @@ -236,6 +238,8 @@ export class AstraDbAdmin extends DbAdmin { this.#db.useKeyspace(keyspace); } + const tm = this.#httpClient.tm.multipart('keyspaceAdminTimeout', options); + await this.#httpClient.requestLongRunning({ method: HttpMethods.Post, path: `/databases/${this.#db.id}/keyspaces/${keyspace}`, @@ -244,6 +248,7 @@ export class AstraDbAdmin extends DbAdmin { target: 'ACTIVE', legalStates: ['MAINTENANCE'], defaultPollInterval: 1000, + timeoutManager: tm, options, }); } @@ -280,6 +285,8 @@ export class AstraDbAdmin extends DbAdmin { * @returns A promise that resolves when the operation completes. */ public override async dropKeyspace(keyspace: string, options?: AstraAdminBlockingOptions): Promise { + const tm = this.#httpClient.tm.multipart('keyspaceAdminTimeout', options); + await this.#httpClient.requestLongRunning({ method: HttpMethods.Delete, path: `/databases/${this.#db.id}/keyspaces/${keyspace}`, @@ -288,6 +295,7 @@ export class AstraDbAdmin extends DbAdmin { target: 'ACTIVE', legalStates: ['MAINTENANCE'], defaultPollInterval: 1000, + timeoutManager: tm, options, }); } @@ -314,6 +322,8 @@ export class AstraDbAdmin extends DbAdmin { * @remarks Use with caution. Use a surge protector. Don't say I didn't warn you. */ public async drop(options?: AstraAdminBlockingOptions): Promise { + const tm = this.#httpClient.tm.multipart('databaseAdminTimeout', options); + await this.#httpClient.requestLongRunning({ method: HttpMethods.Post, path: `/databases/${this.#db.id}/terminate`, @@ -322,6 +332,7 @@ export class AstraDbAdmin extends DbAdmin { target: 'TERMINATED', legalStates: ['TERMINATING'], defaultPollInterval: 10000, + timeoutManager: tm, options, }); } @@ -329,4 +340,13 @@ export class AstraDbAdmin extends DbAdmin { public get _httpClient() { return this.#httpClient; } + + async #info(options: WithTimeout | nullish, tm: TimeoutManager): Promise { + const resp = await this.#httpClient.request({ + method: HttpMethods.Get, + path: `/databases/${this.#db.id}`, + }, tm); + + return buildAstraDatabaseAdminInfo(resp.data!, this.#environment); + } } diff --git a/src/administration/data-api-db-admin.ts b/src/administration/data-api-db-admin.ts index 99e37a1b..a5825b1c 100644 --- a/src/administration/data-api-db-admin.ts +++ b/src/administration/data-api-db-admin.ts @@ -15,7 +15,7 @@ import { AdminSpawnOptions, LocalCreateKeyspaceOptions } from '@/src/administration/types'; import { DbAdmin } from '@/src/administration/db-admin'; -import { WithTimeout } from '@/src/lib/types'; +import type { WithTimeout } from '@/src/lib'; import { FindEmbeddingProvidersResult } from '@/src/administration/types/db-admin/find-embedding-providers'; import { DataAPIHttpClient } from '@/src/lib/api/clients/data-api-http-client'; import { Db } from '@/src/db'; @@ -113,7 +113,10 @@ export class DataAPIDbAdmin extends DbAdmin { * @returns The available embedding providers. */ public override async findEmbeddingProviders(options?: WithTimeout): Promise { - const resp = await this.#httpClient.executeCommand({ findEmbeddingProviders: {} }, { keyspace: null, maxTimeMS: options?.maxTimeMS }); + const resp = await this.#httpClient.executeCommand({ findEmbeddingProviders: {} }, { + timeoutManager: this.#httpClient.tm.single('databaseAdminTimeout', options), + keyspace: null, + }); return resp.status as FindEmbeddingProvidersResult; } @@ -134,7 +137,10 @@ export class DataAPIDbAdmin extends DbAdmin { * @returns A promise that resolves to list of all the keyspaces in the database. */ public override async listKeyspaces(options?: WithTimeout): Promise { - const resp = await this.#httpClient.executeCommand({ findKeyspaces: {} }, { maxTimeMS: options?.maxTimeMS, keyspace: null }); + const resp = await this.#httpClient.executeCommand({ findKeyspaces: {} }, { + timeoutManager: this.#httpClient.tm.single('keyspaceAdminTimeout', options), + keyspace: null, + }); return resp.status!.keyspaces; } @@ -178,7 +184,10 @@ export class DataAPIDbAdmin extends DbAdmin { replicationFactor: 1, }; - await this.#httpClient.executeCommand({ createKeyspace: { name: keyspace, options: { replication } } }, { maxTimeMS: options?.maxTimeMS, keyspace: null }); + await this.#httpClient.executeCommand({ createKeyspace: { name: keyspace, options: { replication } } }, { + timeoutManager: this.#httpClient.tm.single('keyspaceAdminTimeout', options), + keyspace: null, + }); } /** @@ -203,7 +212,10 @@ export class DataAPIDbAdmin extends DbAdmin { * @returns A promise that resolves when the operation completes. */ public override async dropKeyspace(keyspace: string, options?: WithTimeout): Promise { - await this.#httpClient.executeCommand({ dropKeyspace: { name: keyspace } }, { maxTimeMS: options?.maxTimeMS, keyspace: null }); + await this.#httpClient.executeCommand({ dropKeyspace: { name: keyspace } }, { + timeoutManager: this.#httpClient.tm.single('keyspaceAdminTimeout', options), + keyspace: null, + }); } public get _httpClient() { diff --git a/src/administration/errors.ts b/src/administration/errors.ts index 2e31a254..fc78f8f5 100644 --- a/src/administration/errors.ts +++ b/src/administration/errors.ts @@ -12,8 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { FetcherResponseInfo } from '@/src/lib/api'; +import { FetcherResponseInfo, type TimeoutDescriptor } from '@/src/lib/api'; import { SomeDoc } from '@/src/documents'; +import { HTTPRequestInfo } from '@/src/lib/api/clients'; +import { TimedOutTypes, Timeouts } from '@/src/lib/api/timeouts'; /** * A representation of what went wrong when interacting with the DevOps API. @@ -64,19 +66,26 @@ export class DevOpsAPITimeoutError extends DevOpsAPIError { /** The timeout that was set for the operation, in milliseconds. */ - public readonly timeout: number; + public readonly timeout: Partial; + + public readonly timedOutTypes: TimedOutTypes; /** * Shouldn't be instantiated directly. * * @internal */ - constructor(url: string, timeout: number) { - super(`Command timed out after ${timeout}ms`); - this.url = url; - this.timeout = timeout; + constructor(info: HTTPRequestInfo, types: TimedOutTypes) { + super(Timeouts.fmtTimeoutMsg(info.timeoutManager, types)); + this.url = info.url; + this.timeout = info.timeoutManager.initial(); + this.timedOutTypes = types; this.name = 'DevOpsAPITimeoutError'; } + + public static mk(info: HTTPRequestInfo, types: TimedOutTypes): DevOpsAPITimeoutError { + return new DevOpsAPITimeoutError(info, types); + } } /** diff --git a/src/administration/events.ts b/src/administration/events.ts index 94f4b2f6..db8c2169 100644 --- a/src/administration/events.ts +++ b/src/administration/events.ts @@ -16,6 +16,7 @@ import type { DevOpsAPIRequestInfo } from '@/src/lib/api/clients/devops-api-http import type { DataAPIErrorDescriptor } from '@/src/documents'; // import { DataAPIClientEvent } from '@/src/lib/logging/events'; needs to be like this or it errors import { DataAPIClientEvent } from '@/src/lib/logging/events'; +import { TimeoutDescriptor } from '@/src/lib/api/timeouts'; /** * The events emitted by the {@link DataAPIClient}. These events are emitted at various stages of the @@ -100,14 +101,14 @@ export class AdminCommandStartedEvent extends AdminCommandEvent { /** * The timeout for the request, in milliseconds. */ - public readonly timeout: number; + public readonly timeout: Partial; /** * Should not be instantiated by the user. * * @internal */ - constructor(info: DevOpsAPIRequestInfo, longRunning: boolean, timeout: number) { + constructor(info: DevOpsAPIRequestInfo, longRunning: boolean, timeout: Partial) { super(info, longRunning); this.timeout = timeout; } diff --git a/src/administration/types/admin/admin-common.ts b/src/administration/types/admin/admin-common.ts index c3b5d0fb..4bb9529a 100644 --- a/src/administration/types/admin/admin-common.ts +++ b/src/administration/types/admin/admin-common.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { WithTimeout } from '@/src/lib/types'; +import type { WithTimeout } from '@/src/lib'; /** * Represents the available cloud providers that Astra offers. diff --git a/src/administration/types/admin/list-databases.ts b/src/administration/types/admin/list-databases.ts index e653a3d9..94fec309 100644 --- a/src/administration/types/admin/list-databases.ts +++ b/src/administration/types/admin/list-databases.ts @@ -13,7 +13,7 @@ // limitations under the License. import { AstraDbCloudProvider, AstraDbStatus } from '@/src/administration/types'; -import { WithTimeout } from '@/src/lib/types'; +import type { WithTimeout } from '@/src/lib'; /** * Represents all possible statuses of a database that you can filter by. diff --git a/src/client/data-api-client.ts b/src/client/data-api-client.ts index 0c12d4c6..c0f811a2 100644 --- a/src/client/data-api-client.ts +++ b/src/client/data-api-client.ts @@ -37,6 +37,7 @@ import { parseHttpOpts } from '@/src/client/parsers/http-opts'; import { parseAdminSpawnOpts } from '@/src/client/parsers/spawn-admin'; import { parseDbSpawnOpts } from '@/src/client/parsers/spawn-db'; import { $CustomInspect } from '@/src/lib/constants'; +import { Timeouts } from '@/src/lib/api/timeouts'; /** * The base class for the {@link DataAPIClient} event emitter to make it properly typed. @@ -162,11 +163,13 @@ export class DataAPIClient extends DataAPIClientEventEmitterBase { dbOptions: { ...options?.dbOptions, token: TokenProvider.parseToken([options?.dbOptions?.token, token], 'provided token'), + timeoutDefaults: Timeouts.merge(Timeouts.Default, options?.timeoutDefaults), logging, }, adminOptions: { ...options?.adminOptions, adminToken: TokenProvider.parseToken([options?.adminOptions?.adminToken, token], 'provided token'), + timeoutDefaults: Timeouts.merge(Timeouts.Default, options?.timeoutDefaults), logging, }, emitter: this, @@ -321,7 +324,6 @@ function buildFetchCtx(options: DataAPIClientOptions | undefined): FetchCtx { return { ctx: ctx, closed: { ref: false }, - maxTimeMS: options?.httpOptions?.maxTimeMS, }; } diff --git a/src/client/parsers/http-opts.ts b/src/client/parsers/http-opts.ts index b742312c..c218b9e7 100644 --- a/src/client/parsers/http-opts.ts +++ b/src/client/parsers/http-opts.ts @@ -44,7 +44,6 @@ export const parseHttpOpts: Parser = (raw, field const parseDefaultHttpOpts: Parser = (opts, field) => { const preferHttp2 = p.parse('boolean?')(opts.preferHttp2, `${field}.preferHttp2`) ?? true; - const maxTimeMS = p.parse('number?')(opts.maxTimeMS, `${field}.maxTimeMS`); const http1 = p.parse('object?', (http1, field) => { return { @@ -62,17 +61,14 @@ const parseDefaultHttpOpts: Parser = (opts, field) => }; })(opts.fetchH2, `${field}.fetchH2`); - return { client: 'default', preferHttp2, maxTimeMS, http1, fetchH2 }; + return { client: 'default', preferHttp2, http1, fetchH2 }; }; -const parseFetchHttpOpts: Parser = (opts, field) => { - const maxTimeMS = p.parse('number?')(opts.maxTimeMS, `${field}.maxTimeMS`); - return { client: 'fetch', maxTimeMS }; +const parseFetchHttpOpts: Parser = () => { + return { client: 'fetch' }; }; const parseCustomHttpOpts: Parser = (opts, field) => { - const maxTimeMS = p.parse('number?')(opts.maxTimeMS, `${field}.maxTimeMS`); - const fetcher = p.parse('object!', (fetcher, field) => { return { close: p.parse('function?')(fetcher.close, `${field}.close`), @@ -80,5 +76,5 @@ const parseCustomHttpOpts: Parser = (opts, field) => { }; })(opts.fetcher, `${field}.fetcher`); - return { client: 'custom', maxTimeMS, fetcher }; + return { client: 'custom', fetcher }; }; diff --git a/src/client/types/client-opts.ts b/src/client/types/client-opts.ts index 677390a5..42ef6dfc 100644 --- a/src/client/types/client-opts.ts +++ b/src/client/types/client-opts.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import type { DataAPIEnvironment, DataAPILoggingConfig } from '@/src/lib'; +import type { DataAPIEnvironment, DataAPILoggingConfig, TimeoutDescriptor } from '@/src/lib'; import type { Caller, DataAPIHttpOptions, DefaultAdminSpawnOptions, DefaultDbSpawnOptions } from '@/src/client'; import { OneOrMany } from '@/src/lib/types'; @@ -115,4 +115,5 @@ export interface DataAPIClientOptions { * ``` */ caller?: OneOrMany, + timeoutDefaults?: Partial, } diff --git a/src/client/types/http-opts.ts b/src/client/types/http-opts.ts index 77b7ebbc..690a9cfc 100644 --- a/src/client/types/http-opts.ts +++ b/src/client/types/http-opts.ts @@ -78,15 +78,6 @@ export interface DefaultHttpClientOptions { * @defaultValue true */ preferHttp2?: boolean, - /** - * The default maximum time in milliseconds to wait for a response from the server. - * - * This does *not* mean the request will be cancelled after this time, but rather that the client will wait - * for this time before considering the request to have timed out. - * - * The request may or may not still be running on the server after this time. - */ - maxTimeMS?: number, /** * Options specific to HTTP/1.1 requests. */ @@ -115,15 +106,6 @@ export interface FetchHttpClientOptions { * Use the native fetch API for making HTTP requests. */ client: 'fetch', - /** - * The default maximum time in milliseconds to wait for a response from the server. - * - * This does *not* mean the request will be cancelled after this time, but rather that the client will wait - * for this time before considering the request to have timed out. - * - * The request may or may not still be running on the server after this time. - */ - maxTimeMS?: number, } /** @@ -146,15 +128,6 @@ export interface CustomHttpClientOptions { * The custom "fetcher" to use. */ fetcher: Fetcher, - /** - * The default maximum time in milliseconds to wait for a response from the server. - * - * This does *not* mean the request will be cancelled after this time, but rather that the client will wait - * for this time before considering the request to have timed out. - * - * The request may or may not still be running on the server after this time. - */ - maxTimeMS?: number, } /** diff --git a/src/client/types/internal.ts b/src/client/types/internal.ts index 4275d813..accc3aae 100644 --- a/src/client/types/internal.ts +++ b/src/client/types/internal.ts @@ -12,7 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -import type { DataAPIClientEvents, DataAPIEnvironment, DataAPILoggingOutput, TokenProvider } from '@/src/lib'; +import type { + DataAPIClientEvents, + DataAPIEnvironment, + DataAPILoggingOutput, + TimeoutDescriptor, + TokenProvider, +} from '@/src/lib'; import type TypedEmitter from 'typed-emitter'; import type { FetchCtx } from '@/src/lib/api/fetch/types'; import type { AdminSpawnOptions, DbSpawnOptions } from '@/src/client'; @@ -34,9 +40,11 @@ export interface InternalRootClientOpts { dbOptions: Omit & { token: TokenProvider | undefined, logging: NormalizedLoggingConfig[] | undefined, + timeoutDefaults: TimeoutDescriptor, }, adminOptions: Omit & { adminToken: TokenProvider | undefined, logging: NormalizedLoggingConfig[] | undefined, + timeoutDefaults: TimeoutDescriptor, }, } diff --git a/src/client/types/spawn-admin.ts b/src/client/types/spawn-admin.ts index 83f853a6..711bd866 100644 --- a/src/client/types/spawn-admin.ts +++ b/src/client/types/spawn-admin.ts @@ -12,9 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -import type { DataAPILoggingConfig, TokenProvider } from '@/src/lib'; +import type { DataAPILoggingConfig, TimeoutDescriptor, TokenProvider } from '@/src/lib'; -export type DefaultAdminSpawnOptions = Omit; +export type DefaultAdminSpawnOptions = Omit; /** * The options available spawning a new {@link AstraAdmin} instance. @@ -78,4 +78,5 @@ export interface AdminSpawnOptions { * In the case of {@link DataAPIDbAdmin}, it will simply ignore this value. */ astraEnv?: 'dev' | 'prod' | 'test', + timeoutDefaults?: Partial, } diff --git a/src/client/types/spawn-db.ts b/src/client/types/spawn-db.ts index ea67d3e6..ace2c50f 100644 --- a/src/client/types/spawn-db.ts +++ b/src/client/types/spawn-db.ts @@ -12,10 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { DataAPILoggingConfig, TokenProvider } from '@/src/lib'; +import { DataAPILoggingConfig, type TimeoutDescriptor, TokenProvider } from '@/src/lib'; import { CollectionSerDesConfig, SomeDoc, SomeRow, TableSerDesConfig } from '@/src/documents'; -export type DefaultDbSpawnOptions = Omit; +export type DefaultDbSpawnOptions = Omit; /** * The options available spawning a new {@link Db} instance. @@ -111,6 +111,7 @@ export interface DbSpawnOptions { * as for enabling feature-flags or other non-standard headers. */ additionalHeaders?: Record, + timeoutDefaults?: Partial, } /** diff --git a/src/db/db.ts b/src/db/db.ts index c3302545..5336fdbf 100644 --- a/src/db/db.ts +++ b/src/db/db.ts @@ -13,9 +13,9 @@ // limitations under the License. import { Collection, SomeDoc } from '@/src/documents/collections'; -import { DEFAULT_KEYSPACE, RawDataAPIResponse } from '@/src/lib/api'; +import { DEFAULT_KEYSPACE, RawDataAPIResponse, WithTimeout } from '@/src/lib/api'; import { AstraDbAdmin } from '@/src/administration/astra-db-admin'; -import { DataAPIEnvironment, nullish, WithTimeout } from '@/src/lib/types'; +import { DataAPIEnvironment, nullish } from '@/src/lib/types'; import { extractDbIdFromUrl, extractRegionFromUrl } from '@/src/documents/utils'; import { AdminSpawnOptions, DbAdmin } from '@/src/administration'; import { DataAPIDbAdmin } from '@/src/administration/data-api-db-admin'; @@ -42,6 +42,7 @@ import { Logger } from '@/src/lib/logging/logger'; import { $CustomInspect } from '@/src/lib/constants'; import { InvalidEnvironmentError } from '@/src/db/errors'; import { AstraDbInfo } from '@/src/administration/types/admin/database-info'; +import { Timeouts } from '@/src/lib/api/timeouts'; /** * #### Overview @@ -140,6 +141,7 @@ export class Db { token: token, logging: Logger.advanceConfig(rootOpts.dbOptions.logging, dbOpts?.logging), additionalHeaders: { ...rootOpts.dbOptions.additionalHeaders, ...dbOpts?.additionalHeaders }, + timeoutDefaults: Timeouts.merge(rootOpts.dbOptions.timeoutDefaults, dbOpts?.timeoutDefaults), serdes: { collection: { serialize: [...toArray(dbOpts?.serdes?.collection?.serialize ?? []), ...toArray(rootOpts.dbOptions.serdes?.collection?.serialize ?? [])], @@ -179,6 +181,7 @@ export class Db { userAgent: rootOpts.userAgent, emissionStrategy: EmissionStrategy.Normal, additionalHeaders: this.#defaultOpts.dbOptions.additionalHeaders, + timeoutDefaults: this.#defaultOpts.dbOptions.timeoutDefaults, }); this.#id = extractDbIdFromUrl(endpoint); @@ -770,8 +773,8 @@ export class Db { }; await this.#httpClient.executeCommand(command, { - keyspace: options?.keyspace ?? this.keyspace, - maxTimeMS: options?.maxTimeMS, + timeoutManager: this.#httpClient.tm.custom({}, () => [720000, 'collectionAdminTimeout']), + keyspace: options?.keyspace, }); return this.collection(name, options); @@ -955,8 +958,8 @@ export class Db { }; await this.#httpClient.executeCommand(command, { - keyspace: options?.keyspace ?? this.keyspace, - maxTimeMS: options?.maxTimeMS, + timeoutManager: this.#httpClient.tm.single('tableAdminTimeout', options), + keyspace: options?.keyspace, }); return this.table(name, options); @@ -989,7 +992,10 @@ export class Db { * @remarks Use with caution. Have steel-toe boots on. Don't say I didn't warn you. */ public async dropCollection(name: string, options?: DropCollectionOptions): Promise { - await this.#httpClient.executeCommand({ deleteCollection: { name } }, options); + await this.#httpClient.executeCommand({ deleteCollection: { name } }, { + timeoutManager: this.#httpClient.tm.single('collectionAdminTimeout', options), + keyspace: options?.keyspace, + }); } /** @@ -1019,11 +1025,16 @@ export class Db { * @remarks Use with caution. Wear a mask. Don't say I didn't warn you. */ public async dropTable(name: string, options?: DropTableOptions): Promise { - await this.#httpClient.executeCommand({ dropTable: { name } }, options); + await this.#httpClient.executeCommand({ dropTable: { name } }, { + timeoutManager: this.#httpClient.tm.single('tableAdminTimeout', options), + keyspace: options?.keyspace, + }); } public async dropTableIndex(name: string, options?: WithTimeout): Promise { - await this.#httpClient.executeCommand({ dropIndex: { name } }, options); + await this.#httpClient.executeCommand({ dropIndex: { name } }, { + timeoutManager: this.#httpClient.tm.single('tableAdminTimeout', options), + }); } /** @@ -1080,7 +1091,10 @@ export class Db { }, }; - const resp = await this.#httpClient.executeCommand(command, options); + const resp = await this.#httpClient.executeCommand(command, { + timeoutManager: this.#httpClient.tm.single('collectionAdminTimeout', options), + keyspace: options?.keyspace, + }); return resp.status!.collections; } @@ -1138,7 +1152,10 @@ export class Db { }, }; - const resp = await this.#httpClient.executeCommand(command, options); + const resp = await this.#httpClient.executeCommand(command, { + timeoutManager: this.#httpClient.tm.single('tableAdminTimeout', options), + keyspace: options?.keyspace, + }); return resp.status!.tables; } @@ -1178,9 +1195,9 @@ export class Db { } return await this.#httpClient.executeCommand(command, { - keyspace: options?.keyspace, + timeoutManager: this.#httpClient.tm.single('generalMethodTimeout', options), collection: options?.collection ?? options?.table, - maxTimeMS: options?.maxTimeMS, + keyspace: options?.keyspace, }); } diff --git a/src/db/types/collections/create-collection.ts b/src/db/types/collections/create-collection.ts index 5647fd81..d9026d2c 100644 --- a/src/db/types/collections/create-collection.ts +++ b/src/db/types/collections/create-collection.ts @@ -13,7 +13,7 @@ // limitations under the License. import { SomeDoc } from '@/src/documents/collections'; -import { WithTimeout } from '@/src/lib/types'; +import type { WithTimeout } from '@/src/lib'; import { CollectionOptions, CollectionSpawnOptions } from '@/src/db'; /** diff --git a/src/db/types/collections/drop-collection.ts b/src/db/types/collections/drop-collection.ts index 0e22f482..c8ba0358 100644 --- a/src/db/types/collections/drop-collection.ts +++ b/src/db/types/collections/drop-collection.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { WithTimeout } from '@/src/lib/types'; +import type { WithTimeout } from '@/src/lib'; import { WithKeyspace } from '@/src/db'; /** diff --git a/src/db/types/collections/list-collections.ts b/src/db/types/collections/list-collections.ts index 9a97a0e3..408764f5 100644 --- a/src/db/types/collections/list-collections.ts +++ b/src/db/types/collections/list-collections.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { WithTimeout } from '@/src/lib/types'; +import type { WithTimeout } from '@/src/lib'; import { CollectionOptions, WithKeyspace } from '@/src/db'; import { SomeDoc } from '@/src/documents'; diff --git a/src/db/types/collections/spawn-collection.ts b/src/db/types/collections/spawn-collection.ts index a0e15e1a..8fda0143 100644 --- a/src/db/types/collections/spawn-collection.ts +++ b/src/db/types/collections/spawn-collection.ts @@ -14,7 +14,7 @@ import { WithKeyspace } from '@/src/db'; import { CollectionSerDesConfig, EmbeddingHeadersProvider, SomeDoc } from '@/src/documents'; -import { DataAPILoggingConfig } from '@/src/lib'; +import { DataAPILoggingConfig, type TimeoutDescriptor } from '@/src/lib'; /** * Options for spawning a new `Collection` instance through {@link db.collection} or {@link db.createCollection}. @@ -34,16 +34,6 @@ export interface CollectionSpawnOptions extends WithKeys * a provider that requires it (e.g. AWS bedrock). */ embeddingApiKey?: string | EmbeddingHeadersProvider | null, - /** - * The default `maxTimeMS` for all operations on the collection. Will override the maxTimeMS set in the DataAPIClient - * options; it can be overridden on a per-operation basis. - * - * This does *not* mean the request will be cancelled after this time, but rather that the client will wait - * for this time before considering the request to have timed out. - * - * The request may or may not still be running on the server after this time. - */ - defaultMaxTimeMS?: number | null, /** * The configuration for logging events emitted by the {@link DataAPIClient}. * @@ -53,4 +43,5 @@ export interface CollectionSpawnOptions extends WithKeys */ logging?: DataAPILoggingConfig, serdes?: CollectionSerDesConfig, + timeoutDefaults?: Partial, } diff --git a/src/db/types/common.ts b/src/db/types/common.ts index b500bccf..d33867a3 100644 --- a/src/db/types/common.ts +++ b/src/db/types/common.ts @@ -49,10 +49,3 @@ export interface WithKeyspace { */ keyspace?: string; } - -/** - * @internal - */ -export interface WithNullableKeyspace { - keyspace?: string | null; -} diff --git a/src/db/types/tables/drop-table.ts b/src/db/types/tables/drop-table.ts index 426bb70a..39b650ef 100644 --- a/src/db/types/tables/drop-table.ts +++ b/src/db/types/tables/drop-table.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { WithTimeout } from '@/src/lib/types'; +import type { WithTimeout } from '@/src/lib'; import { WithKeyspace } from '@/src/db'; export interface DropTableOptions extends WithTimeout, WithKeyspace {} diff --git a/src/db/types/tables/list-tables.ts b/src/db/types/tables/list-tables.ts index a3c5b31b..7e45e014 100644 --- a/src/db/types/tables/list-tables.ts +++ b/src/db/types/tables/list-tables.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { WithTimeout } from '@/src/lib/types'; +import type { WithTimeout } from '@/src/lib'; import { FullCreateTablePrimaryKeyDefinition, StrictCreateTableColumnDefinition, WithKeyspace } from '@/src/db'; export interface ListTablesOptions extends WithTimeout, WithKeyspace { diff --git a/src/db/types/tables/spawn-table.ts b/src/db/types/tables/spawn-table.ts index 3da9bdd1..4e66f27e 100644 --- a/src/db/types/tables/spawn-table.ts +++ b/src/db/types/tables/spawn-table.ts @@ -14,7 +14,7 @@ import { WithKeyspace } from '@/src/db'; import { EmbeddingHeadersProvider, SomeDoc, TableSerDesConfig } from '@/src/documents'; -import { DataAPILoggingConfig } from '@/src/lib'; +import { DataAPILoggingConfig, type TimeoutDescriptor } from '@/src/lib'; /** * Options for spawning a new `Table` instance through {@link db.table} or {@link db.createTable}. @@ -34,16 +34,6 @@ export interface TableSpawnOptions extends WithKeyspace * a provider that requires it (e.g. AWS bedrock). */ embeddingApiKey?: string | EmbeddingHeadersProvider | null, - /** - * The default `maxTimeMS` for all operations on the table. Will override the maxTimeMS set in the DataAPIClient - * options; it can be overridden on a per-operation basis. - * - * This does *not* mean the request will be cancelled after this time, but rather that the client will wait - * for this time before considering the request to have timed out. - * - * The request may or may not still be running on the server after this time. - */ - defaultMaxTimeMS?: number | null, /** * The configuration for logging events emitted by the {@link DataAPIClient}. * @@ -53,4 +43,5 @@ export interface TableSpawnOptions extends WithKeyspace */ logging?: DataAPILoggingConfig, serdes?: TableSerDesConfig, + timeoutDefaults?: Partial, } diff --git a/src/documents/collections/collection.ts b/src/documents/collections/collection.ts index c90c1d7c..71c603ae 100644 --- a/src/documents/collections/collection.ts +++ b/src/documents/collections/collection.ts @@ -212,7 +212,6 @@ export class Collection { parseWithBigNumbers: () => !!opts?.serdes?.enableBigNumbers, parser: withJbiNullProtoFix(jbi), }; - this.#httpClient = httpClient.forTableSlashCollectionOrWhateverWeWouldCallTheUnionOfTheseTypes(this.keyspace, this.name, opts, hack); this.#commands = new CommandImpls(this, this.#httpClient, mkCollectionSerDes(opts?.serdes)); this.#db = db; @@ -1564,7 +1563,7 @@ export class Collection { * @returns The options that the collection was created with (i.e. the `vector` and `indexing` operations). */ public async options(options?: WithTimeout): Promise> { - const results = await this.#db.listCollections({ maxTimeMS: options?.maxTimeMS, keyspace: this.keyspace }); + const results = await this.#db.listCollections({ timeout: options?.timeout, keyspace: this.keyspace }); const collection = results.find((c) => c.name === this.name); diff --git a/src/documents/collections/types/find/find-one-delete.ts b/src/documents/collections/types/find/find-one-delete.ts index edace62b..f9efcfbf 100644 --- a/src/documents/collections/types/find/find-one-delete.ts +++ b/src/documents/collections/types/find/find-one-delete.ts @@ -13,7 +13,7 @@ // limitations under the License. import type { Projection, Sort } from '@/src/documents'; -import { WithTimeout } from '@/src/lib/types'; +import type { WithTimeout } from '@/src/lib'; /** * Represents the options for the `findOneAndDelete` command. diff --git a/src/documents/collections/types/find/find-one-replace.ts b/src/documents/collections/types/find/find-one-replace.ts index 51914026..81313637 100644 --- a/src/documents/collections/types/find/find-one-replace.ts +++ b/src/documents/collections/types/find/find-one-replace.ts @@ -13,7 +13,7 @@ // limitations under the License. import type { Projection, Sort } from '@/src/documents'; -import { WithTimeout } from '@/src/lib/types'; +import type { WithTimeout } from '@/src/lib'; /** * Represents the options for the `findOneAndReplace` command. diff --git a/src/documents/collections/types/find/find-one-update.ts b/src/documents/collections/types/find/find-one-update.ts index f4109c0d..b30396bc 100644 --- a/src/documents/collections/types/find/find-one-update.ts +++ b/src/documents/collections/types/find/find-one-update.ts @@ -13,7 +13,7 @@ // limitations under the License. import type { Projection, Sort } from '@/src/documents'; -import { WithTimeout } from '@/src/lib/types'; +import type { WithTimeout } from '@/src/lib'; /** * Represents the options for the `findOneAndUpdate` command. diff --git a/src/documents/commands/command-impls.ts b/src/documents/commands/command-impls.ts index c6636fbc..902a0b81 100644 --- a/src/documents/commands/command-impls.ts +++ b/src/documents/commands/command-impls.ts @@ -65,7 +65,7 @@ export class CommandImpls { }); const raw = await this.#httpClient.executeCommand(command, { - maxTimeMS: options?.maxTimeMS, + timeoutManager: this.#httpClient.tm.single('generalMethodTimeout', options), bigNumsPresent: document[1], }); @@ -76,7 +76,7 @@ export class CommandImpls { public async insertMany(docs: readonly SomeDoc[], options: CollectionInsertManyOptions | nullish, err: new (descs: DataAPIDetailedErrorDescriptor[]) => DataAPIResponseError): Promise> { const chunkSize = options?.chunkSize ?? 50; - const timeoutManager = this.#httpClient.timeoutManager(options?.maxTimeMS); + const timeoutManager = this.#httpClient.tm.multipart('generalMethodTimeout', options); const insertedIds = (options?.ordered) ? await insertManyOrdered(this.#httpClient, this.#serdes, docs, chunkSize, timeoutManager, err) @@ -101,8 +101,8 @@ export class CommandImpls { }); const resp = await this.#httpClient.executeCommand(command, { + timeoutManager: this.#httpClient.tm.single('generalMethodTimeout', options), bigNumsPresent: filter[1] || update[1], - maxTimeMS: options?.maxTimeMS, }); return coalesceUpsertIntoUpdateResult(mkUpdateResult(resp), resp); @@ -121,7 +121,7 @@ export class CommandImpls { }, }); - const timeoutManager = this.#httpClient.timeoutManager(options?.maxTimeMS); + const timeoutManager = this.#httpClient.tm.multipart('generalMethodTimeout', options); const commonResult = mkUpdateResult(); let resp; @@ -168,8 +168,8 @@ export class CommandImpls { }); const resp = await this.#httpClient.executeCommand(command, { + timeoutManager: this.#httpClient.tm.single('generalMethodTimeout', options), bigNumsPresent: filter[1] || replacement[1], - maxTimeMS: options?.maxTimeMS, }); return coalesceUpsertIntoUpdateResult(mkUpdateResult(resp), resp); @@ -183,7 +183,7 @@ export class CommandImpls { }); const deleteOneResp = await this.#httpClient.executeCommand(command, { - maxTimeMS: options?.maxTimeMS, + timeoutManager: this.#httpClient.tm.single('generalMethodTimeout', options), bigNumsPresent: filter[1], }); @@ -199,7 +199,7 @@ export class CommandImpls { filter: filter[0], }); - const timeoutManager = this.#httpClient.timeoutManager(options?.maxTimeMS); + const timeoutManager = this.#httpClient.tm.multipart('generalMethodTimeout', options); let resp, numDeleted = 0; try { @@ -244,7 +244,7 @@ export class CommandImpls { }); const resp = await this.#httpClient.executeCommand(command, { - maxTimeMS: options?.maxTimeMS, + timeoutManager: this.#httpClient.tm.single('generalMethodTimeout', options), bigNumsPresent: filter[1], }); @@ -265,8 +265,8 @@ export class CommandImpls { }); const resp = await this.#httpClient.executeCommand(command, { + timeoutManager: this.#httpClient.tm.single('generalMethodTimeout', options), bigNumsPresent: filter[1] || replacement[1], - maxTimeMS: options?.maxTimeMS, }); return resp.data?.document || null; } @@ -279,7 +279,7 @@ export class CommandImpls { }); const resp = await this.#httpClient.executeCommand(command, { - maxTimeMS: options?.maxTimeMS, + timeoutManager: this.#httpClient.tm.single('generalMethodTimeout', options), bigNumsPresent: filter[1], }); return resp.data?.document || null; @@ -299,8 +299,8 @@ export class CommandImpls { }); const resp = await this.#httpClient.executeCommand(command, { + timeoutManager: this.#httpClient.tm.single('generalMethodTimeout', options), bigNumsPresent: filter[1] || update[1], - maxTimeMS: options?.maxTimeMS, }); return resp.data?.document || null; } @@ -350,7 +350,7 @@ export class CommandImpls { }); const resp = await this.#httpClient.executeCommand(command, { - maxTimeMS: options?.maxTimeMS, + timeoutManager: this.#httpClient.tm.single('generalMethodTimeout', options), bigNumsPresent, }); @@ -367,7 +367,11 @@ export class CommandImpls { public async estimatedDocumentCount(options?: WithTimeout): Promise { const command = mkBasicCmd('estimatedDocumentCount', {}); - const resp = await this.#httpClient.executeCommand(command, options); + + const resp = await this.#httpClient.executeCommand(command, { + timeoutManager: this.#httpClient.tm.single('generalMethodTimeout', options), + }); + return resp.status?.count; } } diff --git a/src/documents/commands/helpers/insertion.ts b/src/documents/commands/helpers/insertion.ts index 0f612b91..ccbb6636 100644 --- a/src/documents/commands/helpers/insertion.ts +++ b/src/documents/commands/helpers/insertion.ts @@ -14,7 +14,6 @@ import { DataAPIHttpClient } from '@/src/lib/api/clients'; import { DataAPISerDes } from '@/src/lib/api/ser-des'; -import { TimeoutManager } from '@/src/lib/api/timeout-managers'; import { DataAPIDetailedErrorDescriptor, DataAPIResponseError, @@ -22,6 +21,7 @@ import { mkRespErrorFromResponses, } from '@/src/documents/errors'; import { GenericInsertManyDocumentResponse, SomeDoc, SomeId } from '@/src/documents'; +import { TimeoutManager } from '@/src/lib/api/timeouts'; export const insertManyOrdered = async ( httpClient: DataAPIHttpClient, diff --git a/src/documents/commands/types/delete/delete-one.ts b/src/documents/commands/types/delete/delete-one.ts index 00ca8841..ea1c8091 100644 --- a/src/documents/commands/types/delete/delete-one.ts +++ b/src/documents/commands/types/delete/delete-one.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { WithTimeout } from '@/src/lib/types'; +import type { WithTimeout } from '@/src/lib'; import { Sort } from '@/src/documents'; export interface GenericDeleteOneResult { diff --git a/src/documents/commands/types/find/find.ts b/src/documents/commands/types/find/find.ts index ccd77d2c..946e1f67 100644 --- a/src/documents/commands/types/find/find.ts +++ b/src/documents/commands/types/find/find.ts @@ -13,8 +13,9 @@ // limitations under the License. import { Projection, Sort } from '@/src/documents'; +import { WithTimeout } from '@/src/lib'; -export interface GenericFindOptions { +export interface GenericFindOptions extends WithTimeout { sort?: Sort, projection?: Projection, limit?: number, diff --git a/src/documents/commands/types/insert/insert-many.ts b/src/documents/commands/types/insert/insert-many.ts index ba193514..8943fb92 100644 --- a/src/documents/commands/types/insert/insert-many.ts +++ b/src/documents/commands/types/insert/insert-many.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import type { WithTimeout } from '@/src/lib/types'; +import type { WithTimeout } from '@/src/lib'; /** * Options for a generic `insertMany` command using the Data API. diff --git a/src/documents/commands/types/update/replace-one.ts b/src/documents/commands/types/update/replace-one.ts index b532786f..73d1ba0f 100644 --- a/src/documents/commands/types/update/replace-one.ts +++ b/src/documents/commands/types/update/replace-one.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { WithTimeout } from '@/src/lib/types'; +import type { WithTimeout } from '@/src/lib'; import { Sort } from '@/src/documents'; export interface GenericReplaceOneOptions extends WithTimeout { diff --git a/src/documents/commands/types/update/update-many.ts b/src/documents/commands/types/update/update-many.ts index 517895fa..393040b1 100644 --- a/src/documents/commands/types/update/update-many.ts +++ b/src/documents/commands/types/update/update-many.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { WithTimeout } from '@/src/lib/types'; +import type { WithTimeout } from '@/src/lib'; export interface GenericUpdateManyOptions extends WithTimeout { upsert?: boolean, diff --git a/src/documents/commands/types/update/update-one.ts b/src/documents/commands/types/update/update-one.ts index 2c4b862c..403f561a 100644 --- a/src/documents/commands/types/update/update-one.ts +++ b/src/documents/commands/types/update/update-one.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import type { WithTimeout } from '@/src/lib/types'; +import type { WithTimeout } from '@/src/lib'; import type { Sort } from '@/src/documents'; /** diff --git a/src/documents/cursor.ts b/src/documents/cursor.ts index 54455c1d..779249a5 100644 --- a/src/documents/cursor.ts +++ b/src/documents/cursor.ts @@ -21,6 +21,7 @@ import { $CustomInspect } from '@/src/lib/constants'; import type { DataAPISerDes } from '@/src/lib/api/ser-des'; import { DataAPIError } from '@/src/documents/errors'; import type { Table } from '@/src/documents/tables'; +import { TimeoutManager } from '@/src/lib/api/timeouts'; export class CursorError extends DataAPIError { public readonly cursor: FindCursor; @@ -120,6 +121,8 @@ export abstract class FindCursor { readonly #filter: [Filter, boolean]; readonly #mapping?: (doc: any) => T; + readonly #timeoutManager: TimeoutManager; + #buffer: TRaw[] = []; #nextPageState?: string | null; #state = 'idle' as CursorStatus; @@ -137,6 +140,7 @@ export abstract class FindCursor { this.#filter = filter; this.#options = options ?? {}; this.#mapping = mapping; + this.#timeoutManager = parent._httpClient.tm.multipart('generalMethodTimeout', options); Object.defineProperty(this, $CustomInspect, { value: () => `FindCursor(source="${this.#parent.keyspace}.${this.#parent.name}",state="${this.#state}",consumed=${this.#consumed},buffered=${this.#buffer.length})`, @@ -618,7 +622,10 @@ export abstract class FindCursor { }, }; - const raw = await this.#parent._httpClient.executeCommand(command, { bigNumsPresent: this.#filter[1] }); + const raw = await this.#parent._httpClient.executeCommand(command, { + timeoutManager: this.#timeoutManager, + bigNumsPresent: this.#filter[1], + }); this.#nextPageState = raw.data?.nextPageState || null; this.#buffer = raw.data?.documents ?? []; diff --git a/src/documents/errors.ts b/src/documents/errors.ts index 5da99e1b..20b486fb 100644 --- a/src/documents/errors.ts +++ b/src/documents/errors.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import type { FetcherResponseInfo, RawDataAPIResponse } from '@/src/lib'; +import { FetcherResponseInfo, RawDataAPIResponse, TimeoutDescriptor } from '@/src/lib'; import type { CollectionDeleteManyResult, CollectionInsertManyResult, @@ -20,6 +20,8 @@ import type { SomeDoc, } from '@/src/documents/collections'; import type { TableInsertManyResult } from '@/src/documents/tables'; +import { HTTPRequestInfo } from '@/src/lib/api/clients'; +import { TimedOutTypes, Timeouts } from '@/src/lib/api/timeouts'; /** * An object representing a single "soft" (2XX) error returned from the Data API, typically with an error code and a @@ -178,18 +180,25 @@ export class DataAPITimeoutError extends DataAPIError { /** * The timeout that was set for the operation, in milliseconds. */ - public readonly timeout: number; + public readonly timeout: Partial; + + public readonly timedOutTypes: TimedOutTypes; /** * Should not be instantiated by the user. * * @internal */ - constructor(timeout: number) { - super(`Command timed out after ${timeout}ms`); - this.timeout = timeout; + constructor(info: HTTPRequestInfo, types: TimedOutTypes) { + super(Timeouts.fmtTimeoutMsg(info.timeoutManager, types)); + this.timeout = info.timeoutManager.initial(); + this.timedOutTypes = types; this.name = 'DataAPITimeoutError'; } + + public static mk(info: HTTPRequestInfo, types: TimedOutTypes): DataAPITimeoutError { + return new DataAPITimeoutError(info, types); + } } /** diff --git a/src/documents/events.ts b/src/documents/events.ts index 06840976..0fe202b6 100644 --- a/src/documents/events.ts +++ b/src/documents/events.ts @@ -17,6 +17,7 @@ import { DEFAULT_KEYSPACE, type RawDataAPIResponse } from '@/src/lib'; import { DataAPIClientEvent } from '@/src/lib/logging/events'; import type { DataAPIRequestInfo } from '@/src/lib/api/clients/data-api-http-client'; import type { DataAPIErrorDescriptor } from '@/src/documents/errors'; +import { TimeoutDescriptor } from '@/src/lib/api/timeouts'; /** * The events emitted by the {@link DataAPIClient}. These events are emitted at various stages of the @@ -118,7 +119,7 @@ export class CommandStartedEvent extends CommandEvent { /** * The timeout for the command, in milliseconds. */ - public readonly timeout: number; + public readonly timeout: Partial; /** * Should not be instantiated by the user. @@ -127,7 +128,7 @@ export class CommandStartedEvent extends CommandEvent { */ constructor(info: DataAPIRequestInfo) { super(info); - this.timeout = info.timeoutManager.ms; + this.timeout = info.timeoutManager.initial(); } public formatted(): string { diff --git a/src/documents/tables/table.ts b/src/documents/tables/table.ts index 1596ad20..d1fd5676 100644 --- a/src/documents/tables/table.ts +++ b/src/documents/tables/table.ts @@ -375,7 +375,9 @@ export class Table { name: this.name, operation: options.operation, }, - }, options); + }, { + timeoutManager: this.#httpClient.tm.single('tableAdminTimeout', options), + }); return this; } @@ -395,7 +397,9 @@ export class Table { ifNotExists: options?.ifNotExists, }, }, - }, options); + }, { + timeoutManager: this.#httpClient.tm.single('tableAdminTimeout', options), + }); } public async createVectorIndex(name: string, column: Cols | string, options?: CreateTableVectorIndexOptions): Promise { @@ -413,11 +417,16 @@ export class Table { ifNotExists: options?.ifNotExists, }, }, - }, options); + }, { + timeoutManager: this.#httpClient.tm.single('tableAdminTimeout', options), + }); } public async definition(options?: WithTimeout): Promise { - const results = await this.#db.listTables({ maxTimeMS: options?.maxTimeMS, keyspace: this.keyspace }); + const results = await this.#db.listTables({ + timeout: options?.timeout, + keyspace: this.keyspace, + }); const table = results.find((t) => t.name === this.name); diff --git a/src/documents/types/common.ts b/src/documents/types/common.ts index 3f25f68e..b7a45bf2 100644 --- a/src/documents/types/common.ts +++ b/src/documents/types/common.ts @@ -52,7 +52,7 @@ export type SortDirection = 1 | -1 | 'asc' | 'desc' | 'ascending' | 'descending' * * @public */ -export type Sort = Record; +export type Sort = Record; /** * Specifies which fields should be included/excluded in the returned documents. diff --git a/src/lib/api/clients/data-api-http-client.ts b/src/lib/api/clients/data-api-http-client.ts index afd73c50..87e946a0 100644 --- a/src/lib/api/clients/data-api-http-client.ts +++ b/src/lib/api/clients/data-api-http-client.ts @@ -13,8 +13,6 @@ // limitations under the License. // noinspection ExceptionCaughtLocallyJS -import { TimeoutManager, TimeoutOptions } from '@/src/lib/api/timeout-managers'; -import type { WithNullableKeyspace } from '@/src/db/types/common'; import { Logger } from '@/src/lib/logging/logger'; import { nullish, RawDataAPIResponse, TokenProvider } from '@/src/lib'; import { @@ -27,16 +25,19 @@ import { } from '@/src/documents'; import type { HeaderProvider, HTTPClientOptions, KeyspaceRef } from '@/src/lib/api/clients/types'; import { HttpClient } from '@/src/lib/api/clients/http-client'; -import { DEFAULT_DATA_API_AUTH_HEADER, DEFAULT_TIMEOUT, HttpMethods } from '@/src/lib/api/constants'; +import { DEFAULT_DATA_API_AUTH_HEADER, HttpMethods } from '@/src/lib/api/constants'; import { CollectionSpawnOptions, TableSpawnOptions } from '@/src/db'; import type { AdminSpawnOptions } from '@/src/client'; import { isNullish } from '@/src/lib/utils'; import { mkRespErrorFromResponse } from '@/src/documents/errors'; +import { TimeoutManager, Timeouts } from '@/src/lib/api/timeouts'; /** * @internal */ -type ExecuteCommandOptions = TimeoutOptions & WithNullableKeyspace & { +type ExecuteCommandOptions = { + keyspace?: string | null, + timeoutManager: TimeoutManager, bigNumsPresent?: boolean, collection?: string, } @@ -69,7 +70,7 @@ export const EmissionStrategy: Record<'Normal' | 'Admin', EmissionStrategy> = { }), Admin: (logger) => ({ emitCommandStarted(info) { - logger.adminCommandStarted?.(adaptInfo4Devops(info), true, info.timeoutManager.msRemaining()); + logger.adminCommandStarted?.(adaptInfo4Devops(info), true, null!); // TODO }, emitCommandFailed(info, error, started) { logger.adminCommandFailed?.(adaptInfo4Devops(info), true, error, started); @@ -114,16 +115,14 @@ export interface BigNumberHack { export class DataAPIHttpClient extends HttpClient { public collection?: string; public keyspace: KeyspaceRef; - public maxTimeMS: number; public emissionStrategy: ReturnType; public bigNumHack?: BigNumberHack; readonly #props: DataAPIHttpClientOpts; constructor(props: DataAPIHttpClientOpts) { - super(props, [mkAuthHeaderProvider(props.tokenProvider), () => props.embeddingHeaders.getHeaders()]); + super(props, [mkAuthHeaderProvider(props.tokenProvider), () => props.embeddingHeaders.getHeaders()], DataAPITimeoutError.mk); this.keyspace = props.keyspace; this.#props = props; - this.maxTimeMS = this.fetchCtx.maxTimeMS ?? DEFAULT_TIMEOUT; this.emissionStrategy = props.emissionStrategy(this.logger); } @@ -136,8 +135,8 @@ export class DataAPIHttpClient extends HttpClient { }); clone.collection = collection; - clone.maxTimeMS = opts?.defaultMaxTimeMS ?? this.maxTimeMS; clone.bigNumHack = bigNumHack; + clone.tm = new Timeouts(DataAPITimeoutError.mk, { ...this.tm.baseTimeouts, ...opts?.timeoutDefaults }); return clone; } @@ -152,23 +151,17 @@ export class DataAPIHttpClient extends HttpClient { additionalHeaders: { ...this.#props.additionalHeaders, ...opts?.additionalHeaders }, }); - clone.emissionStrategy = EmissionStrategy.Admin(clone.logger); clone.collection = undefined; + clone.emissionStrategy = EmissionStrategy.Admin(clone.logger); + clone.tm = new Timeouts(DataAPITimeoutError.mk, { ...this.tm.baseTimeouts, ...opts?.timeoutDefaults }); return clone; } - public timeoutManager(timeout: number | undefined) { - timeout ??= this.maxTimeMS; - return new TimeoutManager(timeout, () => new DataAPITimeoutError(timeout)); - } - - public async executeCommand(command: Record, options: ExecuteCommandOptions | nullish) { - const timeoutManager = options?.timeoutManager ?? this.timeoutManager(options?.maxTimeMS); - + public async executeCommand(command: Record, options: ExecuteCommandOptions) { return await this._requestDataAPI({ url: this.baseUrl, - timeoutManager: timeoutManager, + timeoutManager: options.timeoutManager, collection: options?.collection, keyspace: options?.keyspace, command: command, diff --git a/src/lib/api/clients/devops-api-http-client.ts b/src/lib/api/clients/devops-api-http-client.ts index b90bad10..289fc0b5 100644 --- a/src/lib/api/clients/devops-api-http-client.ts +++ b/src/lib/api/clients/devops-api-http-client.ts @@ -15,12 +15,12 @@ import { HttpClient } from '@/src/lib/api/clients'; import { DevOpsAPIResponseError, DevOpsAPITimeoutError, DevOpsUnexpectedStateError } from '@/src/administration/errors'; -import { TimeoutManager, type TimeoutOptions } from '@/src/lib/api/timeout-managers'; import { AstraAdminBlockingOptions } from '@/src/administration/types'; import { DEFAULT_DEVOPS_API_AUTH_HEADER, HttpMethods } from '@/src/lib/api/constants'; import type { HeaderProvider, HTTPClientOptions, HttpMethodStrings } from '@/src/lib/api/clients/types'; import type { nullish, TokenProvider } from '@/src/lib'; import { jsonTryParse } from '@/src/lib/utils'; +import { TimeoutManager } from '@/src/lib/api/timeouts'; /** * @internal @@ -38,6 +38,7 @@ interface LongRunningRequestInfo { legalStates: string[], defaultPollInterval: number, options: AstraAdminBlockingOptions | undefined, + timeoutManager: TimeoutManager, } interface DevopsAPIResponse { @@ -55,18 +56,17 @@ interface DevOpsAPIHttpClientOpts extends HTTPClientOptions { */ export class DevOpsAPIHttpClient extends HttpClient { constructor(opts: DevOpsAPIHttpClientOpts) { - super(opts, [mkAuthHeaderProvider(opts.tokenProvider)]); + super(opts, [mkAuthHeaderProvider(opts.tokenProvider)], DevOpsAPITimeoutError.mk); } - public async request(req: DevOpsAPIRequestInfo, options: TimeoutOptions | undefined, started: number = 0): Promise { + public async request(req: DevOpsAPIRequestInfo, timeoutManager: TimeoutManager, started: number = 0): Promise { const isLongRunning = started !== 0; try { - const timeoutManager = options?.timeoutManager ?? this._timeoutManager(options?.maxTimeMS); const url = this.baseUrl + req.path; if (!isLongRunning) { - this.logger.adminCommandStarted?.(req, isLongRunning, timeoutManager.ms); + this.logger.adminCommandStarted?.(req, isLongRunning, timeoutManager.initial()); } started ||= performance.now(); @@ -105,31 +105,26 @@ export class DevOpsAPIHttpClient extends HttpClient { } public async requestLongRunning(req: DevOpsAPIRequestInfo, info: LongRunningRequestInfo): Promise { - const timeoutManager = this._timeoutManager(info.options?.maxTimeMS); const isLongRunning = info.options?.blocking !== false; + const timeoutManager = info.timeoutManager; - this.logger.adminCommandStarted?.(req, isLongRunning, timeoutManager.ms); + this.logger.adminCommandStarted?.(req, isLongRunning, timeoutManager.initial()); const started = performance.now(); - const resp = await this.request(req, { timeoutManager }, started); + const resp = await this.request(req, timeoutManager, started); const id = (typeof info.id === 'function') ? info.id(resp) : info.id; - await this._awaitStatus(id, req, info, timeoutManager, started); + await this._awaitStatus(id, req, info, started); this.logger.adminCommandSucceeded?.(req, isLongRunning, resp, started); return resp; } - private _timeoutManager(timeout: number | undefined) { - timeout ??= this.fetchCtx.maxTimeMS ?? (12 * 60 * 1000); - return new TimeoutManager(timeout, (info) => new DevOpsAPITimeoutError(info.url, timeout)); - } - - private async _awaitStatus(id: string, req: DevOpsAPIRequestInfo, info: LongRunningRequestInfo, timeoutManager: TimeoutManager, started: number): Promise { + private async _awaitStatus(id: string, req: DevOpsAPIRequestInfo, info: LongRunningRequestInfo, started: number): Promise { if (info.options?.blocking === false) { return; } @@ -148,9 +143,7 @@ export class DevOpsAPIHttpClient extends HttpClient { const resp = await this.request({ method: HttpMethods.Get, path: `/databases/${id}`, - }, { - timeoutManager: timeoutManager, - }, started); + }, info.timeoutManager, started); if (resp.data?.status === info.target) { break; diff --git a/src/lib/api/clients/http-client.ts b/src/lib/api/clients/http-client.ts index d22d621e..3c1ac20a 100644 --- a/src/lib/api/clients/http-client.ts +++ b/src/lib/api/clients/http-client.ts @@ -20,6 +20,7 @@ import type { HeaderProvider, HTTPClientOptions, HTTPRequestInfo } from '@/src/l import type { DataAPIClientEvents } from '@/src/lib/logging'; import { Logger } from '@/src/lib/logging/logger'; import { OneOrMany } from '@/src/lib/types'; +import { MkTimeoutError, Timeouts } from '@/src/lib/api/timeouts'; /** * @internal @@ -31,8 +32,9 @@ export abstract class HttpClient { readonly fetchCtx: FetchCtx; readonly baseHeaders: Record; readonly headerProviders: HeaderProvider[]; + tm: Timeouts; - protected constructor(options: HTTPClientOptions, headerProviders: HeaderProvider[]) { + protected constructor(options: HTTPClientOptions, headerProviders: HeaderProvider[], mkTimeoutError: MkTimeoutError) { this.baseUrl = options.baseUrl; this.emitter = options.emitter; this.logger = new Logger(options.logging, options.emitter, console); @@ -48,6 +50,7 @@ export abstract class HttpClient { this.baseHeaders['Feature-Flag-tables'] = 'true'; this.headerProviders = headerProviders; + this.tm = new Timeouts(mkTimeoutError, options.timeoutDefaults); } protected async _request(info: HTTPRequestInfo): Promise { @@ -55,10 +58,10 @@ export abstract class HttpClient { throw new Error('Can\'t make requests on a closed client'); } - const msRemaining = info.timeoutManager.msRemaining(); + const [msRemaining, mkTimeoutError] = info.timeoutManager.advance(info); if (msRemaining <= 0) { - throw info.timeoutManager.mkTimeoutError(info); + throw mkTimeoutError(); } const params = info.params ?? {}; @@ -86,7 +89,7 @@ export abstract class HttpClient { headers: reqHeaders, forceHttp1: info.forceHttp1, timeout: msRemaining, - mkTimeoutError: () => info.timeoutManager.mkTimeoutError(info), + mkTimeoutError, }); } } diff --git a/src/lib/api/clients/types.ts b/src/lib/api/clients/types.ts index 595b7eaa..1e286393 100644 --- a/src/lib/api/clients/types.ts +++ b/src/lib/api/clients/types.ts @@ -17,8 +17,8 @@ import type { DataAPICommandEvents } from '@/src/documents'; import type { FetchCtx } from '@/src/lib/api/fetch/types'; import type { HttpMethods } from '@/src/lib/api/constants'; import type { Ref } from '@/src/lib/types'; -import type { TimeoutManager } from '@/src/lib/api/timeout-managers'; import type { NormalizedLoggingConfig } from '@/src/lib/logging/types'; +import { TimeoutDescriptor, TimeoutManager } from '@/src/lib/api/timeouts'; /** * @internal @@ -31,6 +31,7 @@ export interface HTTPClientOptions { fetchCtx: FetchCtx, userAgent: string, additionalHeaders: Record | undefined, + timeoutDefaults: TimeoutDescriptor, } /** diff --git a/src/lib/api/constants.ts b/src/lib/api/constants.ts index 611c5ab8..afcd4844 100644 --- a/src/lib/api/constants.ts +++ b/src/lib/api/constants.ts @@ -35,11 +35,6 @@ export const HttpMethods = { */ export const DEFAULT_KEYSPACE = 'default_keyspace'; -/** - * @internal - */ -export const DEFAULT_TIMEOUT = 30000; - /** * @internal */ diff --git a/src/lib/api/fetch/types.ts b/src/lib/api/fetch/types.ts index 627c8483..70503e87 100644 --- a/src/lib/api/fetch/types.ts +++ b/src/lib/api/fetch/types.ts @@ -123,5 +123,4 @@ export interface FetcherResponseInfo { export interface FetchCtx { ctx: Fetcher, closed: Ref, - maxTimeMS: number | undefined, } diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts index 08dd456a..110b7792 100644 --- a/src/lib/api/index.ts +++ b/src/lib/api/index.ts @@ -31,3 +31,8 @@ export type { DataAPISerCtx, DataAPIDesCtx, } from './ser-des'; + +export type { + TimeoutDescriptor, + WithTimeout, +} from './timeouts'; diff --git a/src/lib/api/timeout-managers.ts b/src/lib/api/timeout-managers.ts deleted file mode 100644 index 84509e81..00000000 --- a/src/lib/api/timeout-managers.ts +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright DataStax, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { HTTPRequestInfo } from '@/src/lib/api/clients/types'; - -/** - * Internal representation of timeout options, allowing a shorthand for a single call timeout manager via - * `maxTimeMS`, with an explicit {@link TimeoutManager} being allowed to be passed if necessary, generally - * for multi-call timeout management. - * - * @internal - */ -export type TimeoutOptions = { - maxTimeMS?: number, - timeoutManager?: never, -} | { - timeoutManager: TimeoutManager, - maxTimeMS?: never, -} - -/** - * Represents a function that creates a timeout error for a given request context. - * - * @example - * ```typescript - * const mkTimeoutError: MkTimeoutError = (info) => { - *   return new DevOpsAPITimeoutError(info.url, timeout); - * } - * ``` - * - * @internal - */ -export type MkTimeoutError = (info: HTTPRequestInfo) => Error; - -/** - * Tracks the remaining time before a timeout occurs. Can be used for both single and multi-call timeout management. - * - * The first call to `msRemaining` will start the timer. - * - * @internal - */ -export class TimeoutManager { - private _deadline!: number; - private _started: boolean; - - public readonly ms: number; - - constructor(ms: number, readonly mkTimeoutError: MkTimeoutError) { - this.ms = ms || 600000; - this._started = false; - } - - msRemaining() { - if (!this._started) { - this._started = true; - this._deadline = Date.now() + this.ms; - return this.ms; - } - return this._deadline - Date.now(); - } -} diff --git a/src/lib/api/timeouts.ts b/src/lib/api/timeouts.ts new file mode 100644 index 00000000..8538d0e4 --- /dev/null +++ b/src/lib/api/timeouts.ts @@ -0,0 +1,170 @@ +// Copyright DataStax, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { nullish, OneOrMany } from '@/src/lib'; +import { HTTPRequestInfo } from '@/src/lib/api/clients'; +import { toArray } from '@/src/lib/utils'; + +export type TimedOutTypes = OneOrMany | 'provided'; + +export interface TimeoutDescriptor { + requestTimeout: number, + generalMethodTimeout: number, + collectionAdminTimeout: number, + tableAdminTimeout: number, + databaseAdminTimeout: number, + keyspaceAdminTimeout: number, +} + +export interface WithTimeout { + timeout?: number | Pick, Timeouts>; +} + +export type MkTimeoutError = (info: HTTPRequestInfo, timeoutType: TimedOutTypes) => Error; + +export interface TimeoutManager { + initial(): Partial, + advance(info: HTTPRequestInfo): [number, () => Error], +} + +export const EffectivelyInfinity = 2 ** 31 - 1; + +export class Timeouts { + constructor( + private readonly _mkTimeoutError: MkTimeoutError, + public readonly baseTimeouts: TimeoutDescriptor, + ) {} + + public single(key: Exclude, override: WithTimeout | nullish): TimeoutManager { + if (typeof override?.timeout === 'number') { + const timeout = override.timeout; + + const initial = { + requestTimeout: timeout, + [key]: timeout, + }; + + return this.custom(initial, () => { + return [timeout, 'provided']; + }); + } + + const timeouts = { + requestTimeout: (override?.timeout?.requestTimeout ?? this.baseTimeouts.requestTimeout) || EffectivelyInfinity, + [key]: (override?.timeout?.[key] ?? this.baseTimeouts[key]) || EffectivelyInfinity, + }; + + const timeout = Math.min(timeouts.requestTimeout, timeouts[key]); + + const type = + (timeouts.requestTimeout === timeouts[key]) + ? ['requestTimeout', key] : + (timeouts.requestTimeout < timeouts[key]) + ? 'requestTimeout' + : key; + + return this.custom(timeouts, () => { + return [timeout, type]; + }); + } + + public multipart(key: Exclude, override: WithTimeout | nullish): TimeoutManager { + const requestTimeout = (typeof override?.timeout === 'object') + ? override.timeout?.requestTimeout ?? this.baseTimeouts.requestTimeout + : this.baseTimeouts.requestTimeout; + + const overallTimeout = + (typeof override?.timeout === 'object') + ? override.timeout?.[key] ?? this.baseTimeouts[key] : + (typeof override?.timeout === 'number') + ? override.timeout + : this.baseTimeouts[key]; + + const initial = { + requestTimeout, + [key]: overallTimeout, + }; + + let started: number; + + return this.custom(initial, () => { + if (!started) { + started = Date.now(); + } + + const overallLeft = overallTimeout - (Date.now() - started); + + if (overallLeft < requestTimeout) { + return [overallLeft, key]; + } else if (overallLeft > requestTimeout) { + return [requestTimeout, 'requestTimeout']; + } else { + return [overallLeft, ['requestTimeout', key]]; + } + }); + } + + public custom(peek: Partial, advance: () => [number, TimedOutTypes]): TimeoutManager { + return { + initial() { + return peek; + }, + advance: (info) => { + const advanced = advance() as any; + const timeoutType = advanced[1]; + advanced[1] = () => this._mkTimeoutError(info, timeoutType); + return advanced; + }, + }; + } + + public static Default: TimeoutDescriptor = { + requestTimeout: 10000, + generalMethodTimeout: 30000, + collectionAdminTimeout: 60000, + tableAdminTimeout: 30000, + databaseAdminTimeout: 600000, + keyspaceAdminTimeout: 30000, + }; + + public static merge(base: TimeoutDescriptor, custom: Partial | nullish): TimeoutDescriptor { + if (!custom) { + return base; + } + + return { + requestTimeout: custom.requestTimeout ?? base.requestTimeout, + generalMethodTimeout: custom.generalMethodTimeout ?? base.generalMethodTimeout, + collectionAdminTimeout: custom.collectionAdminTimeout ?? base.collectionAdminTimeout, + tableAdminTimeout: custom.tableAdminTimeout ?? base.tableAdminTimeout, + databaseAdminTimeout: custom.databaseAdminTimeout ?? base.databaseAdminTimeout, + keyspaceAdminTimeout: custom.keyspaceAdminTimeout ?? base.keyspaceAdminTimeout, + }; + } + + public static fmtTimeoutMsg = (tm: TimeoutManager, timeoutTypes: TimedOutTypes) => { + const timeout = (timeoutTypes === 'provided') + ? Object.values(tm.initial())[0]! + : tm.initial()[toArray(timeoutTypes)[0]]; + + const types = + (timeoutTypes === 'provided') + ? `The timeout provided via \`{ timeout: ${timeout} }\` timed out` : + (Array.isArray(timeoutTypes)) + ? timeoutTypes.join(' and ') + ' simultaneously timed out' + : `${timeoutTypes} timed out`; + + return `Command timed out after ${timeout}ms (${types})`; + }; +} diff --git a/src/lib/index.ts b/src/lib/index.ts index a91e599e..f68f561e 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -16,4 +16,4 @@ export * from './api'; export * from './token-providers'; export * from './logging'; export { DataAPIEnvironments } from './constants'; -export type { nullish, WithTimeout, DataAPIEnvironment, DeepPartial, OneOrMany } from './types'; +export type { nullish, DataAPIEnvironment, DeepPartial, OneOrMany } from './types'; diff --git a/src/lib/timeouts.ts b/src/lib/timeouts.ts deleted file mode 100644 index 5781e369..00000000 --- a/src/lib/timeouts.ts +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright DataStax, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -export interface TimeoutDescriptor { - requestTimeout: number, - generalMethodTimeout: number, - collectionAdminTimeout: number, - tableAdminTimeout: number, - databaseAdminTimeout: number, - keyspaceAdminTimeout: number, -} diff --git a/src/lib/types.ts b/src/lib/types.ts index aa5b2232..0c17b55e 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -15,21 +15,21 @@ import { DataAPIEnvironments } from '@/src/lib/constants'; -/** - * Represents options related to timeouts. Note that this means "the max time the client will wait for a response - * from the server"—**an operation timing out does not necessarily mean the operation failed on the server**. - * - * On paginated operations, the timeout applies across all network requests. For example, if you set a timeout of 5 - * seconds and the operation requires 3 network requests, each request must complete in less than 5 seconds total. - * - * @public - */ -export interface WithTimeout { - /** - * The maximum time to wait for a response from the server, in milliseconds. - */ - maxTimeMS?: number; -} +// /** +// * Represents options related to timeouts. Note that this means "the max time the client will wait for a response +// * from the server"—**an operation timing out does not necessarily mean the operation failed on the server**. +// * +// * On paginated operations, the timeout applies across all network requests. For example, if you set a timeout of 5 +// * seconds and the operation requires 3 network requests, each request must complete in less than 5 seconds total. +// * +// * @public +// */ +// export interface WithTimeout { +// /** +// * The maximum time to wait for a response from the server, in milliseconds. +// */ +// maxTimeMS?: number; +// } /** * Shorthand type to represent some nullish value. diff --git a/tests/integration/administration/lifecycle.test.ts b/tests/integration/administration/lifecycle.test.ts index 3c55686f..d60a11d4 100644 --- a/tests/integration/administration/lifecycle.test.ts +++ b/tests/integration/administration/lifecycle.test.ts @@ -15,7 +15,6 @@ import assert from 'assert'; import { DevOpsAPIResponseError } from '@/src/administration'; -import { TimeoutManager } from '@/src/lib/api/timeout-managers'; import { background, it, TEMP_DB_NAME } from '@/tests/testlib'; import { DEFAULT_KEYSPACE, HttpMethods } from '@/src/lib/api/constants'; import { buildAstraEndpoint } from '@/src/lib/utils'; @@ -26,7 +25,7 @@ background('(ADMIN) (LONG) (NOT-DEV) (ASTRA) integration.administration.lifecycl for (const db of await admin.listDatabases()) { if (db.name === TEMP_DB_NAME && db.status !== 'TERMINATING') { - void admin.dropDatabase(db.id, { maxTimeMS: 720000 }); + void admin.dropDatabase(db.id, { timeout: 720000 }); } } @@ -37,7 +36,7 @@ background('(ADMIN) (LONG) (NOT-DEV) (ASTRA) integration.administration.lifecycl keyspace: 'my_keyspace', }, { blocking: false, - maxTimeMS: 720000, + timeout: 720000, }); const asyncDb = asyncDbAdmin.db(); @@ -100,7 +99,7 @@ background('(ADMIN) (LONG) (NOT-DEV) (ASTRA) integration.administration.lifecycl name: TEMP_DB_NAME, cloudProvider: 'GCP', region: 'us-east1', - }, { maxTimeMS: 720000 }); + }, { timeout: 720000 }); const syncDb = syncDbAdmin.db(); { @@ -131,7 +130,8 @@ background('(ADMIN) (LONG) (NOT-DEV) (ASTRA) integration.administration.lifecycl defaultPollInterval: 10000, id: null!, options: undefined, - }, new TimeoutManager(0, () => new Error('Timeout')), 0); + timeoutManager: asyncDbAdmin._httpClient.tm.multipart('generalMethodTimeout', { timeout: 0 }), + }, 0); } for (const [dbAdmin, db, dbType] of [[syncDbAdmin, syncDb, 'sync'], [asyncDbAdmin, asyncDb, 'async']] as const) { @@ -179,7 +179,8 @@ background('(ADMIN) (LONG) (NOT-DEV) (ASTRA) integration.administration.lifecycl defaultPollInterval: 1000, id: null!, options: undefined, - }, new TimeoutManager(0, () => new Error('Timeout')), 0); + timeoutManager: asyncDbAdmin._httpClient.tm.multipart('generalMethodTimeout', { timeout: 0 }), + }, 0); } for (const [dbAdmin, db, dbType] of [[syncDbAdmin, syncDb, 'sync'], [asyncDbAdmin, asyncDb, 'async']] as const) { @@ -203,7 +204,8 @@ background('(ADMIN) (LONG) (NOT-DEV) (ASTRA) integration.administration.lifecycl defaultPollInterval: 1000, id: null!, options: undefined, - }, new TimeoutManager(0, () => new Error('Timeout')), 0); + timeoutManager: asyncDbAdmin._httpClient.tm.multipart('generalMethodTimeout', { timeout: 0 }), + }, 0); } for (const [dbAdmin, db, dbType] of [[syncDbAdmin, syncDb, 'sync'], [asyncDbAdmin, asyncDb, 'async']] as const) { @@ -222,14 +224,15 @@ background('(ADMIN) (LONG) (NOT-DEV) (ASTRA) integration.administration.lifecycl } { - await admin.dropDatabase(syncDb, { maxTimeMS: 720000 }); + await admin.dropDatabase(syncDb, { timeout: 720000 }); await asyncDbAdmin._httpClient['_awaitStatus'](asyncDb.id, {} as any, { target: 'TERMINATED', legalStates: ['TERMINATING'], defaultPollInterval: 10000, id: null!, options: undefined, - }, new TimeoutManager(0, () => new Error('Timeout')), 0); + timeoutManager: asyncDbAdmin._httpClient.tm.multipart('generalMethodTimeout', { timeout: 0 }), + }, 0); } for (const [dbAdmin, dbType] of [[syncDbAdmin, 'sync'], [asyncDbAdmin, 'async']] as const) { @@ -238,10 +241,10 @@ background('(ADMIN) (LONG) (NOT-DEV) (ASTRA) integration.administration.lifecycl } { - await assert.rejects(async () => { await admin.dropDatabase(syncDb.id, { maxTimeMS: 720000 }); }, DevOpsAPIResponseError); - await assert.rejects(async () => { await admin.dropDatabase(syncDb.id, { blocking: false, maxTimeMS: 720000 }); }, DevOpsAPIResponseError); - await assert.rejects(async () => { await admin.dropDatabase(syncDb, { maxTimeMS: 720000 }); }, DevOpsAPIResponseError); - await assert.rejects(async () => { await admin.dropDatabase(syncDb, { blocking: false, maxTimeMS: 720000 }); }, DevOpsAPIResponseError); + await assert.rejects(async () => { await admin.dropDatabase(syncDb.id, { timeout: 720000 }); }, DevOpsAPIResponseError); + await assert.rejects(async () => { await admin.dropDatabase(syncDb.id, { blocking: false, timeout: 720000 }); }, DevOpsAPIResponseError); + await assert.rejects(async () => { await admin.dropDatabase(syncDb, { timeout: 720000 }); }, DevOpsAPIResponseError); + await assert.rejects(async () => { await admin.dropDatabase(syncDb, { blocking: false, timeout: 720000 }); }, DevOpsAPIResponseError); await assert.rejects(async () => { await syncDbAdmin.drop(); }, DevOpsAPIResponseError); await assert.rejects(async () => { await syncDbAdmin.drop({ blocking: false }); }, DevOpsAPIResponseError); } diff --git a/tests/integration/client/data-api-client.test.ts b/tests/integration/client/data-api-client.test.ts index 9c9b79b1..ee777581 100644 --- a/tests/integration/client/data-api-client.test.ts +++ b/tests/integration/client/data-api-client.test.ts @@ -28,10 +28,11 @@ import { } from '@/tests/testlib'; import { DataAPIResponseError, DataAPITimeoutError, UUID } from '@/src/documents'; import { DEFAULT_KEYSPACE } from '@/src/lib/api'; -import { DEFAULT_DATA_API_PATHS, DEFAULT_TIMEOUT } from '@/src/lib/api/constants'; +import { DEFAULT_DATA_API_PATHS } from '@/src/lib/api/constants'; import { before } from 'mocha'; +import { Timeouts } from '@/src/lib/api/timeouts'; -describe('integration.client.documents-client', () => { +describe('integration.client.data-api-client', () => { parallel('db', () => { it('properly connects to a db by endpoint', async () => { const client = new DataAPIClient(TEST_APPLICATION_TOKEN, { environment: ENVIRONMENT }); @@ -142,7 +143,7 @@ describe('integration.client.documents-client', () => { }); await collection1.insertOne({ name: 'Chthonic' }); - await collection2.deleteOne({ name: 'Chthonic' }, { maxTimeMS: 10000 }); + await collection2.deleteOne({ name: 'Chthonic' }, { timeout: 10000 }); assert.ok(startedEvents[0] instanceof CommandStartedEvent); assert.ok(succeededEvents[0] instanceof CommandSucceededEvent); @@ -169,9 +170,9 @@ describe('integration.client.documents-client', () => { assert.strictEqual(startedEvents[1].url, `${TEST_APPLICATION_URI}/${DEFAULT_DATA_API_PATHS[ENVIRONMENT]}/${OTHER_KEYSPACE}/${DEFAULT_COLLECTION_NAME}`); assert.strictEqual(succeededEvents[1].url, `${TEST_APPLICATION_URI}/${DEFAULT_DATA_API_PATHS[ENVIRONMENT]}/${OTHER_KEYSPACE}/${DEFAULT_COLLECTION_NAME}`); - assert.strictEqual(startedEvents[0].timeout, DEFAULT_TIMEOUT); + assert.deepStrictEqual(startedEvents[0].timeout, { generalMethodTimeout: Timeouts.Default.generalMethodTimeout, requestTimeout: Timeouts.Default.requestTimeout }); assert.ok(succeededEvents[0].duration > 0); - assert.strictEqual(startedEvents[1].timeout, 10000); + assert.deepStrictEqual(startedEvents[1].timeout, { requestTimeout: 10000, generalMethodTimeout: 10000 }); assert.ok(succeededEvents[1].duration > 0); assert.deepStrictEqual(startedEvents[0].command, { insertOne: { document: { name: 'Chthonic' } } }); @@ -233,7 +234,7 @@ describe('integration.client.documents-client', () => { assert.strictEqual(startedEvent.url, `${TEST_APPLICATION_URI}/${DEFAULT_DATA_API_PATHS[ENVIRONMENT]}/${DEFAULT_KEYSPACE}/${DEFAULT_COLLECTION_NAME}`); assert.strictEqual(failedEvent.url, `${TEST_APPLICATION_URI}/${DEFAULT_DATA_API_PATHS[ENVIRONMENT]}/${DEFAULT_KEYSPACE}/${DEFAULT_COLLECTION_NAME}`); - assert.strictEqual(startedEvent.timeout, DEFAULT_TIMEOUT); + assert.deepStrictEqual(startedEvent.timeout, { generalMethodTimeout: Timeouts.Default.generalMethodTimeout, requestTimeout: Timeouts.Default.requestTimeout }); assert.ok(failedEvent.duration > 0); assert.deepStrictEqual(startedEvent.command, { insertOne: { document: { _id: 0, name: 'Oasis' } } }); @@ -269,7 +270,7 @@ describe('integration.client.documents-client', () => { failedEvent = event; }); - await assert.rejects(() => collection.insertOne({ name: 'Xandria' }, { maxTimeMS: 1 })); + await assert.rejects(() => collection.insertOne({ name: 'Xandria' }, { timeout: 1 })); assert.ok(startedEvent instanceof CommandStartedEvent); assert.ok(failedEvent instanceof CommandFailedEvent); @@ -286,14 +287,14 @@ describe('integration.client.documents-client', () => { assert.strictEqual(startedEvent.url, `${TEST_APPLICATION_URI}/${DEFAULT_DATA_API_PATHS[ENVIRONMENT]}/${DEFAULT_KEYSPACE}/${DEFAULT_COLLECTION_NAME}`); assert.strictEqual(failedEvent.url, `${TEST_APPLICATION_URI}/${DEFAULT_DATA_API_PATHS[ENVIRONMENT]}/${DEFAULT_KEYSPACE}/${DEFAULT_COLLECTION_NAME}`); - assert.strictEqual(startedEvent.timeout, 1); + assert.deepStrictEqual(startedEvent.timeout, { generalMethodTimeout: 1, requestTimeout: 1 }); assert.ok(failedEvent.duration > 0); assert.deepStrictEqual(startedEvent.command, { insertOne: { document: { name: 'Xandria' } } }); assert.deepStrictEqual(failedEvent.command, { insertOne: { document: { name: 'Xandria' } } }); assert.ok(failedEvent.error instanceof DataAPITimeoutError); - assert.strictEqual(failedEvent.error.timeout, 1); + assert.deepStrictEqual(failedEvent.error.timeout, { generalMethodTimeout: 1, requestTimeout: 1 }); assert.deepStrictEqual(stdout, []); assert.deepStrictEqual(stderr[0], startedEvent.formatted()); diff --git a/tests/integration/db/db.test.ts b/tests/integration/db/db.test.ts index f45893d8..8196f450 100644 --- a/tests/integration/db/db.test.ts +++ b/tests/integration/db/db.test.ts @@ -96,7 +96,7 @@ parallel('integration.db', { dropEphemeral: 'colls:after' }, ({ db }) => { .admin({ adminToken: 'tummy-token', astraEnv: 'dev' }) .db(); - const res = await db.createCollection('coll_8c', { indexing: { deny: ['*'] }, maxTimeMS: 60000 }); + const res = await db.createCollection('coll_8c', { indexing: { deny: ['*'] }, timeout: 60000 }); assert.ok(res); assert.strictEqual(res.name, 'coll_8c'); assert.strictEqual(res.keyspace, DEFAULT_KEYSPACE); diff --git a/tests/integration/documents/collections/insert-many.test.ts b/tests/integration/documents/collections/insert-many.test.ts index 2b333d8c..016a3563 100644 --- a/tests/integration/documents/collections/insert-many.test.ts +++ b/tests/integration/documents/collections/insert-many.test.ts @@ -16,6 +16,7 @@ import { DataAPIError, DataAPITimeoutError, DataAPIVector, CollectionInsertManyError, ObjectId, UUID } from '@/src/documents'; import { initCollectionWithFailingClient, it, parallel } from '@/tests/testlib'; import assert from 'assert'; +import { Timeouts } from '@/src/lib/api/timeouts'; parallel('integration.documents.collections.insert-many', { truncate: 'colls:before' }, ({ collection }) => { it('should insertMany documents', async () => { @@ -173,11 +174,11 @@ parallel('integration.documents.collections.insert-many', { truncate: 'colls:bef it('times out properly', async (key) => { try { const docs = Array.from({ length: 1000 }, () => ({ key })); - await collection.insertMany(docs, { ordered: true, maxTimeMS: 500, chunkSize: 10 }); + await collection.insertMany(docs, { ordered: true, timeout: 500, chunkSize: 10 }); assert.fail('Expected an error'); } catch (e) { assert.ok(e instanceof DataAPITimeoutError); - assert.strictEqual(e.timeout, 500); + assert.deepStrictEqual(e.timeout, { generalMethodTimeout: 500, requestTimeout: Timeouts.Default.requestTimeout }); const found = await collection.find({ key }).toArray(); assert.ok(found.length > 0); assert.ok(found.length < 1000); diff --git a/tests/integration/documents/collections/misc.test.ts b/tests/integration/documents/collections/misc.test.ts index 64ccadcf..0106ae43 100644 --- a/tests/integration/documents/collections/misc.test.ts +++ b/tests/integration/documents/collections/misc.test.ts @@ -21,10 +21,10 @@ parallel('integration.documents.collections.misc', ({ db }) => { const { db: newDb } = initTestObjects({ httpClient: 'default:http2' }); try { - await newDb.collection(DEFAULT_COLLECTION_NAME).insertOne({ username: 'test' }, { maxTimeMS: 10 }); + await newDb.collection(DEFAULT_COLLECTION_NAME).insertOne({ username: 'test' }, { timeout: 10 }); } catch (e) { assert.ok(e instanceof DataAPITimeoutError); - assert.strictEqual(e.message, 'Command timed out after 10ms'); + assert.strictEqual(e.message, 'Command timed out after 10ms (provided timed out)'); } }); @@ -32,10 +32,10 @@ parallel('integration.documents.collections.misc', ({ db }) => { const { db: newDb } = initTestObjects({ httpClient: 'default:http1' }); try { - await newDb.collection(DEFAULT_COLLECTION_NAME).insertOne({ username: 'test' }, { maxTimeMS: 10 }); + await newDb.collection(DEFAULT_COLLECTION_NAME).insertOne({ username: 'test' }, { timeout: 10 }); } catch (e) { assert.ok(e instanceof DataAPITimeoutError); - assert.strictEqual(e.message, 'Command timed out after 10ms'); + assert.strictEqual(e.message, 'Command timed out after 10ms (provided timed out)'); } }); diff --git a/tests/integration/documents/vectorize/vec-test-groups.ts b/tests/integration/documents/vectorize/vec-test-groups.ts index f2f68688..232b6111 100644 --- a/tests/integration/documents/vectorize/vec-test-groups.ts +++ b/tests/integration/documents/vectorize/vec-test-groups.ts @@ -110,7 +110,7 @@ class HeaderKMSTestGroup implements VectorizeTestGroup { }, }, embeddingApiKey: branch.header, - maxTimeMS: 0, + timeout: 0, }); private _useColl = (branch: FinalVectorizeTestBranch) => (db: Db) => Promise.resolve( diff --git a/tests/integration/lib/api/clients/data-api-http-client.test.ts b/tests/integration/lib/api/clients/data-api-http-client.test.ts index f1ed645e..d7ed3572 100644 --- a/tests/integration/lib/api/clients/data-api-http-client.test.ts +++ b/tests/integration/lib/api/clients/data-api-http-client.test.ts @@ -37,7 +37,9 @@ describe('integration.lib.api.clients.documents-http-client', ({ db }) => { it('should execute a db-level command', async () => { const resp = await httpClient.executeCommand({ findCollections: {}, - }, {}); + }, { + timeoutManager: httpClient.tm.single('generalMethodTimeout', {}), + }); assert.strictEqual(typeof resp.status?.collections.length, 'number'); }); @@ -45,6 +47,7 @@ describe('integration.lib.api.clients.documents-http-client', ({ db }) => { const resp = await httpClient.executeCommand({ findCollections: {}, }, { + timeoutManager: httpClient.tm.single('generalMethodTimeout', {}), keyspace: OTHER_KEYSPACE, }); assert.strictEqual(resp.status?.collections.length, 1); @@ -54,6 +57,7 @@ describe('integration.lib.api.clients.documents-http-client', ({ db }) => { const resp = await httpClient.executeCommand({ insertOne: { document: { name: 'John' } }, }, { + timeoutManager: httpClient.tm.single('generalMethodTimeout', {}), collection: DEFAULT_COLLECTION_NAME, }); assert.ok(resp.status?.insertedIds[0]); @@ -64,7 +68,9 @@ describe('integration.lib.api.clients.documents-http-client', ({ db }) => { const httpClient = client.db(TEST_APPLICATION_URI, { token: 'invalid-token' })._httpClient; try { - await httpClient.executeCommand({ findCollections: {} }, {}); + await httpClient.executeCommand({ findCollections: {} }, { + timeoutManager: httpClient.tm.single('generalMethodTimeout', {}), + }); assert.fail('Expected error'); } catch (e) { assert.ok(e instanceof DataAPIResponseError); @@ -79,7 +85,9 @@ describe('integration.lib.api.clients.documents-http-client', ({ db }) => { const httpClient = client.db(TEST_APPLICATION_URI + '/invalid_path')._httpClient; try { - await httpClient.executeCommand({ findCollections: {} }, {}); + await httpClient.executeCommand({ findCollections: {} }, { + timeoutManager: httpClient.tm.single('generalMethodTimeout', {}), + }); assert.fail('Expected error'); } catch (e) { assert.ok(e instanceof DataAPIHttpError); diff --git a/tests/integration/misc/quickstart.test.ts b/tests/integration/misc/quickstart.test.ts index 29732c27..c8ddbbd1 100644 --- a/tests/integration/misc/quickstart.test.ts +++ b/tests/integration/misc/quickstart.test.ts @@ -36,7 +36,7 @@ parallel('integration.misc.quickstart', { dropEphemeral: 'colls:after' }, () => const client = new DataAPIClient(TEST_APPLICATION_TOKEN, { environment: ENVIRONMENT }); const db = client.db(TEST_APPLICATION_URI, { keyspace: DEFAULT_KEYSPACE }); - const collection = await db.createCollection('vector_5_collection', { vector: { dimension: 5, metric: 'cosine' }, maxTimeMS: 60000 }); + const collection = await db.createCollection('vector_5_collection', { vector: { dimension: 5, metric: 'cosine' }, timeout: 60000 }); const ideas = [ { @@ -115,7 +115,7 @@ parallel('integration.misc.quickstart', { dropEphemeral: 'colls:after' }, () => const client = new DataAPIClient(TEST_APPLICATION_TOKEN, { environment: ENVIRONMENT }); const db = client.db(TEST_APPLICATION_URI, { keyspace: DEFAULT_KEYSPACE }); - const collection = await db.createCollection('my_collection', { defaultId: { type: 'uuidv7' }, maxTimeMS: 60000 }); + const collection = await db.createCollection('my_collection', { defaultId: { type: 'uuidv7' }, timeout: 60000 }); await collection.insertOne({ _id: new ObjectId("65fd9b52d7fabba03349d013"), name: 'John' }); diff --git a/tests/integration/misc/timeouts.test.ts b/tests/integration/misc/timeouts.test.ts index 2cb53148..544974bb 100644 --- a/tests/integration/misc/timeouts.test.ts +++ b/tests/integration/misc/timeouts.test.ts @@ -13,98 +13,100 @@ // limitations under the License. // noinspection DuplicatedCode -import { DEFAULT_KEYSPACE } from '@/src/lib/api'; -import { DevOpsAPITimeoutError } from '@/src/administration'; -import { DataAPIClient } from '@/src/client'; -import { - DEFAULT_COLLECTION_NAME, - describe, - ENVIRONMENT, - it, - parallel, - TEST_APPLICATION_TOKEN, - TEST_APPLICATION_URI, -} from '@/tests/testlib'; -import assert from 'assert'; -import { DataAPITimeoutError } from '@/src/documents'; -import { HttpMethods } from '@/src/lib/api/constants'; - -parallel('integration.misc.timeouts', ({ collection, dbAdmin }) => { - describe('in data-api', () => { - it('should timeout @ the http-client level', async () => { - const httpClient = collection._httpClient; - - await assert.rejects(async () => { - await httpClient.executeCommand({ findOne: { filter: {} } }, { maxTimeMS: 5 }); - }, DataAPITimeoutError); - }); - - it('should timeout @ the http-client level if timeout is negative', async () => { - const httpClient = collection._httpClient; - - await assert.rejects(async () => { - await httpClient.executeCommand({ findOne: { filter: {} } }, { maxTimeMS: -1 }); - }, DataAPITimeoutError); - }); - - it('should timeout based on DataAPIClient maxTimeMS', async () => { - const collection = new DataAPIClient(TEST_APPLICATION_TOKEN, { httpOptions: { maxTimeMS: 1 }, environment: ENVIRONMENT }) - .db(TEST_APPLICATION_URI, { keyspace: DEFAULT_KEYSPACE }) - .collection(DEFAULT_COLLECTION_NAME); - - await assert.rejects(async () => { - await collection.findOne({}); - }, DataAPITimeoutError); - }); - - it('should timeout based on collections maxTimeMS', async () => { - const collection = new DataAPIClient(TEST_APPLICATION_TOKEN, { httpOptions: { maxTimeMS: 30000 }, environment: ENVIRONMENT }) - .db(TEST_APPLICATION_URI, { keyspace: DEFAULT_KEYSPACE }) - .collection(DEFAULT_COLLECTION_NAME, { defaultMaxTimeMS: 1 }); - - await assert.rejects(async () => { - await collection.findOne({}); - }, DataAPITimeoutError); - }); - - it('should timeout based on operation maxTimeMS', async () => { - const collection = new DataAPIClient(TEST_APPLICATION_TOKEN, { httpOptions: { maxTimeMS: 30000 }, environment: ENVIRONMENT }) - .db(TEST_APPLICATION_URI, { keyspace: DEFAULT_KEYSPACE }) - .collection(DEFAULT_COLLECTION_NAME, { defaultMaxTimeMS: 30000 }); - - await assert.rejects(async () => { - await collection.findOne({}, { maxTimeMS: 1 }); - }, DataAPITimeoutError); - }); - }); - - describe('(ASTRA) in devops', () => { - it('should timeout @ the http-client level', async () => { - const httpClient = (dbAdmin)._httpClient; - - await assert.rejects(async () => { - await httpClient.request({ method: HttpMethods.Get, path: '/databases' }, { maxTimeMS: 5 }); - }, DevOpsAPITimeoutError); - }); - - it('should timeout based on DataAPIClient maxTimeMS', async () => { - const admin = new DataAPIClient(TEST_APPLICATION_TOKEN, { httpOptions: { maxTimeMS: 1 }, environment: ENVIRONMENT }) - .db(TEST_APPLICATION_URI) - .admin(); - - await assert.rejects(async () => { - await admin.listKeyspaces(); - }, DevOpsAPITimeoutError); - }); - - it('should timeout based on operation maxTimeMS', async () => { - const admin = new DataAPIClient(TEST_APPLICATION_TOKEN, { httpOptions: { maxTimeMS: 30000 }, environment: ENVIRONMENT }) - .db(TEST_APPLICATION_URI) - .admin(); - - await assert.rejects(async () => { - await admin.listKeyspaces({ maxTimeMS: 1 }); - }, DevOpsAPITimeoutError); - }); - }); -}); +// TODO +// import { DEFAULT_KEYSPACE } from '@/src/lib/api'; +// import { DevOpsAPITimeoutError } from '@/src/administration'; +// import { DataAPIClient } from '@/src/client'; +// import { +// DEFAULT_COLLECTION_NAME, +// describe, +// ENVIRONMENT, +// it, +// parallel, +// TEST_APPLICATION_TOKEN, +// TEST_APPLICATION_URI, +// } from '@/tests/testlib'; +// import assert from 'assert'; +// import { DataAPITimeoutError } from '@/src/documents'; +// import { HttpMethods } from '@/src/lib/api/constants'; +// +// +// parallel('integration.misc.timeouts', ({ collection, dbAdmin }) => { +// describe('in data-api', () => { +// it('should timeout @ the http-client level', async () => { +// const httpClient = collection._httpClient; +// +// await assert.rejects(async () => { +// await httpClient.executeCommand({ findOne: { filter: {} } }, { timeout: 5 }); +// }, DataAPITimeoutError); +// }); +// +// it('should timeout @ the http-client level if timeout is negative', async () => { +// const httpClient = collection._httpClient; +// +// await assert.rejects(async () => { +// await httpClient.executeCommand({ findOne: { filter: {} } }, { timeout: -1 }); +// }, DataAPITimeoutError); +// }); +// +// it('should timeout based on DataAPIClient timeout', async () => { +// const collection = new DataAPIClient(TEST_APPLICATION_TOKEN, { httpOptions: { timeout: 1 }, environment: ENVIRONMENT }) +// .db(TEST_APPLICATION_URI, { keyspace: DEFAULT_KEYSPACE }) +// .collection(DEFAULT_COLLECTION_NAME); +// +// await assert.rejects(async () => { +// await collection.findOne({}); +// }, DataAPITimeoutError); +// }); +// +// it('should timeout based on collections timeout', async () => { +// const collection = new DataAPIClient(TEST_APPLICATION_TOKEN, { httpOptions: { timeout: 30000 }, environment: ENVIRONMENT }) +// .db(TEST_APPLICATION_URI, { keyspace: DEFAULT_KEYSPACE }) +// .collection(DEFAULT_COLLECTION_NAME, { defaulttimeout: 1 }); +// +// await assert.rejects(async () => { +// await collection.findOne({}); +// }, DataAPITimeoutError); +// }); +// +// it('should timeout based on operation timeout', async () => { +// const collection = new DataAPIClient(TEST_APPLICATION_TOKEN, { httpOptions: { timeout: 30000 }, environment: ENVIRONMENT }) +// .db(TEST_APPLICATION_URI, { keyspace: DEFAULT_KEYSPACE }) +// .collection(DEFAULT_COLLECTION_NAME, { defaulttimeout: 30000 }); +// +// await assert.rejects(async () => { +// await collection.findOne({}, { timeout: 1 }); +// }, DataAPITimeoutError); +// }); +// }); +// +// describe('(ASTRA) in devops', () => { +// it('should timeout @ the http-client level', async () => { +// const httpClient = (dbAdmin)._httpClient; +// +// await assert.rejects(async () => { +// await httpClient.request({ method: HttpMethods.Get, path: '/databases' }, { timeout: 5 }); +// }, DevOpsAPITimeoutError); +// }); +// +// it('should timeout based on DataAPIClient timeout', async () => { +// const admin = new DataAPIClient(TEST_APPLICATION_TOKEN, { httpOptions: { timeout: 1 }, environment: ENVIRONMENT }) +// .db(TEST_APPLICATION_URI) +// .admin(); +// +// await assert.rejects(async () => { +// await admin.listKeyspaces(); +// }, DevOpsAPITimeoutError); +// }); +// +// it('should timeout based on operation timeout', async () => { +// const admin = new DataAPIClient(TEST_APPLICATION_TOKEN, { httpOptions: { timeout: 30000 }, environment: ENVIRONMENT }) +// .db(TEST_APPLICATION_URI) +// .admin(); +// +// await assert.rejects(async () => { +// await admin.listKeyspaces({ timeout: 1 }); +// }, DevOpsAPITimeoutError); +// }); +// }); +// }); diff --git a/tests/prelude.test.ts b/tests/prelude.test.ts index 365c9e31..8fd8da47 100644 --- a/tests/prelude.test.ts +++ b/tests/prelude.test.ts @@ -49,7 +49,7 @@ before(async () => { const allTables = await TEST_KEYSPACES .map(async (keyspace) => { - const colls = await db.listTables({ keyspace: keyspace, nameOnly: true }); + const colls = await db.listTables({ keyspace, nameOnly: true }); return [keyspace, colls] as const; }) .awaitAll(); @@ -73,6 +73,7 @@ before(async () => { ifNotExists: true, keyspace, }); + await table.createVectorIndex(`vector_idx_${keyspace}`, 'vector', { metric: 'dot_product', ifNotExists: true }); }) .awaitAll(); diff --git a/tests/testlib/fixtures.ts b/tests/testlib/fixtures.ts index 15444883..ad14aa82 100644 --- a/tests/testlib/fixtures.ts +++ b/tests/testlib/fixtures.ts @@ -85,7 +85,7 @@ export const initTestObjects = (opts?: TestObjectsOptions) => { const clientType = httpClient.split(':')[0]; const client = new DataAPIClient(TEST_APPLICATION_TOKEN, { - httpOptions: { preferHttp2, client: clientType, maxTimeMS: 60000 }, + httpOptions: { preferHttp2, client: clientType }, dbOptions: { keyspace: DEFAULT_KEYSPACE }, environment: env, logging, diff --git a/tests/testlib/test-fns/describe.ts b/tests/testlib/test-fns/describe.ts index 19b79aaf..303910fd 100644 --- a/tests/testlib/test-fns/describe.ts +++ b/tests/testlib/test-fns/describe.ts @@ -66,7 +66,6 @@ describe = function (name: string, optsOrFn: SuiteOptions | SuiteBlock, maybeFn? GLOBAL_FIXTURES.collection_.deleteMany({}), ]); } - console.log(opts); if (opts?.truncate?.startsWith('tables')) { await Promise.all([ GLOBAL_FIXTURES.table.deleteMany({}), diff --git a/tests/typing/sort.ts b/tests/typing/sort.ts index 07258b86..b75a952a 100644 --- a/tests/typing/sort.ts +++ b/tests/typing/sort.ts @@ -36,12 +36,13 @@ const test4: Sort = { 'num1': 1n, }; -const test5: Sort = { - // @ts-expect-error - Must be a number[] - $vector: '[0.23, 0.38, 0.27, 0.91, 0.21]', -}; - -const test6: Sort = { - // @ts-expect-error - Must be a string - $vectorize: [0.23, 0.38, 0.27, 0.91, 0.21], -}; +// TODO +// const test5: Sort = { +// // @ts-expect-error - Must be a number[] +// $vector: '[0.23, 0.38, 0.27, 0.91, 0.21]', +// }; +// +// const test6: Sort = { +// // @ts-expect-error - Must be a string +// $vectorize: [0.23, 0.38, 0.27, 0.91, 0.21], +// }; diff --git a/tests/unit/administration/admin.test.ts b/tests/unit/administration/admin.test.ts index c3b183f1..d18df544 100644 --- a/tests/unit/administration/admin.test.ts +++ b/tests/unit/administration/admin.test.ts @@ -19,11 +19,12 @@ import { DataAPIClient } from '@/src/client'; import { describe, it } from '@/tests/testlib'; import { DEFAULT_DEVOPS_API_ENDPOINTS } from '@/src/lib/api/constants'; import { InternalRootClientOpts } from '@/src/client/types/internal'; +import { Timeouts } from '@/src/lib/api/timeouts'; describe('unit.administration.admin', () => { const internalOps = (db?: Partial, devops?: Partial, preferredType = 'http2'): InternalRootClientOpts => ({ - dbOptions: { token: new StaticTokenProvider('old'), logging: undefined, ...db }, - adminOptions: { adminToken: new StaticTokenProvider('old-admin'), logging: undefined, ...devops }, + dbOptions: { token: new StaticTokenProvider('old'), logging: undefined, timeoutDefaults: Timeouts.Default, ...db }, + adminOptions: { adminToken: new StaticTokenProvider('old-admin'), logging: undefined, timeoutDefaults: Timeouts.Default, ...devops }, emitter: null!, fetchCtx: { preferredType } as any, userAgent: '', diff --git a/tests/unit/client/data-api-client.test.ts b/tests/unit/client/data-api-client.test.ts index 178d84d7..2949e4d1 100644 --- a/tests/unit/client/data-api-client.test.ts +++ b/tests/unit/client/data-api-client.test.ts @@ -22,7 +22,7 @@ import { describe, it, TEST_APPLICATION_URI } from '@/tests/testlib'; import assert from 'assert'; import { DataAPIEnvironments } from '@/src/lib/constants'; -describe('unit.client.documents-client', () => { +describe('unit.client.data-api-client', () => { it('should accept valid tokens', () => { assert.doesNotThrow(() => new DataAPIClient()); assert.doesNotThrow(() => new DataAPIClient('token')); @@ -104,10 +104,6 @@ describe('unit.client.documents-client', () => { }); it('validates options properly', () => { - assert.throws(() => new DataAPIClient('dummy-token', { - // @ts-expect-error - testing invalid input - httpOptions: { maxTimeMS: '3' }, - })); assert.throws(() => new DataAPIClient('dummy-token', { // @ts-expect-error - testing invalid input httpOptions: { preferHttp2: 3 }, diff --git a/tests/unit/db/db.test.ts b/tests/unit/db/db.test.ts index c1a0f220..da6dcc94 100644 --- a/tests/unit/db/db.test.ts +++ b/tests/unit/db/db.test.ts @@ -21,11 +21,12 @@ import { DEMO_APPLICATION_URI, describe, it, TEST_APPLICATION_URI } from '@/test import { DEFAULT_DATA_API_PATHS, DEFAULT_KEYSPACE } from '@/src/lib/api/constants'; import { buildAstraEndpoint } from '@/src/lib/utils'; import { InternalRootClientOpts } from '@/src/client/types/internal'; +import { Timeouts } from '@/src/lib/api/timeouts'; describe('unit.db', () => { const internalOps = (db?: Partial, devops?: Partial, preferredType = 'http2'): InternalRootClientOpts => ({ - dbOptions: { token: new StaticTokenProvider('old'), logging: undefined, ...db }, - adminOptions: { adminToken: new StaticTokenProvider('old-admin'), logging: undefined, ...devops }, + dbOptions: { token: new StaticTokenProvider('old'), logging: undefined, timeoutDefaults: Timeouts.Default, ...db }, + adminOptions: { adminToken: new StaticTokenProvider('old-admin'), logging: undefined, timeoutDefaults: Timeouts.Default, ...devops }, emitter: null!, fetchCtx: { preferredType } as any, userAgent: '', diff --git a/tests/unit/lib/api/timeout-manager.test.ts b/tests/unit/lib/api/timeout-manager.test.ts deleted file mode 100644 index 3aad617a..00000000 --- a/tests/unit/lib/api/timeout-manager.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright DataStax, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// noinspection DuplicatedCode - -import assert from 'assert'; -import { TimeoutManager } from '@/src/lib/api/timeout-managers'; -import { describe, it } from '@/tests/testlib'; - -describe('unit.lib.api.timeout-manager', () => { - it('works', async () => { - const timeoutManager = new TimeoutManager(1000, () => new Error('timeout')); - assert.strictEqual(timeoutManager.msRemaining(), 1000); - await new Promise((resolve) => setTimeout(resolve, 500)); - assert.ok(timeoutManager.msRemaining() < 510); - assert.ok(timeoutManager.msRemaining() > 480); - await new Promise((resolve) => setTimeout(resolve, 500)); - assert.ok(timeoutManager.msRemaining() < 20); - assert.ok(timeoutManager.msRemaining() > -40); - }); -}); diff --git a/tests/unit/lib/api/timeouts.test.ts b/tests/unit/lib/api/timeouts.test.ts new file mode 100644 index 00000000..72841c44 --- /dev/null +++ b/tests/unit/lib/api/timeouts.test.ts @@ -0,0 +1,260 @@ +// Copyright DataStax, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// noinspection DuplicatedCode + +import assert from 'assert'; +import { describe, it, parallel } from '@/tests/testlib'; +import { TimedOutTypes, TimeoutManager, Timeouts } from '@/src/lib/api/timeouts'; +import { HTTPRequestInfo } from '@/src/lib/api/clients'; + +describe('unit.lib.api.timeouts', () => { + class TimeoutError extends Error { + constructor(public readonly info: HTTPRequestInfo, public readonly timeoutType: TimedOutTypes) { + super(Timeouts.fmtTimeoutMsg(info.timeoutManager, timeoutType)); + } + } + + const timeouts = new Timeouts((info, timeoutType) => new TimeoutError(info, timeoutType), Timeouts.Default); + const info = (timeoutManager: TimeoutManager) => ({ timeoutManager }) as HTTPRequestInfo; + + describe('single', () => { + it('works w/ no override', () => { + const tm = timeouts.single('generalMethodTimeout', null); + const [timeout, mkError] = tm.advance(info(tm)); + + assert.strictEqual(timeout, Timeouts.Default.requestTimeout); + + const e = mkError(); + assert.ok(e instanceof TimeoutError); + assert.deepStrictEqual(e.info, info(tm)); + assert.strictEqual(e.timeoutType, 'requestTimeout'); + assert.strictEqual(e.message, `Command timed out after ${Timeouts.Default.requestTimeout}ms (requestTimeout timed out)`); + + assert.deepStrictEqual(tm.initial(), { + generalMethodTimeout: Timeouts.Default.generalMethodTimeout, + requestTimeout: Timeouts.Default.requestTimeout, + }); + }); + + it('works w/ override number', () => { + const tm = timeouts.single('generalMethodTimeout', { timeout: 100 }); + const [timeout, mkError] = tm.advance(info(tm)); + + assert.strictEqual(timeout, 100); + + const e = mkError(); + assert.ok(e instanceof TimeoutError); + assert.deepStrictEqual(e.info, info(tm)); + assert.strictEqual(e.timeoutType, 'provided'); + assert.strictEqual(e.message, 'Command timed out after 100ms (The timeout provided via `{ timeout: 100 }` timed out)'); + + assert.deepStrictEqual(tm.initial(), { + generalMethodTimeout: 100, + requestTimeout: 100, + }); + }); + + it('works w/ partial override object', () => { + const tm = timeouts.single('databaseAdminTimeout', { timeout: { generalMethodTimeout: 100, databaseAdminTimeout: 50 } }); + const [timeout, mkError] = tm.advance(info(tm)); + + assert.strictEqual(timeout, 50); + + const e = mkError(); + assert.ok(e instanceof TimeoutError); + assert.deepStrictEqual(e.info, info(tm)); + assert.strictEqual(e.timeoutType, 'databaseAdminTimeout'); + assert.strictEqual(e.message, 'Command timed out after 50ms (databaseAdminTimeout timed out)'); + + assert.deepStrictEqual(tm.initial(), { + requestTimeout: Timeouts.Default.requestTimeout, + databaseAdminTimeout: 50, + }); + }); + + it('works w/ full override object', () => { + const tm = timeouts.single('databaseAdminTimeout', { timeout: { generalMethodTimeout: 100, requestTimeout: 10, databaseAdminTimeout: 50 } }); + const [timeout, mkError] = tm.advance(info(tm)); + + assert.strictEqual(timeout, 10); + + const e = mkError(); + assert.ok(e instanceof TimeoutError); + assert.deepStrictEqual(e.info, info(tm)); + assert.strictEqual(e.timeoutType, 'requestTimeout'); + assert.strictEqual(e.message, 'Command timed out after 10ms (requestTimeout timed out)'); + + assert.deepStrictEqual(tm.initial(), { + databaseAdminTimeout: 50, + requestTimeout: 10, + }); + }); + + it('works w/ uniform full override object', () => { + const tm = timeouts.single('keyspaceAdminTimeout', { timeout: { keyspaceAdminTimeout: Timeouts.Default.requestTimeout } }); + const [timeout, mkError] = tm.advance(info(tm)); + + assert.strictEqual(timeout, Timeouts.Default.requestTimeout); + + const e = mkError(); + assert.ok(e instanceof TimeoutError); + assert.deepStrictEqual(e.info, info(tm)); + assert.deepStrictEqual(e.timeoutType, ['requestTimeout', 'keyspaceAdminTimeout']); + assert.strictEqual(e.message, `Command timed out after ${Timeouts.Default.requestTimeout}ms (requestTimeout and keyspaceAdminTimeout simultaneously timed out)`); + + assert.deepStrictEqual(tm.initial(), { + keyspaceAdminTimeout: Timeouts.Default.requestTimeout, + requestTimeout: Timeouts.Default.requestTimeout, + }); + }); + }); + + parallel('multipart', () => { + it('works w/ override number', async () => { + const tm = timeouts.multipart('generalMethodTimeout', { timeout: 10001 }); + let [timeout, mkError] = tm.advance(info(tm)); + assert.strictEqual(timeout, Timeouts.Default.requestTimeout); + + const e = mkError(); + assert.ok(e instanceof TimeoutError); + assert.deepStrictEqual(e.info, info(tm)); + assert.strictEqual(e.timeoutType, 'requestTimeout'); + assert.strictEqual(e.message, `Command timed out after ${Timeouts.Default.requestTimeout}ms (requestTimeout timed out)`); + + assert.deepStrictEqual(tm.initial(), { + requestTimeout: Timeouts.Default.requestTimeout, + generalMethodTimeout: 10001, + }); + + await new Promise(resolve => setTimeout(resolve, 5001)); + [timeout] = tm.advance(info(tm)); + assert.ok(timeout <= 5000.5); + + await new Promise(resolve => setTimeout(resolve, 5001)); + [timeout, mkError] = tm.advance(info(tm)); + assert.ok(timeout <= 0); + + const e2 = mkError(); + assert.ok(e2 instanceof TimeoutError); + assert.deepStrictEqual(e2.info, info(tm)); + assert.strictEqual(e2.timeoutType, 'generalMethodTimeout'); + assert.strictEqual(e2.message, 'Command timed out after 10001ms (generalMethodTimeout timed out)'); + }); + + it('works w/ partial override object', async () => { + const tm = timeouts.multipart('tableAdminTimeout', { timeout: { requestTimeout: 10 } }); + let [timeout, mkError] = tm.advance(info(tm)); + assert.strictEqual(timeout, 10); + + const e = mkError(); + assert.ok(e instanceof TimeoutError); + assert.deepStrictEqual(e.info, info(tm)); + assert.strictEqual(e.timeoutType, 'requestTimeout'); + assert.strictEqual(e.message, 'Command timed out after 10ms (requestTimeout timed out)'); + + assert.deepStrictEqual(tm.initial(), { + tableAdminTimeout: Timeouts.Default.tableAdminTimeout, + requestTimeout: 10, + }); + + await new Promise(resolve => setTimeout(resolve, 5)); + [timeout] = tm.advance(info(tm)); + assert.strictEqual(timeout, 10); + + await new Promise(resolve => setTimeout(resolve, 1000)); + [timeout, mkError] = tm.advance(info(tm)); + assert.strictEqual(timeout, 10); + + const e2 = mkError(); + assert.ok(e2 instanceof TimeoutError); + assert.deepStrictEqual(e2.info, info(tm)); + assert.strictEqual(e2.timeoutType, 'requestTimeout'); + assert.strictEqual(e2.message, 'Command timed out after 10ms (requestTimeout timed out)'); + }); + + it('works w/ full override object', async () => { + const tm = timeouts.multipart('tableAdminTimeout', { timeout: { requestTimeout: 10, tableAdminTimeout: 100 } }); + let [timeout, mkError] = tm.advance(info(tm)); + assert.strictEqual(timeout, 10); + + const e = mkError(); + assert.ok(e instanceof TimeoutError); + assert.deepStrictEqual(e.info, info(tm)); + assert.strictEqual(e.timeoutType, 'requestTimeout'); + assert.strictEqual(e.message, 'Command timed out after 10ms (requestTimeout timed out)'); + + assert.deepStrictEqual(tm.initial(), { + tableAdminTimeout: 100, + requestTimeout: 10, + }); + + await new Promise(resolve => setTimeout(resolve, 5)); + [timeout, mkError] = tm.advance(info(tm)); + assert.strictEqual(timeout, 10); + + const e2 = mkError(); + assert.ok(e2 instanceof TimeoutError); + assert.deepStrictEqual(e2.info, info(tm)); + assert.strictEqual(e2.timeoutType, 'requestTimeout'); + assert.strictEqual(e2.message, 'Command timed out after 10ms (requestTimeout timed out)'); + + await new Promise(resolve => setTimeout(resolve, 100)); + [timeout, mkError] = tm.advance(info(tm)); + assert.ok(timeout <= 0); + + const e3 = mkError(); + assert.ok(e3 instanceof TimeoutError); + assert.deepStrictEqual(e3.info, info(tm)); + assert.strictEqual(e3.timeoutType, 'tableAdminTimeout'); + assert.strictEqual(e3.message, 'Command timed out after 100ms (tableAdminTimeout timed out)'); + }); + + it('works w/ uniform full override object', async () => { + const tm = timeouts.multipart('keyspaceAdminTimeout', { timeout: { requestTimeout: 100, keyspaceAdminTimeout: 100 } }); + let [timeout, mkError] = tm.advance(info(tm)); + assert.strictEqual(timeout, 100); + + const e = mkError(); + assert.ok(e instanceof TimeoutError); + assert.deepStrictEqual(e.info, info(tm)); + assert.deepStrictEqual(e.timeoutType, ['requestTimeout', 'keyspaceAdminTimeout']); + assert.strictEqual(e.message, 'Command timed out after 100ms (requestTimeout and keyspaceAdminTimeout simultaneously timed out)'); + + assert.deepStrictEqual(tm.initial(), { + keyspaceAdminTimeout: 100, + requestTimeout: 100, + }); + + await new Promise(resolve => setTimeout(resolve, 51)); + [timeout, mkError] = tm.advance(info(tm)); + assert.ok(timeout <= 50); + + const e2 = mkError(); + assert.ok(e2 instanceof TimeoutError); + assert.deepStrictEqual(e2.info, info(tm)); + assert.strictEqual(e2.timeoutType, 'keyspaceAdminTimeout'); + assert.strictEqual(e2.message, 'Command timed out after 100ms (keyspaceAdminTimeout timed out)'); + + await new Promise(resolve => setTimeout(resolve, 51)); + [timeout, mkError] = tm.advance(info(tm)); + assert.ok(timeout <= 0); + + const e3 = mkError(); + assert.ok(e3 instanceof TimeoutError); + assert.deepStrictEqual(e3.info, info(tm)); + assert.strictEqual(e3.timeoutType, 'keyspaceAdminTimeout'); + assert.strictEqual(e3.message, 'Command timed out after 100ms (keyspaceAdminTimeout timed out)'); + }); + }); +}); diff --git a/tests/unit/lib/logging/logger.test.ts b/tests/unit/lib/logging/logger.test.ts index 6fd6e9f3..7dd19d67 100644 --- a/tests/unit/lib/logging/logger.test.ts +++ b/tests/unit/lib/logging/logger.test.ts @@ -23,7 +23,6 @@ import { beforeEach } from 'mocha'; import TypedEmitter from 'typed-emitter'; import { CommandStartedEvent } from '@/src/documents'; import { AdminCommandStartedEvent } from '@/src/administration'; -import { TimeoutManager } from '@/src/lib/api/timeout-managers'; describe('unit.lib.logging.logger', () => { describe('parseConfig', () => { @@ -137,10 +136,10 @@ describe('unit.lib.logging.logger', () => { it('should handle default logging behavior', () => { const logger = new Logger(EventLoggingDefaults, emitter, console); - logger.commandStarted?.({ timeoutManager: new TimeoutManager(0, null!), command: {} } as any); + logger.commandStarted?.({ timeoutManager: { initial: () => ({}) }, command: {} } as any); assert.strictEqual(events.at(-1)?.[0], 'commandStarted'); assert.ok(events.at(-1)?.[1] instanceof CommandStartedEvent); - logger.adminCommandStarted?.({} as any, true, 1000); + logger.adminCommandStarted?.({} as any, true, {}); assert.strictEqual(events.at(-1)?.[0], 'adminCommandStarted'); assert.ok(events.at(-1)?.[1] instanceof AdminCommandStartedEvent); assert.strictEqual(stdout.at(-1), (events.at(-1)?.[1]).formatted()); From e69ab8cf5fc95019c3dec5c6d78ee76a9cd339be Mon Sep 17 00:00:00 2001 From: toptobes Date: Sun, 17 Nov 2024 22:21:05 +0530 Subject: [PATCH 03/10] more tests & tweaks --- src/documents/cursor.ts | 6 +----- src/lib/api/timeouts.ts | 2 +- tests/integration/documents/collections/misc.test.ts | 8 ++++---- tests/unit/lib/api/timeouts.test.ts | 2 +- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/documents/cursor.ts b/src/documents/cursor.ts index 779249a5..a7c8589b 100644 --- a/src/documents/cursor.ts +++ b/src/documents/cursor.ts @@ -21,7 +21,6 @@ import { $CustomInspect } from '@/src/lib/constants'; import type { DataAPISerDes } from '@/src/lib/api/ser-des'; import { DataAPIError } from '@/src/documents/errors'; import type { Table } from '@/src/documents/tables'; -import { TimeoutManager } from '@/src/lib/api/timeouts'; export class CursorError extends DataAPIError { public readonly cursor: FindCursor; @@ -121,8 +120,6 @@ export abstract class FindCursor { readonly #filter: [Filter, boolean]; readonly #mapping?: (doc: any) => T; - readonly #timeoutManager: TimeoutManager; - #buffer: TRaw[] = []; #nextPageState?: string | null; #state = 'idle' as CursorStatus; @@ -140,7 +137,6 @@ export abstract class FindCursor { this.#filter = filter; this.#options = options ?? {}; this.#mapping = mapping; - this.#timeoutManager = parent._httpClient.tm.multipart('generalMethodTimeout', options); Object.defineProperty(this, $CustomInspect, { value: () => `FindCursor(source="${this.#parent.keyspace}.${this.#parent.name}",state="${this.#state}",consumed=${this.#consumed},buffered=${this.#buffer.length})`, @@ -623,7 +619,7 @@ export abstract class FindCursor { }; const raw = await this.#parent._httpClient.executeCommand(command, { - timeoutManager: this.#timeoutManager, + timeoutManager: this.#parent._httpClient.tm.multipart('generalMethodTimeout', this.#options), bigNumsPresent: this.#filter[1], }); diff --git a/src/lib/api/timeouts.ts b/src/lib/api/timeouts.ts index 8538d0e4..1708ea0c 100644 --- a/src/lib/api/timeouts.ts +++ b/src/lib/api/timeouts.ts @@ -160,7 +160,7 @@ export class Timeouts { const types = (timeoutTypes === 'provided') - ? `The timeout provided via \`{ timeout: ${timeout} }\` timed out` : + ? `The timeout provided via \`{ timeout: }\` timed out` : (Array.isArray(timeoutTypes)) ? timeoutTypes.join(' and ') + ' simultaneously timed out' : `${timeoutTypes} timed out`; diff --git a/tests/integration/documents/collections/misc.test.ts b/tests/integration/documents/collections/misc.test.ts index 0106ae43..6fb3bc7a 100644 --- a/tests/integration/documents/collections/misc.test.ts +++ b/tests/integration/documents/collections/misc.test.ts @@ -24,7 +24,7 @@ parallel('integration.documents.collections.misc', ({ db }) => { await newDb.collection(DEFAULT_COLLECTION_NAME).insertOne({ username: 'test' }, { timeout: 10 }); } catch (e) { assert.ok(e instanceof DataAPITimeoutError); - assert.strictEqual(e.message, 'Command timed out after 10ms (provided timed out)'); + assert.strictEqual(e.message, 'Command timed out after 10ms (The timeout provided via `{ timeout: }` timed out)'); } }); @@ -32,10 +32,10 @@ parallel('integration.documents.collections.misc', ({ db }) => { const { db: newDb } = initTestObjects({ httpClient: 'default:http1' }); try { - await newDb.collection(DEFAULT_COLLECTION_NAME).insertOne({ username: 'test' }, { timeout: 10 }); + await newDb.collection(DEFAULT_COLLECTION_NAME).insertOne({ username: 'test' }, { timeout: 11 }); } catch (e) { assert.ok(e instanceof DataAPITimeoutError); - assert.strictEqual(e.message, 'Command timed out after 10ms (provided timed out)'); + assert.strictEqual(e.message, 'Command timed out after 11ms (The timeout provided via `{ timeout: }` timed out)'); } }); @@ -49,7 +49,7 @@ parallel('integration.documents.collections.misc', ({ db }) => { } }); - it('Error is thrown when doing .options() on non-existent collections', async () => { + it('error is thrown when doing .options() on non-existent collections', async () => { const collection = db.collection('non_existent_collection'); try { diff --git a/tests/unit/lib/api/timeouts.test.ts b/tests/unit/lib/api/timeouts.test.ts index 72841c44..867c5365 100644 --- a/tests/unit/lib/api/timeouts.test.ts +++ b/tests/unit/lib/api/timeouts.test.ts @@ -57,7 +57,7 @@ describe('unit.lib.api.timeouts', () => { assert.ok(e instanceof TimeoutError); assert.deepStrictEqual(e.info, info(tm)); assert.strictEqual(e.timeoutType, 'provided'); - assert.strictEqual(e.message, 'Command timed out after 100ms (The timeout provided via `{ timeout: 100 }` timed out)'); + assert.strictEqual(e.message, 'Command timed out after 100ms (The timeout provided via `{ timeout: }` timed out)'); assert.deepStrictEqual(tm.initial(), { generalMethodTimeout: 100, From 7676c71252d59875173146deb5d5993fc606d6b5 Mon Sep 17 00:00:00 2001 From: toptobes Date: Sun, 17 Nov 2024 22:52:54 +0530 Subject: [PATCH 04/10] more tests & tweaks --- src/administration/astra-admin.ts | 8 +- src/administration/astra-db-admin.ts | 12 +-- src/administration/data-api-db-admin.ts | 8 +- src/db/db.ts | 16 ++-- src/documents/commands/command-impls.ts | 26 +++--- src/documents/cursor.ts | 2 +- src/documents/datatypes/blob.ts | 2 +- src/documents/datatypes/vector.ts | 2 +- src/documents/tables/table.ts | 6 +- src/documents/types/common.ts | 2 +- src/documents/utils.ts | 10 +-- src/lib/api/timeouts.ts | 60 ++++++------- .../administration/lifecycle.test.ts | 8 +- .../client/data-api-client.test.ts | 10 +-- .../documents/collections/insert-many.test.ts | 2 +- .../api/clients/data-api-http-client.test.ts | 10 +-- tests/unit/lib/api/timeouts.test.ts | 86 +++++++++---------- 17 files changed, 132 insertions(+), 138 deletions(-) diff --git a/src/administration/astra-admin.ts b/src/administration/astra-admin.ts index 7a8c8202..8733660f 100644 --- a/src/administration/astra-admin.ts +++ b/src/administration/astra-admin.ts @@ -287,7 +287,7 @@ export class AstraAdmin { * @returns A promise that resolves to the complete database information. */ public async dbInfo(id: string, options?: WithTimeout): Promise { - const tm = this.#httpClient.tm.single('databaseAdminTimeout', options); + const tm = this.#httpClient.tm.single('databaseAdminTimeoutMs', options); const resp = await this.#httpClient.request({ method: HttpMethods.Get, @@ -342,7 +342,7 @@ export class AstraAdmin { params['starting_after'] = String(options.skip); } - const tm = this.#httpClient.tm.single('databaseAdminTimeout', options); + const tm = this.#httpClient.tm.single('databaseAdminTimeoutMs', options); const resp = await this.#httpClient.request({ method: HttpMethods.Get, @@ -414,7 +414,7 @@ export class AstraAdmin { ...config, }; - const tm = this.#httpClient.tm.multipart('databaseAdminTimeout', options); + const tm = this.#httpClient.tm.multipart('databaseAdminTimeoutMs', options); const resp = await this.#httpClient.requestLongRunning({ method: HttpMethods.Post, @@ -462,7 +462,7 @@ export class AstraAdmin { public async dropDatabase(db: Db | string, options?: AstraAdminBlockingOptions): Promise { const id = typeof db === 'string' ? db : db.id; - const tm = this.#httpClient.tm.multipart('databaseAdminTimeout', options); + const tm = this.#httpClient.tm.multipart('databaseAdminTimeoutMs', options); await this.#httpClient.requestLongRunning({ method: HttpMethods.Post, diff --git a/src/administration/astra-db-admin.ts b/src/administration/astra-db-admin.ts index e52e5d2c..5794bb53 100644 --- a/src/administration/astra-db-admin.ts +++ b/src/administration/astra-db-admin.ts @@ -156,7 +156,7 @@ export class AstraDbAdmin extends DbAdmin { */ public override async findEmbeddingProviders(options?: WithTimeout): Promise { const resp = await this.#db._httpClient.executeCommand({ findEmbeddingProviders: {} }, { - timeoutManager: this.#httpClient.tm.single('databaseAdminTimeout', options), + timeoutManager: this.#httpClient.tm.single('databaseAdminTimeoutMs', options), keyspace: null, }); return resp.status as FindEmbeddingProvidersResult; @@ -178,7 +178,7 @@ export class AstraDbAdmin extends DbAdmin { * @returns A promise that resolves to the complete database information. */ public async info(options?: WithTimeout): Promise { - const tm = this.#httpClient.tm.single('databaseAdminTimeout', options); + const tm = this.#httpClient.tm.single('databaseAdminTimeoutMs', options); return this.#info(options, tm); } @@ -199,7 +199,7 @@ export class AstraDbAdmin extends DbAdmin { * @returns A promise that resolves to list of all the keyspaces in the database. */ public override async listKeyspaces(options?: WithTimeout): Promise { - const tm = this.#httpClient.tm.single('keyspaceAdminTimeout', options); + const tm = this.#httpClient.tm.single('keyspaceAdminTimeoutMs', options); return this.#info(options, tm).then(i => i.keyspaces); } @@ -238,7 +238,7 @@ export class AstraDbAdmin extends DbAdmin { this.#db.useKeyspace(keyspace); } - const tm = this.#httpClient.tm.multipart('keyspaceAdminTimeout', options); + const tm = this.#httpClient.tm.multipart('keyspaceAdminTimeoutMs', options); await this.#httpClient.requestLongRunning({ method: HttpMethods.Post, @@ -285,7 +285,7 @@ export class AstraDbAdmin extends DbAdmin { * @returns A promise that resolves when the operation completes. */ public override async dropKeyspace(keyspace: string, options?: AstraAdminBlockingOptions): Promise { - const tm = this.#httpClient.tm.multipart('keyspaceAdminTimeout', options); + const tm = this.#httpClient.tm.multipart('keyspaceAdminTimeoutMs', options); await this.#httpClient.requestLongRunning({ method: HttpMethods.Delete, @@ -322,7 +322,7 @@ export class AstraDbAdmin extends DbAdmin { * @remarks Use with caution. Use a surge protector. Don't say I didn't warn you. */ public async drop(options?: AstraAdminBlockingOptions): Promise { - const tm = this.#httpClient.tm.multipart('databaseAdminTimeout', options); + const tm = this.#httpClient.tm.multipart('databaseAdminTimeoutMs', options); await this.#httpClient.requestLongRunning({ method: HttpMethods.Post, diff --git a/src/administration/data-api-db-admin.ts b/src/administration/data-api-db-admin.ts index a5825b1c..44b7e9e4 100644 --- a/src/administration/data-api-db-admin.ts +++ b/src/administration/data-api-db-admin.ts @@ -114,7 +114,7 @@ export class DataAPIDbAdmin extends DbAdmin { */ public override async findEmbeddingProviders(options?: WithTimeout): Promise { const resp = await this.#httpClient.executeCommand({ findEmbeddingProviders: {} }, { - timeoutManager: this.#httpClient.tm.single('databaseAdminTimeout', options), + timeoutManager: this.#httpClient.tm.single('databaseAdminTimeoutMs', options), keyspace: null, }); return resp.status as FindEmbeddingProvidersResult; @@ -138,7 +138,7 @@ export class DataAPIDbAdmin extends DbAdmin { */ public override async listKeyspaces(options?: WithTimeout): Promise { const resp = await this.#httpClient.executeCommand({ findKeyspaces: {} }, { - timeoutManager: this.#httpClient.tm.single('keyspaceAdminTimeout', options), + timeoutManager: this.#httpClient.tm.single('keyspaceAdminTimeoutMs', options), keyspace: null, }); return resp.status!.keyspaces; @@ -185,7 +185,7 @@ export class DataAPIDbAdmin extends DbAdmin { }; await this.#httpClient.executeCommand({ createKeyspace: { name: keyspace, options: { replication } } }, { - timeoutManager: this.#httpClient.tm.single('keyspaceAdminTimeout', options), + timeoutManager: this.#httpClient.tm.single('keyspaceAdminTimeoutMs', options), keyspace: null, }); } @@ -213,7 +213,7 @@ export class DataAPIDbAdmin extends DbAdmin { */ public override async dropKeyspace(keyspace: string, options?: WithTimeout): Promise { await this.#httpClient.executeCommand({ dropKeyspace: { name: keyspace } }, { - timeoutManager: this.#httpClient.tm.single('keyspaceAdminTimeout', options), + timeoutManager: this.#httpClient.tm.single('keyspaceAdminTimeoutMs', options), keyspace: null, }); } diff --git a/src/db/db.ts b/src/db/db.ts index 5336fdbf..04d0f820 100644 --- a/src/db/db.ts +++ b/src/db/db.ts @@ -773,7 +773,7 @@ export class Db { }; await this.#httpClient.executeCommand(command, { - timeoutManager: this.#httpClient.tm.custom({}, () => [720000, 'collectionAdminTimeout']), + timeoutManager: this.#httpClient.tm.custom({}, () => [720000, 'collectionAdminTimeoutMs']), keyspace: options?.keyspace, }); @@ -958,7 +958,7 @@ export class Db { }; await this.#httpClient.executeCommand(command, { - timeoutManager: this.#httpClient.tm.single('tableAdminTimeout', options), + timeoutManager: this.#httpClient.tm.single('tableAdminTimeoutMs', options), keyspace: options?.keyspace, }); @@ -993,7 +993,7 @@ export class Db { */ public async dropCollection(name: string, options?: DropCollectionOptions): Promise { await this.#httpClient.executeCommand({ deleteCollection: { name } }, { - timeoutManager: this.#httpClient.tm.single('collectionAdminTimeout', options), + timeoutManager: this.#httpClient.tm.single('collectionAdminTimeoutMs', options), keyspace: options?.keyspace, }); } @@ -1026,14 +1026,14 @@ export class Db { */ public async dropTable(name: string, options?: DropTableOptions): Promise { await this.#httpClient.executeCommand({ dropTable: { name } }, { - timeoutManager: this.#httpClient.tm.single('tableAdminTimeout', options), + timeoutManager: this.#httpClient.tm.single('tableAdminTimeoutMs', options), keyspace: options?.keyspace, }); } public async dropTableIndex(name: string, options?: WithTimeout): Promise { await this.#httpClient.executeCommand({ dropIndex: { name } }, { - timeoutManager: this.#httpClient.tm.single('tableAdminTimeout', options), + timeoutManager: this.#httpClient.tm.single('tableAdminTimeoutMs', options), }); } @@ -1092,7 +1092,7 @@ export class Db { }; const resp = await this.#httpClient.executeCommand(command, { - timeoutManager: this.#httpClient.tm.single('collectionAdminTimeout', options), + timeoutManager: this.#httpClient.tm.single('collectionAdminTimeoutMs', options), keyspace: options?.keyspace, }); return resp.status!.collections; @@ -1153,7 +1153,7 @@ export class Db { }; const resp = await this.#httpClient.executeCommand(command, { - timeoutManager: this.#httpClient.tm.single('tableAdminTimeout', options), + timeoutManager: this.#httpClient.tm.single('tableAdminTimeoutMs', options), keyspace: options?.keyspace, }); return resp.status!.tables; @@ -1195,7 +1195,7 @@ export class Db { } return await this.#httpClient.executeCommand(command, { - timeoutManager: this.#httpClient.tm.single('generalMethodTimeout', options), + timeoutManager: this.#httpClient.tm.single('generalMethodTimeoutMs', options), collection: options?.collection ?? options?.table, keyspace: options?.keyspace, }); diff --git a/src/documents/commands/command-impls.ts b/src/documents/commands/command-impls.ts index 902a0b81..16ddefc5 100644 --- a/src/documents/commands/command-impls.ts +++ b/src/documents/commands/command-impls.ts @@ -65,7 +65,7 @@ export class CommandImpls { }); const raw = await this.#httpClient.executeCommand(command, { - timeoutManager: this.#httpClient.tm.single('generalMethodTimeout', options), + timeoutManager: this.#httpClient.tm.single('generalMethodTimeoutMs', options), bigNumsPresent: document[1], }); @@ -76,7 +76,7 @@ export class CommandImpls { public async insertMany(docs: readonly SomeDoc[], options: CollectionInsertManyOptions | nullish, err: new (descs: DataAPIDetailedErrorDescriptor[]) => DataAPIResponseError): Promise> { const chunkSize = options?.chunkSize ?? 50; - const timeoutManager = this.#httpClient.tm.multipart('generalMethodTimeout', options); + const timeoutManager = this.#httpClient.tm.multipart('generalMethodTimeoutMs', options); const insertedIds = (options?.ordered) ? await insertManyOrdered(this.#httpClient, this.#serdes, docs, chunkSize, timeoutManager, err) @@ -101,7 +101,7 @@ export class CommandImpls { }); const resp = await this.#httpClient.executeCommand(command, { - timeoutManager: this.#httpClient.tm.single('generalMethodTimeout', options), + timeoutManager: this.#httpClient.tm.single('generalMethodTimeoutMs', options), bigNumsPresent: filter[1] || update[1], }); @@ -121,7 +121,7 @@ export class CommandImpls { }, }); - const timeoutManager = this.#httpClient.tm.multipart('generalMethodTimeout', options); + const timeoutManager = this.#httpClient.tm.multipart('generalMethodTimeoutMs', options); const commonResult = mkUpdateResult(); let resp; @@ -168,7 +168,7 @@ export class CommandImpls { }); const resp = await this.#httpClient.executeCommand(command, { - timeoutManager: this.#httpClient.tm.single('generalMethodTimeout', options), + timeoutManager: this.#httpClient.tm.single('generalMethodTimeoutMs', options), bigNumsPresent: filter[1] || replacement[1], }); @@ -183,7 +183,7 @@ export class CommandImpls { }); const deleteOneResp = await this.#httpClient.executeCommand(command, { - timeoutManager: this.#httpClient.tm.single('generalMethodTimeout', options), + timeoutManager: this.#httpClient.tm.single('generalMethodTimeoutMs', options), bigNumsPresent: filter[1], }); @@ -199,7 +199,7 @@ export class CommandImpls { filter: filter[0], }); - const timeoutManager = this.#httpClient.tm.multipart('generalMethodTimeout', options); + const timeoutManager = this.#httpClient.tm.multipart('generalMethodTimeoutMs', options); let resp, numDeleted = 0; try { @@ -244,7 +244,7 @@ export class CommandImpls { }); const resp = await this.#httpClient.executeCommand(command, { - timeoutManager: this.#httpClient.tm.single('generalMethodTimeout', options), + timeoutManager: this.#httpClient.tm.single('generalMethodTimeoutMs', options), bigNumsPresent: filter[1], }); @@ -265,7 +265,7 @@ export class CommandImpls { }); const resp = await this.#httpClient.executeCommand(command, { - timeoutManager: this.#httpClient.tm.single('generalMethodTimeout', options), + timeoutManager: this.#httpClient.tm.single('generalMethodTimeoutMs', options), bigNumsPresent: filter[1] || replacement[1], }); return resp.data?.document || null; @@ -279,7 +279,7 @@ export class CommandImpls { }); const resp = await this.#httpClient.executeCommand(command, { - timeoutManager: this.#httpClient.tm.single('generalMethodTimeout', options), + timeoutManager: this.#httpClient.tm.single('generalMethodTimeoutMs', options), bigNumsPresent: filter[1], }); return resp.data?.document || null; @@ -299,7 +299,7 @@ export class CommandImpls { }); const resp = await this.#httpClient.executeCommand(command, { - timeoutManager: this.#httpClient.tm.single('generalMethodTimeout', options), + timeoutManager: this.#httpClient.tm.single('generalMethodTimeoutMs', options), bigNumsPresent: filter[1] || update[1], }); return resp.data?.document || null; @@ -350,7 +350,7 @@ export class CommandImpls { }); const resp = await this.#httpClient.executeCommand(command, { - timeoutManager: this.#httpClient.tm.single('generalMethodTimeout', options), + timeoutManager: this.#httpClient.tm.single('generalMethodTimeoutMs', options), bigNumsPresent, }); @@ -369,7 +369,7 @@ export class CommandImpls { const command = mkBasicCmd('estimatedDocumentCount', {}); const resp = await this.#httpClient.executeCommand(command, { - timeoutManager: this.#httpClient.tm.single('generalMethodTimeout', options), + timeoutManager: this.#httpClient.tm.single('generalMethodTimeoutMs', options), }); return resp.status?.count; diff --git a/src/documents/cursor.ts b/src/documents/cursor.ts index a7c8589b..41674ab9 100644 --- a/src/documents/cursor.ts +++ b/src/documents/cursor.ts @@ -619,7 +619,7 @@ export abstract class FindCursor { }; const raw = await this.#parent._httpClient.executeCommand(command, { - timeoutManager: this.#parent._httpClient.tm.multipart('generalMethodTimeout', this.#options), + timeoutManager: this.#parent._httpClient.tm.multipart('generalMethodTimeoutMs', this.#options), bigNumsPresent: this.#filter[1], }); diff --git a/src/documents/datatypes/blob.ts b/src/documents/datatypes/blob.ts index 85b544d7..af176d64 100644 --- a/src/documents/datatypes/blob.ts +++ b/src/documents/datatypes/blob.ts @@ -98,7 +98,7 @@ export class CqlBlob { } public static isBlobLike(value: unknown): value is CqlBlobLike { - return !!value && typeof value === 'object' && (value instanceof CqlBlob || '$binary' in value || value instanceof ArrayBuffer || value instanceof Buffer); + return !!value && typeof value === 'object' && (value instanceof CqlBlob || ('$binary' in value && typeof value.$binary === 'string') || value instanceof ArrayBuffer || value instanceof Buffer); } } diff --git a/src/documents/datatypes/vector.ts b/src/documents/datatypes/vector.ts index d7190eb4..e34c1af5 100644 --- a/src/documents/datatypes/vector.ts +++ b/src/documents/datatypes/vector.ts @@ -113,7 +113,7 @@ export class DataAPIVector { } public static isVectorLike(value: unknown): value is DataAPIVectorLike { - return !!value && typeof value === 'object' && (Array.isArray(value) || value instanceof Float32Array || '$binary' in value || value instanceof DataAPIVector); + return !!value && typeof value === 'object' && (Array.isArray(value) || value instanceof Float32Array || ('$binary' in value && typeof value.$binary === 'string') || value instanceof DataAPIVector); } } diff --git a/src/documents/tables/table.ts b/src/documents/tables/table.ts index d1fd5676..4e58f9a8 100644 --- a/src/documents/tables/table.ts +++ b/src/documents/tables/table.ts @@ -376,7 +376,7 @@ export class Table { operation: options.operation, }, }, { - timeoutManager: this.#httpClient.tm.single('tableAdminTimeout', options), + timeoutManager: this.#httpClient.tm.single('tableAdminTimeoutMs', options), }); return this; } @@ -398,7 +398,7 @@ export class Table { }, }, }, { - timeoutManager: this.#httpClient.tm.single('tableAdminTimeout', options), + timeoutManager: this.#httpClient.tm.single('tableAdminTimeoutMs', options), }); } @@ -418,7 +418,7 @@ export class Table { }, }, }, { - timeoutManager: this.#httpClient.tm.single('tableAdminTimeout', options), + timeoutManager: this.#httpClient.tm.single('tableAdminTimeoutMs', options), }); } diff --git a/src/documents/types/common.ts b/src/documents/types/common.ts index b7a45bf2..d5f8fba3 100644 --- a/src/documents/types/common.ts +++ b/src/documents/types/common.ts @@ -20,7 +20,7 @@ import { DataAPIVector, ToDotNotation } from '@/src/documents'; * * @public */ -export type SortDirection = 1 | -1 | 'asc' | 'desc' | 'ascending' | 'descending'; +export type SortDirection = 1 | -1; /** * Specifies the sort criteria for selecting documents. diff --git a/src/documents/utils.ts b/src/documents/utils.ts index 9c8b572b..f6b88cec 100644 --- a/src/documents/utils.ts +++ b/src/documents/utils.ts @@ -72,14 +72,8 @@ export const normalizedSort = (sort: SomeDoc): Sort => { for (const key in sort) { const val = sort[key]; - - if (typeof val === 'string') { - if (val[0] === 'a') { - ret[key] = 1; - } else if (val[0] === 'd') { - ret[key] = -1; - } - } else if (val instanceof DataAPIVector) { + + if (val instanceof DataAPIVector) { ret[key] = val[$SerializeForTable]() as Sort[string]; } else { ret[key] = val; diff --git a/src/lib/api/timeouts.ts b/src/lib/api/timeouts.ts index 1708ea0c..3b483424 100644 --- a/src/lib/api/timeouts.ts +++ b/src/lib/api/timeouts.ts @@ -19,15 +19,15 @@ import { toArray } from '@/src/lib/utils'; export type TimedOutTypes = OneOrMany | 'provided'; export interface TimeoutDescriptor { - requestTimeout: number, - generalMethodTimeout: number, - collectionAdminTimeout: number, - tableAdminTimeout: number, - databaseAdminTimeout: number, - keyspaceAdminTimeout: number, + requestTimeoutMs: number, + generalMethodTimeoutMs: number, + collectionAdminTimeoutMs: number, + tableAdminTimeoutMs: number, + databaseAdminTimeoutMs: number, + keyspaceAdminTimeoutMs: number, } -export interface WithTimeout { +export interface WithTimeout { timeout?: number | Pick, Timeouts>; } @@ -46,12 +46,12 @@ export class Timeouts { public readonly baseTimeouts: TimeoutDescriptor, ) {} - public single(key: Exclude, override: WithTimeout | nullish): TimeoutManager { + public single(key: Exclude, override: WithTimeout | nullish): TimeoutManager { if (typeof override?.timeout === 'number') { const timeout = override.timeout; const initial = { - requestTimeout: timeout, + requestTimeoutMs: timeout, [key]: timeout, }; @@ -61,7 +61,7 @@ export class Timeouts { } const timeouts = { - requestTimeout: (override?.timeout?.requestTimeout ?? this.baseTimeouts.requestTimeout) || EffectivelyInfinity, + requestTimeoutMs: (override?.timeout?.requestTimeoutMs ?? this.baseTimeouts.requestTimeoutMs) || EffectivelyInfinity, [key]: (override?.timeout?.[key] ?? this.baseTimeouts[key]) || EffectivelyInfinity, }; @@ -69,9 +69,9 @@ export class Timeouts { const type = (timeouts.requestTimeout === timeouts[key]) - ? ['requestTimeout', key] : + ? ['requestTimeoutMs', key] : (timeouts.requestTimeout < timeouts[key]) - ? 'requestTimeout' + ? 'requestTimeoutMs' : key; return this.custom(timeouts, () => { @@ -79,10 +79,10 @@ export class Timeouts { }); } - public multipart(key: Exclude, override: WithTimeout | nullish): TimeoutManager { + public multipart(key: Exclude, override: WithTimeout | nullish): TimeoutManager { const requestTimeout = (typeof override?.timeout === 'object') - ? override.timeout?.requestTimeout ?? this.baseTimeouts.requestTimeout - : this.baseTimeouts.requestTimeout; + ? override.timeout?.requestTimeoutMs ?? this.baseTimeouts.requestTimeoutMs + : this.baseTimeouts.requestTimeoutMs; const overallTimeout = (typeof override?.timeout === 'object') @@ -92,7 +92,7 @@ export class Timeouts { : this.baseTimeouts[key]; const initial = { - requestTimeout, + requestTimeoutMs: requestTimeout, [key]: overallTimeout, }; @@ -108,9 +108,9 @@ export class Timeouts { if (overallLeft < requestTimeout) { return [overallLeft, key]; } else if (overallLeft > requestTimeout) { - return [requestTimeout, 'requestTimeout']; + return [requestTimeout, 'requestTimeoutMs']; } else { - return [overallLeft, ['requestTimeout', key]]; + return [overallLeft, ['requestTimeoutMs', key]]; } }); } @@ -130,12 +130,12 @@ export class Timeouts { } public static Default: TimeoutDescriptor = { - requestTimeout: 10000, - generalMethodTimeout: 30000, - collectionAdminTimeout: 60000, - tableAdminTimeout: 30000, - databaseAdminTimeout: 600000, - keyspaceAdminTimeout: 30000, + requestTimeoutMs: 10000, + generalMethodTimeoutMs: 30000, + collectionAdminTimeoutMs: 60000, + tableAdminTimeoutMs: 30000, + databaseAdminTimeoutMs: 600000, + keyspaceAdminTimeoutMs: 30000, }; public static merge(base: TimeoutDescriptor, custom: Partial | nullish): TimeoutDescriptor { @@ -144,12 +144,12 @@ export class Timeouts { } return { - requestTimeout: custom.requestTimeout ?? base.requestTimeout, - generalMethodTimeout: custom.generalMethodTimeout ?? base.generalMethodTimeout, - collectionAdminTimeout: custom.collectionAdminTimeout ?? base.collectionAdminTimeout, - tableAdminTimeout: custom.tableAdminTimeout ?? base.tableAdminTimeout, - databaseAdminTimeout: custom.databaseAdminTimeout ?? base.databaseAdminTimeout, - keyspaceAdminTimeout: custom.keyspaceAdminTimeout ?? base.keyspaceAdminTimeout, + requestTimeoutMs: custom.requestTimeoutMs ?? base.requestTimeoutMs, + generalMethodTimeoutMs: custom.generalMethodTimeoutMs ?? base.generalMethodTimeoutMs, + collectionAdminTimeoutMs: custom.collectionAdminTimeoutMs ?? base.collectionAdminTimeoutMs, + tableAdminTimeoutMs: custom.tableAdminTimeoutMs ?? base.tableAdminTimeoutMs, + databaseAdminTimeoutMs: custom.databaseAdminTimeoutMs ?? base.databaseAdminTimeoutMs, + keyspaceAdminTimeoutMs: custom.keyspaceAdminTimeoutMs ?? base.keyspaceAdminTimeoutMs, }; } diff --git a/tests/integration/administration/lifecycle.test.ts b/tests/integration/administration/lifecycle.test.ts index d60a11d4..5fd0e4eb 100644 --- a/tests/integration/administration/lifecycle.test.ts +++ b/tests/integration/administration/lifecycle.test.ts @@ -130,7 +130,7 @@ background('(ADMIN) (LONG) (NOT-DEV) (ASTRA) integration.administration.lifecycl defaultPollInterval: 10000, id: null!, options: undefined, - timeoutManager: asyncDbAdmin._httpClient.tm.multipart('generalMethodTimeout', { timeout: 0 }), + timeoutManager: asyncDbAdmin._httpClient.tm.multipart('generalMethodTimeoutMs', { timeout: 0 }), }, 0); } @@ -179,7 +179,7 @@ background('(ADMIN) (LONG) (NOT-DEV) (ASTRA) integration.administration.lifecycl defaultPollInterval: 1000, id: null!, options: undefined, - timeoutManager: asyncDbAdmin._httpClient.tm.multipart('generalMethodTimeout', { timeout: 0 }), + timeoutManager: asyncDbAdmin._httpClient.tm.multipart('generalMethodTimeoutMs', { timeout: 0 }), }, 0); } @@ -204,7 +204,7 @@ background('(ADMIN) (LONG) (NOT-DEV) (ASTRA) integration.administration.lifecycl defaultPollInterval: 1000, id: null!, options: undefined, - timeoutManager: asyncDbAdmin._httpClient.tm.multipart('generalMethodTimeout', { timeout: 0 }), + timeoutManager: asyncDbAdmin._httpClient.tm.multipart('generalMethodTimeoutMs', { timeout: 0 }), }, 0); } @@ -231,7 +231,7 @@ background('(ADMIN) (LONG) (NOT-DEV) (ASTRA) integration.administration.lifecycl defaultPollInterval: 10000, id: null!, options: undefined, - timeoutManager: asyncDbAdmin._httpClient.tm.multipart('generalMethodTimeout', { timeout: 0 }), + timeoutManager: asyncDbAdmin._httpClient.tm.multipart('generalMethodTimeoutMs', { timeout: 0 }), }, 0); } diff --git a/tests/integration/client/data-api-client.test.ts b/tests/integration/client/data-api-client.test.ts index ee777581..b37e817a 100644 --- a/tests/integration/client/data-api-client.test.ts +++ b/tests/integration/client/data-api-client.test.ts @@ -170,9 +170,9 @@ describe('integration.client.data-api-client', () => { assert.strictEqual(startedEvents[1].url, `${TEST_APPLICATION_URI}/${DEFAULT_DATA_API_PATHS[ENVIRONMENT]}/${OTHER_KEYSPACE}/${DEFAULT_COLLECTION_NAME}`); assert.strictEqual(succeededEvents[1].url, `${TEST_APPLICATION_URI}/${DEFAULT_DATA_API_PATHS[ENVIRONMENT]}/${OTHER_KEYSPACE}/${DEFAULT_COLLECTION_NAME}`); - assert.deepStrictEqual(startedEvents[0].timeout, { generalMethodTimeout: Timeouts.Default.generalMethodTimeout, requestTimeout: Timeouts.Default.requestTimeout }); + assert.deepStrictEqual(startedEvents[0].timeout, { generalMethodTimeoutMs: Timeouts.Default.generalMethodTimeoutMs, requestTimeoutMs: Timeouts.Default.requestTimeoutMs }); assert.ok(succeededEvents[0].duration > 0); - assert.deepStrictEqual(startedEvents[1].timeout, { requestTimeout: 10000, generalMethodTimeout: 10000 }); + assert.deepStrictEqual(startedEvents[1].timeout, { requestTimeoutMs: 10000, generalMethodTimeoutMs: 10000 }); assert.ok(succeededEvents[1].duration > 0); assert.deepStrictEqual(startedEvents[0].command, { insertOne: { document: { name: 'Chthonic' } } }); @@ -234,7 +234,7 @@ describe('integration.client.data-api-client', () => { assert.strictEqual(startedEvent.url, `${TEST_APPLICATION_URI}/${DEFAULT_DATA_API_PATHS[ENVIRONMENT]}/${DEFAULT_KEYSPACE}/${DEFAULT_COLLECTION_NAME}`); assert.strictEqual(failedEvent.url, `${TEST_APPLICATION_URI}/${DEFAULT_DATA_API_PATHS[ENVIRONMENT]}/${DEFAULT_KEYSPACE}/${DEFAULT_COLLECTION_NAME}`); - assert.deepStrictEqual(startedEvent.timeout, { generalMethodTimeout: Timeouts.Default.generalMethodTimeout, requestTimeout: Timeouts.Default.requestTimeout }); + assert.deepStrictEqual(startedEvent.timeout, { generalMethodTimeoutMs: Timeouts.Default.generalMethodTimeoutMs, requestTimeoutMs: Timeouts.Default.requestTimeoutMs }); assert.ok(failedEvent.duration > 0); assert.deepStrictEqual(startedEvent.command, { insertOne: { document: { _id: 0, name: 'Oasis' } } }); @@ -287,14 +287,14 @@ describe('integration.client.data-api-client', () => { assert.strictEqual(startedEvent.url, `${TEST_APPLICATION_URI}/${DEFAULT_DATA_API_PATHS[ENVIRONMENT]}/${DEFAULT_KEYSPACE}/${DEFAULT_COLLECTION_NAME}`); assert.strictEqual(failedEvent.url, `${TEST_APPLICATION_URI}/${DEFAULT_DATA_API_PATHS[ENVIRONMENT]}/${DEFAULT_KEYSPACE}/${DEFAULT_COLLECTION_NAME}`); - assert.deepStrictEqual(startedEvent.timeout, { generalMethodTimeout: 1, requestTimeout: 1 }); + assert.deepStrictEqual(startedEvent.timeout, { generalMethodTimeoutMs: 1, requestTimeoutMs: 1 }); assert.ok(failedEvent.duration > 0); assert.deepStrictEqual(startedEvent.command, { insertOne: { document: { name: 'Xandria' } } }); assert.deepStrictEqual(failedEvent.command, { insertOne: { document: { name: 'Xandria' } } }); assert.ok(failedEvent.error instanceof DataAPITimeoutError); - assert.deepStrictEqual(failedEvent.error.timeout, { generalMethodTimeout: 1, requestTimeout: 1 }); + assert.deepStrictEqual(failedEvent.error.timeout, { generalMethodTimeoutMs: 1, requestTimeoutMs: 1 }); assert.deepStrictEqual(stdout, []); assert.deepStrictEqual(stderr[0], startedEvent.formatted()); diff --git a/tests/integration/documents/collections/insert-many.test.ts b/tests/integration/documents/collections/insert-many.test.ts index 016a3563..8c962991 100644 --- a/tests/integration/documents/collections/insert-many.test.ts +++ b/tests/integration/documents/collections/insert-many.test.ts @@ -178,7 +178,7 @@ parallel('integration.documents.collections.insert-many', { truncate: 'colls:bef assert.fail('Expected an error'); } catch (e) { assert.ok(e instanceof DataAPITimeoutError); - assert.deepStrictEqual(e.timeout, { generalMethodTimeout: 500, requestTimeout: Timeouts.Default.requestTimeout }); + assert.deepStrictEqual(e.timeout, { generalMethodTimeoutMs: 500, requestTimeoutMs: Timeouts.Default.requestTimeoutMs }); const found = await collection.find({ key }).toArray(); assert.ok(found.length > 0); assert.ok(found.length < 1000); diff --git a/tests/integration/lib/api/clients/data-api-http-client.test.ts b/tests/integration/lib/api/clients/data-api-http-client.test.ts index d7ed3572..fdb1d866 100644 --- a/tests/integration/lib/api/clients/data-api-http-client.test.ts +++ b/tests/integration/lib/api/clients/data-api-http-client.test.ts @@ -38,7 +38,7 @@ describe('integration.lib.api.clients.documents-http-client', ({ db }) => { const resp = await httpClient.executeCommand({ findCollections: {}, }, { - timeoutManager: httpClient.tm.single('generalMethodTimeout', {}), + timeoutManager: httpClient.tm.single('generalMethodTimeoutMs', {}), }); assert.strictEqual(typeof resp.status?.collections.length, 'number'); }); @@ -47,7 +47,7 @@ describe('integration.lib.api.clients.documents-http-client', ({ db }) => { const resp = await httpClient.executeCommand({ findCollections: {}, }, { - timeoutManager: httpClient.tm.single('generalMethodTimeout', {}), + timeoutManager: httpClient.tm.single('generalMethodTimeoutMs', {}), keyspace: OTHER_KEYSPACE, }); assert.strictEqual(resp.status?.collections.length, 1); @@ -57,7 +57,7 @@ describe('integration.lib.api.clients.documents-http-client', ({ db }) => { const resp = await httpClient.executeCommand({ insertOne: { document: { name: 'John' } }, }, { - timeoutManager: httpClient.tm.single('generalMethodTimeout', {}), + timeoutManager: httpClient.tm.single('generalMethodTimeoutMs', {}), collection: DEFAULT_COLLECTION_NAME, }); assert.ok(resp.status?.insertedIds[0]); @@ -69,7 +69,7 @@ describe('integration.lib.api.clients.documents-http-client', ({ db }) => { try { await httpClient.executeCommand({ findCollections: {} }, { - timeoutManager: httpClient.tm.single('generalMethodTimeout', {}), + timeoutManager: httpClient.tm.single('generalMethodTimeoutMs', {}), }); assert.fail('Expected error'); } catch (e) { @@ -86,7 +86,7 @@ describe('integration.lib.api.clients.documents-http-client', ({ db }) => { try { await httpClient.executeCommand({ findCollections: {} }, { - timeoutManager: httpClient.tm.single('generalMethodTimeout', {}), + timeoutManager: httpClient.tm.single('generalMethodTimeoutMs', {}), }); assert.fail('Expected error'); } catch (e) { diff --git a/tests/unit/lib/api/timeouts.test.ts b/tests/unit/lib/api/timeouts.test.ts index 867c5365..3255f911 100644 --- a/tests/unit/lib/api/timeouts.test.ts +++ b/tests/unit/lib/api/timeouts.test.ts @@ -30,25 +30,25 @@ describe('unit.lib.api.timeouts', () => { describe('single', () => { it('works w/ no override', () => { - const tm = timeouts.single('generalMethodTimeout', null); + const tm = timeouts.single('generalMethodTimeoutMs', null); const [timeout, mkError] = tm.advance(info(tm)); - assert.strictEqual(timeout, Timeouts.Default.requestTimeout); + assert.strictEqual(timeout, Timeouts.Default.requestTimeoutMs); const e = mkError(); assert.ok(e instanceof TimeoutError); assert.deepStrictEqual(e.info, info(tm)); - assert.strictEqual(e.timeoutType, 'requestTimeout'); - assert.strictEqual(e.message, `Command timed out after ${Timeouts.Default.requestTimeout}ms (requestTimeout timed out)`); + assert.strictEqual(e.timeoutType, 'requestTimeoutMs'); + assert.strictEqual(e.message, `Command timed out after ${Timeouts.Default.requestTimeoutMs}ms (requestTimeout timed out)`); assert.deepStrictEqual(tm.initial(), { - generalMethodTimeout: Timeouts.Default.generalMethodTimeout, - requestTimeout: Timeouts.Default.requestTimeout, + generalMethodTimeoutMs: Timeouts.Default.generalMethodTimeoutMs, + requestTimeoutMs: Timeouts.Default.requestTimeoutMs, }); }); it('works w/ override number', () => { - const tm = timeouts.single('generalMethodTimeout', { timeout: 100 }); + const tm = timeouts.single('generalMethodTimeoutMs', { timeout: 100 }); const [timeout, mkError] = tm.advance(info(tm)); assert.strictEqual(timeout, 100); @@ -60,13 +60,13 @@ describe('unit.lib.api.timeouts', () => { assert.strictEqual(e.message, 'Command timed out after 100ms (The timeout provided via `{ timeout: }` timed out)'); assert.deepStrictEqual(tm.initial(), { - generalMethodTimeout: 100, - requestTimeout: 100, + generalMethodTimeoutMs: 100, + requestTimeoutMs: 100, }); }); it('works w/ partial override object', () => { - const tm = timeouts.single('databaseAdminTimeout', { timeout: { generalMethodTimeout: 100, databaseAdminTimeout: 50 } }); + const tm = timeouts.single('databaseAdminTimeoutMs', { timeout: { generalMethodTimeoutMs: 100, databaseAdminTimeoutMs: 50 } }); const [timeout, mkError] = tm.advance(info(tm)); assert.strictEqual(timeout, 50); @@ -78,13 +78,13 @@ describe('unit.lib.api.timeouts', () => { assert.strictEqual(e.message, 'Command timed out after 50ms (databaseAdminTimeout timed out)'); assert.deepStrictEqual(tm.initial(), { - requestTimeout: Timeouts.Default.requestTimeout, - databaseAdminTimeout: 50, + requestTimeoutMs: Timeouts.Default.requestTimeoutMs, + databaseAdminTimeoutMs: 50, }); }); it('works w/ full override object', () => { - const tm = timeouts.single('databaseAdminTimeout', { timeout: { generalMethodTimeout: 100, requestTimeout: 10, databaseAdminTimeout: 50 } }); + const tm = timeouts.single('databaseAdminTimeoutMs', { timeout: { generalMethodTimeoutMs: 100, requestTimeoutMs: 10, databaseAdminTimeoutMs: 50 } }); const [timeout, mkError] = tm.advance(info(tm)); assert.strictEqual(timeout, 10); @@ -92,49 +92,49 @@ describe('unit.lib.api.timeouts', () => { const e = mkError(); assert.ok(e instanceof TimeoutError); assert.deepStrictEqual(e.info, info(tm)); - assert.strictEqual(e.timeoutType, 'requestTimeout'); + assert.strictEqual(e.timeoutType, 'requestTimeoutMs'); assert.strictEqual(e.message, 'Command timed out after 10ms (requestTimeout timed out)'); assert.deepStrictEqual(tm.initial(), { - databaseAdminTimeout: 50, - requestTimeout: 10, + databaseAdminTimeoutMs: 50, + requestTimeoutMs: 10, }); }); it('works w/ uniform full override object', () => { - const tm = timeouts.single('keyspaceAdminTimeout', { timeout: { keyspaceAdminTimeout: Timeouts.Default.requestTimeout } }); + const tm = timeouts.single('keyspaceAdminTimeoutMs', { timeout: { keyspaceAdminTimeoutMs: Timeouts.Default.requestTimeoutMs } }); const [timeout, mkError] = tm.advance(info(tm)); - assert.strictEqual(timeout, Timeouts.Default.requestTimeout); + assert.strictEqual(timeout, Timeouts.Default.requestTimeoutMs); const e = mkError(); assert.ok(e instanceof TimeoutError); assert.deepStrictEqual(e.info, info(tm)); - assert.deepStrictEqual(e.timeoutType, ['requestTimeout', 'keyspaceAdminTimeout']); - assert.strictEqual(e.message, `Command timed out after ${Timeouts.Default.requestTimeout}ms (requestTimeout and keyspaceAdminTimeout simultaneously timed out)`); + assert.deepStrictEqual(e.timeoutType, ['requestTimeoutMs', 'keyspaceAdminTimeout']); + assert.strictEqual(e.message, `Command timed out after ${Timeouts.Default.requestTimeoutMs}ms (requestTimeout and keyspaceAdminTimeout simultaneously timed out)`); assert.deepStrictEqual(tm.initial(), { - keyspaceAdminTimeout: Timeouts.Default.requestTimeout, - requestTimeout: Timeouts.Default.requestTimeout, + keyspaceAdminTimeoutMs: Timeouts.Default.requestTimeoutMs, + requestTimeoutMs: Timeouts.Default.requestTimeoutMs, }); }); }); parallel('multipart', () => { it('works w/ override number', async () => { - const tm = timeouts.multipart('generalMethodTimeout', { timeout: 10001 }); + const tm = timeouts.multipart('generalMethodTimeoutMs', { timeout: 10001 }); let [timeout, mkError] = tm.advance(info(tm)); - assert.strictEqual(timeout, Timeouts.Default.requestTimeout); + assert.strictEqual(timeout, Timeouts.Default.requestTimeoutMs); const e = mkError(); assert.ok(e instanceof TimeoutError); assert.deepStrictEqual(e.info, info(tm)); - assert.strictEqual(e.timeoutType, 'requestTimeout'); - assert.strictEqual(e.message, `Command timed out after ${Timeouts.Default.requestTimeout}ms (requestTimeout timed out)`); + assert.strictEqual(e.timeoutType, 'requestTimeoutMs'); + assert.strictEqual(e.message, `Command timed out after ${Timeouts.Default.requestTimeoutMs}ms (requestTimeout timed out)`); assert.deepStrictEqual(tm.initial(), { - requestTimeout: Timeouts.Default.requestTimeout, - generalMethodTimeout: 10001, + requestTimeoutMs: Timeouts.Default.requestTimeoutMs, + generalMethodTimeoutMs: 10001, }); await new Promise(resolve => setTimeout(resolve, 5001)); @@ -148,24 +148,24 @@ describe('unit.lib.api.timeouts', () => { const e2 = mkError(); assert.ok(e2 instanceof TimeoutError); assert.deepStrictEqual(e2.info, info(tm)); - assert.strictEqual(e2.timeoutType, 'generalMethodTimeout'); + assert.strictEqual(e2.timeoutType, 'generalMethodTimeoutMs'); assert.strictEqual(e2.message, 'Command timed out after 10001ms (generalMethodTimeout timed out)'); }); it('works w/ partial override object', async () => { - const tm = timeouts.multipart('tableAdminTimeout', { timeout: { requestTimeout: 10 } }); + const tm = timeouts.multipart('tableAdminTimeoutMs', { timeout: { requestTimeoutMs: 10 } }); let [timeout, mkError] = tm.advance(info(tm)); assert.strictEqual(timeout, 10); const e = mkError(); assert.ok(e instanceof TimeoutError); assert.deepStrictEqual(e.info, info(tm)); - assert.strictEqual(e.timeoutType, 'requestTimeout'); + assert.strictEqual(e.timeoutType, 'requestTimeoutMs'); assert.strictEqual(e.message, 'Command timed out after 10ms (requestTimeout timed out)'); assert.deepStrictEqual(tm.initial(), { - tableAdminTimeout: Timeouts.Default.tableAdminTimeout, - requestTimeout: 10, + tableAdminTimeoutMs: Timeouts.Default.tableAdminTimeoutMs, + requestTimeoutMs: 10, }); await new Promise(resolve => setTimeout(resolve, 5)); @@ -179,24 +179,24 @@ describe('unit.lib.api.timeouts', () => { const e2 = mkError(); assert.ok(e2 instanceof TimeoutError); assert.deepStrictEqual(e2.info, info(tm)); - assert.strictEqual(e2.timeoutType, 'requestTimeout'); + assert.strictEqual(e2.timeoutType, 'requestTimeoutMs'); assert.strictEqual(e2.message, 'Command timed out after 10ms (requestTimeout timed out)'); }); it('works w/ full override object', async () => { - const tm = timeouts.multipart('tableAdminTimeout', { timeout: { requestTimeout: 10, tableAdminTimeout: 100 } }); + const tm = timeouts.multipart('tableAdminTimeoutMs', { timeout: { requestTimeoutMs: 10, tableAdminTimeoutMs: 100 } }); let [timeout, mkError] = tm.advance(info(tm)); assert.strictEqual(timeout, 10); const e = mkError(); assert.ok(e instanceof TimeoutError); assert.deepStrictEqual(e.info, info(tm)); - assert.strictEqual(e.timeoutType, 'requestTimeout'); + assert.strictEqual(e.timeoutType, 'requestTimeoutMs'); assert.strictEqual(e.message, 'Command timed out after 10ms (requestTimeout timed out)'); assert.deepStrictEqual(tm.initial(), { - tableAdminTimeout: 100, - requestTimeout: 10, + tableAdminTimeoutMs: 100, + requestTimeoutMs: 10, }); await new Promise(resolve => setTimeout(resolve, 5)); @@ -206,7 +206,7 @@ describe('unit.lib.api.timeouts', () => { const e2 = mkError(); assert.ok(e2 instanceof TimeoutError); assert.deepStrictEqual(e2.info, info(tm)); - assert.strictEqual(e2.timeoutType, 'requestTimeout'); + assert.strictEqual(e2.timeoutType, 'requestTimeoutMs'); assert.strictEqual(e2.message, 'Command timed out after 10ms (requestTimeout timed out)'); await new Promise(resolve => setTimeout(resolve, 100)); @@ -221,19 +221,19 @@ describe('unit.lib.api.timeouts', () => { }); it('works w/ uniform full override object', async () => { - const tm = timeouts.multipart('keyspaceAdminTimeout', { timeout: { requestTimeout: 100, keyspaceAdminTimeout: 100 } }); + const tm = timeouts.multipart('keyspaceAdminTimeoutMs', { timeout: { requestTimeoutMs: 100, keyspaceAdminTimeoutMs: 100 } }); let [timeout, mkError] = tm.advance(info(tm)); assert.strictEqual(timeout, 100); const e = mkError(); assert.ok(e instanceof TimeoutError); assert.deepStrictEqual(e.info, info(tm)); - assert.deepStrictEqual(e.timeoutType, ['requestTimeout', 'keyspaceAdminTimeout']); + assert.deepStrictEqual(e.timeoutType, ['requestTimeoutMs', 'keyspaceAdminTimeout']); assert.strictEqual(e.message, 'Command timed out after 100ms (requestTimeout and keyspaceAdminTimeout simultaneously timed out)'); assert.deepStrictEqual(tm.initial(), { - keyspaceAdminTimeout: 100, - requestTimeout: 100, + keyspaceAdminTimeoutMs: 100, + requestTimeoutMs: 100, }); await new Promise(resolve => setTimeout(resolve, 51)); From 8e17d40797ddc0cdc3af6446b189a087b221a73f Mon Sep 17 00:00:00 2001 From: toptobes Date: Mon, 18 Nov 2024 00:34:05 +0530 Subject: [PATCH 05/10] fix timeout/sort bgus --- src/lib/api/timeouts.ts | 16 ++++---- .../documents/collections/finds.test.ts | 6 +-- tests/unit/lib/api/timeouts.test.ts | 40 +++++++++---------- 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/src/lib/api/timeouts.ts b/src/lib/api/timeouts.ts index 3b483424..14d91422 100644 --- a/src/lib/api/timeouts.ts +++ b/src/lib/api/timeouts.ts @@ -65,12 +65,12 @@ export class Timeouts { [key]: (override?.timeout?.[key] ?? this.baseTimeouts[key]) || EffectivelyInfinity, }; - const timeout = Math.min(timeouts.requestTimeout, timeouts[key]); + const timeout = Math.min(timeouts.requestTimeoutMs, timeouts[key]); const type = - (timeouts.requestTimeout === timeouts[key]) + (timeouts.requestTimeoutMs === timeouts[key]) ? ['requestTimeoutMs', key] : - (timeouts.requestTimeout < timeouts[key]) + (timeouts.requestTimeoutMs < timeouts[key]) ? 'requestTimeoutMs' : key; @@ -80,16 +80,18 @@ export class Timeouts { } public multipart(key: Exclude, override: WithTimeout | nullish): TimeoutManager { - const requestTimeout = (typeof override?.timeout === 'object') + const requestTimeout = ((typeof override?.timeout === 'object') ? override.timeout?.requestTimeoutMs ?? this.baseTimeouts.requestTimeoutMs - : this.baseTimeouts.requestTimeoutMs; + : this.baseTimeouts.requestTimeoutMs) + || EffectivelyInfinity; const overallTimeout = - (typeof override?.timeout === 'object') + ((typeof override?.timeout === 'object') ? override.timeout?.[key] ?? this.baseTimeouts[key] : (typeof override?.timeout === 'number') ? override.timeout - : this.baseTimeouts[key]; + : this.baseTimeouts[key]) + || EffectivelyInfinity; const initial = { requestTimeoutMs: requestTimeout, diff --git a/tests/integration/documents/collections/finds.test.ts b/tests/integration/documents/collections/finds.test.ts index c29d3f8e..51bdf4d0 100644 --- a/tests/integration/documents/collections/finds.test.ts +++ b/tests/integration/documents/collections/finds.test.ts @@ -91,13 +91,13 @@ parallel('integration.documents.collections.finds', { truncate: 'colls:before' } let docs = await collection.find({ key }, { sort: { username: 1, age: 1 }, limit: 20 }).toArray(); assert.deepStrictEqual(docs.map(doc => doc.age), [1, 2, 3]); - docs = await collection.find({ key }, { sort: { username: "asc", age: "desc" }, limit: 20 }).toArray(); + docs = await collection.find({ key }, { sort: { username: 1, age: -1 }, limit: 20 }).toArray(); assert.deepStrictEqual(docs.map(doc => doc.age), [3, 2, 1]); - docs = await collection.find({ key }, { sort: { username: -1, age: "ascending" }, limit: 20 }).toArray(); + docs = await collection.find({ key }, { sort: { username: -1, age: 1 }, limit: 20 }).toArray(); assert.deepStrictEqual(docs.map(doc => doc.age), [1, 2, 3]); - docs = await collection.find({ key }, { sort: { username: -1, age: "descending" }, limit: 20 }).toArray(); + docs = await collection.find({ key }, { sort: { username: -1, age: -1 }, limit: 20 }).toArray(); assert.deepStrictEqual(docs.map(doc => doc.age), [3, 2, 1]); }); diff --git a/tests/unit/lib/api/timeouts.test.ts b/tests/unit/lib/api/timeouts.test.ts index 3255f911..4cbc12b8 100644 --- a/tests/unit/lib/api/timeouts.test.ts +++ b/tests/unit/lib/api/timeouts.test.ts @@ -39,7 +39,7 @@ describe('unit.lib.api.timeouts', () => { assert.ok(e instanceof TimeoutError); assert.deepStrictEqual(e.info, info(tm)); assert.strictEqual(e.timeoutType, 'requestTimeoutMs'); - assert.strictEqual(e.message, `Command timed out after ${Timeouts.Default.requestTimeoutMs}ms (requestTimeout timed out)`); + assert.strictEqual(e.message, `Command timed out after ${Timeouts.Default.requestTimeoutMs}ms (requestTimeoutMs timed out)`); assert.deepStrictEqual(tm.initial(), { generalMethodTimeoutMs: Timeouts.Default.generalMethodTimeoutMs, @@ -74,8 +74,8 @@ describe('unit.lib.api.timeouts', () => { const e = mkError(); assert.ok(e instanceof TimeoutError); assert.deepStrictEqual(e.info, info(tm)); - assert.strictEqual(e.timeoutType, 'databaseAdminTimeout'); - assert.strictEqual(e.message, 'Command timed out after 50ms (databaseAdminTimeout timed out)'); + assert.strictEqual(e.timeoutType, 'databaseAdminTimeoutMs'); + assert.strictEqual(e.message, 'Command timed out after 50ms (databaseAdminTimeoutMs timed out)'); assert.deepStrictEqual(tm.initial(), { requestTimeoutMs: Timeouts.Default.requestTimeoutMs, @@ -93,7 +93,7 @@ describe('unit.lib.api.timeouts', () => { assert.ok(e instanceof TimeoutError); assert.deepStrictEqual(e.info, info(tm)); assert.strictEqual(e.timeoutType, 'requestTimeoutMs'); - assert.strictEqual(e.message, 'Command timed out after 10ms (requestTimeout timed out)'); + assert.strictEqual(e.message, 'Command timed out after 10ms (requestTimeoutMs timed out)'); assert.deepStrictEqual(tm.initial(), { databaseAdminTimeoutMs: 50, @@ -110,8 +110,8 @@ describe('unit.lib.api.timeouts', () => { const e = mkError(); assert.ok(e instanceof TimeoutError); assert.deepStrictEqual(e.info, info(tm)); - assert.deepStrictEqual(e.timeoutType, ['requestTimeoutMs', 'keyspaceAdminTimeout']); - assert.strictEqual(e.message, `Command timed out after ${Timeouts.Default.requestTimeoutMs}ms (requestTimeout and keyspaceAdminTimeout simultaneously timed out)`); + assert.deepStrictEqual(e.timeoutType, ['requestTimeoutMs', 'keyspaceAdminTimeoutMs']); + assert.strictEqual(e.message, `Command timed out after ${Timeouts.Default.requestTimeoutMs}ms (requestTimeoutMs and keyspaceAdminTimeoutMs simultaneously timed out)`); assert.deepStrictEqual(tm.initial(), { keyspaceAdminTimeoutMs: Timeouts.Default.requestTimeoutMs, @@ -130,7 +130,7 @@ describe('unit.lib.api.timeouts', () => { assert.ok(e instanceof TimeoutError); assert.deepStrictEqual(e.info, info(tm)); assert.strictEqual(e.timeoutType, 'requestTimeoutMs'); - assert.strictEqual(e.message, `Command timed out after ${Timeouts.Default.requestTimeoutMs}ms (requestTimeout timed out)`); + assert.strictEqual(e.message, `Command timed out after ${Timeouts.Default.requestTimeoutMs}ms (requestTimeoutMs timed out)`); assert.deepStrictEqual(tm.initial(), { requestTimeoutMs: Timeouts.Default.requestTimeoutMs, @@ -149,7 +149,7 @@ describe('unit.lib.api.timeouts', () => { assert.ok(e2 instanceof TimeoutError); assert.deepStrictEqual(e2.info, info(tm)); assert.strictEqual(e2.timeoutType, 'generalMethodTimeoutMs'); - assert.strictEqual(e2.message, 'Command timed out after 10001ms (generalMethodTimeout timed out)'); + assert.strictEqual(e2.message, 'Command timed out after 10001ms (generalMethodTimeoutMs timed out)'); }); it('works w/ partial override object', async () => { @@ -161,7 +161,7 @@ describe('unit.lib.api.timeouts', () => { assert.ok(e instanceof TimeoutError); assert.deepStrictEqual(e.info, info(tm)); assert.strictEqual(e.timeoutType, 'requestTimeoutMs'); - assert.strictEqual(e.message, 'Command timed out after 10ms (requestTimeout timed out)'); + assert.strictEqual(e.message, 'Command timed out after 10ms (requestTimeoutMs timed out)'); assert.deepStrictEqual(tm.initial(), { tableAdminTimeoutMs: Timeouts.Default.tableAdminTimeoutMs, @@ -180,7 +180,7 @@ describe('unit.lib.api.timeouts', () => { assert.ok(e2 instanceof TimeoutError); assert.deepStrictEqual(e2.info, info(tm)); assert.strictEqual(e2.timeoutType, 'requestTimeoutMs'); - assert.strictEqual(e2.message, 'Command timed out after 10ms (requestTimeout timed out)'); + assert.strictEqual(e2.message, 'Command timed out after 10ms (requestTimeoutMs timed out)'); }); it('works w/ full override object', async () => { @@ -192,7 +192,7 @@ describe('unit.lib.api.timeouts', () => { assert.ok(e instanceof TimeoutError); assert.deepStrictEqual(e.info, info(tm)); assert.strictEqual(e.timeoutType, 'requestTimeoutMs'); - assert.strictEqual(e.message, 'Command timed out after 10ms (requestTimeout timed out)'); + assert.strictEqual(e.message, 'Command timed out after 10ms (requestTimeoutMs timed out)'); assert.deepStrictEqual(tm.initial(), { tableAdminTimeoutMs: 100, @@ -207,7 +207,7 @@ describe('unit.lib.api.timeouts', () => { assert.ok(e2 instanceof TimeoutError); assert.deepStrictEqual(e2.info, info(tm)); assert.strictEqual(e2.timeoutType, 'requestTimeoutMs'); - assert.strictEqual(e2.message, 'Command timed out after 10ms (requestTimeout timed out)'); + assert.strictEqual(e2.message, 'Command timed out after 10ms (requestTimeoutMs timed out)'); await new Promise(resolve => setTimeout(resolve, 100)); [timeout, mkError] = tm.advance(info(tm)); @@ -216,8 +216,8 @@ describe('unit.lib.api.timeouts', () => { const e3 = mkError(); assert.ok(e3 instanceof TimeoutError); assert.deepStrictEqual(e3.info, info(tm)); - assert.strictEqual(e3.timeoutType, 'tableAdminTimeout'); - assert.strictEqual(e3.message, 'Command timed out after 100ms (tableAdminTimeout timed out)'); + assert.strictEqual(e3.timeoutType, 'tableAdminTimeoutMs'); + assert.strictEqual(e3.message, 'Command timed out after 100ms (tableAdminTimeoutMs timed out)'); }); it('works w/ uniform full override object', async () => { @@ -228,8 +228,8 @@ describe('unit.lib.api.timeouts', () => { const e = mkError(); assert.ok(e instanceof TimeoutError); assert.deepStrictEqual(e.info, info(tm)); - assert.deepStrictEqual(e.timeoutType, ['requestTimeoutMs', 'keyspaceAdminTimeout']); - assert.strictEqual(e.message, 'Command timed out after 100ms (requestTimeout and keyspaceAdminTimeout simultaneously timed out)'); + assert.deepStrictEqual(e.timeoutType, ['requestTimeoutMs', 'keyspaceAdminTimeoutMs']); + assert.strictEqual(e.message, 'Command timed out after 100ms (requestTimeoutMs and keyspaceAdminTimeoutMs simultaneously timed out)'); assert.deepStrictEqual(tm.initial(), { keyspaceAdminTimeoutMs: 100, @@ -243,8 +243,8 @@ describe('unit.lib.api.timeouts', () => { const e2 = mkError(); assert.ok(e2 instanceof TimeoutError); assert.deepStrictEqual(e2.info, info(tm)); - assert.strictEqual(e2.timeoutType, 'keyspaceAdminTimeout'); - assert.strictEqual(e2.message, 'Command timed out after 100ms (keyspaceAdminTimeout timed out)'); + assert.strictEqual(e2.timeoutType, 'keyspaceAdminTimeoutMs'); + assert.strictEqual(e2.message, 'Command timed out after 100ms (keyspaceAdminTimeoutMs timed out)'); await new Promise(resolve => setTimeout(resolve, 51)); [timeout, mkError] = tm.advance(info(tm)); @@ -253,8 +253,8 @@ describe('unit.lib.api.timeouts', () => { const e3 = mkError(); assert.ok(e3 instanceof TimeoutError); assert.deepStrictEqual(e3.info, info(tm)); - assert.strictEqual(e3.timeoutType, 'keyspaceAdminTimeout'); - assert.strictEqual(e3.message, 'Command timed out after 100ms (keyspaceAdminTimeout timed out)'); + assert.strictEqual(e3.timeoutType, 'keyspaceAdminTimeoutMs'); + assert.strictEqual(e3.message, 'Command timed out after 100ms (keyspaceAdminTimeoutMs timed out)'); }); }); }); From 95ce369793cd55cbbc72632538a9935dea1ba142 Mon Sep 17 00:00:00 2001 From: toptobes Date: Mon, 18 Nov 2024 02:11:55 +0530 Subject: [PATCH 06/10] refined WithTimeout types --- src/administration/astra-admin.ts | 9 +++--- src/administration/astra-db-admin.ts | 24 +++++++-------- src/administration/data-api-db-admin.ts | 9 +++--- src/administration/db-admin.ts | 8 ++--- .../types/admin/admin-common.ts | 6 ++-- .../types/admin/create-database.ts | 5 ++-- .../types/admin/drop-database.ts | 23 +++++++++++++++ .../types/admin/list-databases.ts | 2 +- .../types/db-admin/astra-create-keyspace.ts | 3 +- .../types/db-admin/astra-drop-keyspace.ts | 21 ++++++++++++++ .../types/db-admin/local-create-keyspace.ts | 4 +-- src/administration/types/index.ts | 12 +++++--- src/client/parsers/spawn-admin.ts | 2 +- src/db/db.ts | 8 ++--- src/db/types/collections/create-collection.ts | 6 ++-- src/db/types/collections/drop-collection.ts | 2 +- src/db/types/collections/list-collections.ts | 2 +- src/db/types/command.ts | 2 +- src/db/types/tables/alter-table.ts | 2 +- src/db/types/tables/create-table.ts | 2 +- src/db/types/tables/drop-table.ts | 2 +- src/db/types/tables/list-tables.ts | 2 +- src/documents/collections/collection.ts | 12 ++++---- .../collections/types/find/find-one-delete.ts | 2 +- .../types/find/find-one-replace.ts | 2 +- .../collections/types/find/find-one-update.ts | 2 +- src/documents/commands/command-impls.ts | 8 ++--- .../commands/types/delete/delete-one.ts | 2 +- .../commands/types/find/find-one-delete.ts | 2 +- .../commands/types/find/find-one-replace.ts | 2 +- .../commands/types/find/find-one-update.ts | 2 +- src/documents/commands/types/find/find-one.ts | 2 +- src/documents/commands/types/find/find.ts | 2 +- .../commands/types/insert/insert-many.ts | 4 +-- .../commands/types/update/replace-one.ts | 2 +- .../commands/types/update/update-many.ts | 2 +- .../commands/types/update/update-one.ts | 2 +- src/documents/tables/table.ts | 14 ++++----- .../tables/types/indexes/create-index.ts | 2 +- .../types/indexes/create-vector-index.ts | 2 +- src/lib/api/index.ts | 1 + src/lib/api/timeouts.ts | 29 ++++++++++++++++--- 42 files changed, 162 insertions(+), 90 deletions(-) create mode 100644 src/administration/types/admin/drop-database.ts create mode 100644 src/administration/types/db-admin/astra-drop-keyspace.ts diff --git a/src/administration/astra-admin.ts b/src/administration/astra-admin.ts index 8733660f..3f35f7d8 100644 --- a/src/administration/astra-admin.ts +++ b/src/administration/astra-admin.ts @@ -14,8 +14,6 @@ // noinspection ExceptionCaughtLocallyJS import { - AdminSpawnOptions, - AstraAdminBlockingOptions, AstraDatabaseConfig, CreateAstraDatabaseOptions, ListAstraDatabasesOptions, @@ -31,10 +29,11 @@ import { parseAdminSpawnOpts } from '@/src/client/parsers/spawn-admin'; import { InternalRootClientOpts } from '@/src/client/types/internal'; import { buildAstraEndpoint } from '@/src/lib/utils'; import { Logger } from '@/src/lib/logging/logger'; -import { DbSpawnOptions } from '@/src/client'; +import { AdminSpawnOptions, DbSpawnOptions } from '@/src/client'; import { $CustomInspect } from '@/src/lib/constants'; import { SomeDoc } from '@/src/documents'; import { Timeouts } from '@/src/lib/api/timeouts'; +import { DropAstraDatabaseOptions } from '@/src/administration/types/admin/drop-database'; /** * An administrative class for managing Astra databases, including creating, listing, and deleting databases. @@ -286,7 +285,7 @@ export class AstraAdmin { * * @returns A promise that resolves to the complete database information. */ - public async dbInfo(id: string, options?: WithTimeout): Promise { + public async dbInfo(id: string, options?: WithTimeout<'databaseAdminTimeoutMs'>): Promise { const tm = this.#httpClient.tm.single('databaseAdminTimeoutMs', options); const resp = await this.#httpClient.request({ @@ -459,7 +458,7 @@ export class AstraAdmin { * * @remarks Use with caution. Wear a harness. Don't say I didn't warn you. */ - public async dropDatabase(db: Db | string, options?: AstraAdminBlockingOptions): Promise { + public async dropDatabase(db: Db | string, options?: DropAstraDatabaseOptions): Promise { const id = typeof db === 'string' ? db : db.id; const tm = this.#httpClient.tm.multipart('databaseAdminTimeoutMs', options); diff --git a/src/administration/astra-db-admin.ts b/src/administration/astra-db-admin.ts index 5794bb53..1d86932a 100644 --- a/src/administration/astra-db-admin.ts +++ b/src/administration/astra-db-admin.ts @@ -14,18 +14,17 @@ // noinspection ExceptionCaughtLocallyJS import { - AdminSpawnOptions, - AstraAdminBlockingOptions, AstraCreateKeyspaceOptions, + AstraDropKeyspaceOptions, } from '@/src/administration/types'; import { DbAdmin } from '@/src/administration/db-admin'; -import type { nullish, WithTimeout } from '@/src/lib'; +import type { WithTimeout } from '@/src/lib'; +import { StaticTokenProvider, TokenProvider } from '@/src/lib'; import { buildAstraDatabaseAdminInfo, extractAstraEnvironment } from '@/src/administration/utils'; import { FindEmbeddingProvidersResult } from '@/src/administration/types/db-admin/find-embedding-providers'; import { DEFAULT_DEVOPS_API_ENDPOINTS, HttpMethods } from '@/src/lib/api/constants'; import { DevOpsAPIHttpClient } from '@/src/lib/api/clients/devops-api-http-client'; import { Db } from '@/src/db'; -import { StaticTokenProvider, TokenProvider } from '@/src/lib'; import { isNullish } from '@/src/lib/utils'; import { parseAdminSpawnOpts } from '@/src/client/parsers/spawn-admin'; import { InternalRootClientOpts } from '@/src/client/types/internal'; @@ -33,6 +32,7 @@ import { $CustomInspect } from '@/src/lib/constants'; import { AstraDbAdminInfo } from '@/src/administration/types/admin/database-info'; import { Logger } from '@/src/lib/logging/logger'; import { TimeoutManager, Timeouts } from '@/src/lib/api/timeouts'; +import { AdminSpawnOptions } from '@/src/client'; /** * An administrative class for managing Astra databases, including creating, listing, and deleting keyspaces. @@ -154,7 +154,7 @@ export class AstraDbAdmin extends DbAdmin { * * @returns The available embedding providers. */ - public override async findEmbeddingProviders(options?: WithTimeout): Promise { + public override async findEmbeddingProviders(options?: WithTimeout<'databaseAdminTimeoutMs'>): Promise { const resp = await this.#db._httpClient.executeCommand({ findEmbeddingProviders: {} }, { timeoutManager: this.#httpClient.tm.single('databaseAdminTimeoutMs', options), keyspace: null, @@ -177,9 +177,9 @@ export class AstraDbAdmin extends DbAdmin { * * @returns A promise that resolves to the complete database information. */ - public async info(options?: WithTimeout): Promise { + public async info(options?: WithTimeout<'databaseAdminTimeoutMs'>): Promise { const tm = this.#httpClient.tm.single('databaseAdminTimeoutMs', options); - return this.#info(options, tm); + return this.#info(tm); } /** @@ -198,9 +198,9 @@ export class AstraDbAdmin extends DbAdmin { * * @returns A promise that resolves to list of all the keyspaces in the database. */ - public override async listKeyspaces(options?: WithTimeout): Promise { + public override async listKeyspaces(options?: WithTimeout<'keyspaceAdminTimeoutMs'>): Promise { const tm = this.#httpClient.tm.single('keyspaceAdminTimeoutMs', options); - return this.#info(options, tm).then(i => i.keyspaces); + return this.#info(tm).then(i => i.keyspaces); } /** @@ -284,7 +284,7 @@ export class AstraDbAdmin extends DbAdmin { * * @returns A promise that resolves when the operation completes. */ - public override async dropKeyspace(keyspace: string, options?: AstraAdminBlockingOptions): Promise { + public override async dropKeyspace(keyspace: string, options?: AstraDropKeyspaceOptions): Promise { const tm = this.#httpClient.tm.multipart('keyspaceAdminTimeoutMs', options); await this.#httpClient.requestLongRunning({ @@ -321,7 +321,7 @@ export class AstraDbAdmin extends DbAdmin { * * @remarks Use with caution. Use a surge protector. Don't say I didn't warn you. */ - public async drop(options?: AstraAdminBlockingOptions): Promise { + public async drop(options?: AstraDropKeyspaceOptions): Promise { const tm = this.#httpClient.tm.multipart('databaseAdminTimeoutMs', options); await this.#httpClient.requestLongRunning({ @@ -341,7 +341,7 @@ export class AstraDbAdmin extends DbAdmin { return this.#httpClient; } - async #info(options: WithTimeout | nullish, tm: TimeoutManager): Promise { + async #info(tm: TimeoutManager): Promise { const resp = await this.#httpClient.request({ method: HttpMethods.Get, path: `/databases/${this.#db.id}`, diff --git a/src/administration/data-api-db-admin.ts b/src/administration/data-api-db-admin.ts index 44b7e9e4..7d64de8d 100644 --- a/src/administration/data-api-db-admin.ts +++ b/src/administration/data-api-db-admin.ts @@ -13,7 +13,7 @@ // limitations under the License. // noinspection ExceptionCaughtLocallyJS -import { AdminSpawnOptions, LocalCreateKeyspaceOptions } from '@/src/administration/types'; +import { LocalCreateKeyspaceOptions } from '@/src/administration/types'; import { DbAdmin } from '@/src/administration/db-admin'; import type { WithTimeout } from '@/src/lib'; import { FindEmbeddingProvidersResult } from '@/src/administration/types/db-admin/find-embedding-providers'; @@ -21,6 +21,7 @@ import { DataAPIHttpClient } from '@/src/lib/api/clients/data-api-http-client'; import { Db } from '@/src/db'; import { parseAdminSpawnOpts } from '@/src/client/parsers/spawn-admin'; import { $CustomInspect } from '@/src/lib/constants'; +import { AdminSpawnOptions } from '@/src/client'; /** * An administrative class for managing non-Astra databases, including creating, listing, and deleting keyspaces. @@ -112,7 +113,7 @@ export class DataAPIDbAdmin extends DbAdmin { * * @returns The available embedding providers. */ - public override async findEmbeddingProviders(options?: WithTimeout): Promise { + public override async findEmbeddingProviders(options?: WithTimeout<'databaseAdminTimeoutMs'>): Promise { const resp = await this.#httpClient.executeCommand({ findEmbeddingProviders: {} }, { timeoutManager: this.#httpClient.tm.single('databaseAdminTimeoutMs', options), keyspace: null, @@ -136,7 +137,7 @@ export class DataAPIDbAdmin extends DbAdmin { * * @returns A promise that resolves to list of all the keyspaces in the database. */ - public override async listKeyspaces(options?: WithTimeout): Promise { + public override async listKeyspaces(options?: WithTimeout<'keyspaceAdminTimeoutMs'>): Promise { const resp = await this.#httpClient.executeCommand({ findKeyspaces: {} }, { timeoutManager: this.#httpClient.tm.single('keyspaceAdminTimeoutMs', options), keyspace: null, @@ -211,7 +212,7 @@ export class DataAPIDbAdmin extends DbAdmin { * * @returns A promise that resolves when the operation completes. */ - public override async dropKeyspace(keyspace: string, options?: WithTimeout): Promise { + public override async dropKeyspace(keyspace: string, options?: WithTimeout<'keyspaceAdminTimeoutMs'>): Promise { await this.#httpClient.executeCommand({ dropKeyspace: { name: keyspace } }, { timeoutManager: this.#httpClient.tm.single('keyspaceAdminTimeoutMs', options), keyspace: null, diff --git a/src/administration/db-admin.ts b/src/administration/db-admin.ts index 237f660f..70e826dd 100644 --- a/src/administration/db-admin.ts +++ b/src/administration/db-admin.ts @@ -63,7 +63,7 @@ export abstract class DbAdmin { * * @returns The available embedding providers. */ - abstract findEmbeddingProviders(options?: WithTimeout): Promise; + abstract findEmbeddingProviders(options?: WithTimeout<'databaseAdminTimeoutMs'>): Promise; /** * Retrieves a list of all the keyspaces in the database. * @@ -80,7 +80,7 @@ export abstract class DbAdmin { * * @returns A promise that resolves to list of all the keyspaces in the database. */ - abstract listKeyspaces(): Promise; + abstract listKeyspaces(options?: WithTimeout<'keyspaceAdminTimeoutMs'>): Promise; /** * Creates a new, additional, keyspace for this database. * @@ -111,7 +111,7 @@ export abstract class DbAdmin { * * @returns A promise that resolves when the operation completes. */ - abstract createKeyspace(keyspace: string, options?: WithTimeout): Promise; + abstract createKeyspace(keyspace: string, options?: WithTimeout<'keyspaceAdminTimeoutMs'>): Promise; /** * Drops a keyspace from this database. * @@ -143,5 +143,5 @@ export abstract class DbAdmin { * * @returns A promise that resolves when the operation completes. */ - abstract dropKeyspace(keyspace: string, options?: WithTimeout): Promise; + abstract dropKeyspace(keyspace: string, options?: WithTimeout<'keyspaceAdminTimeoutMs'>): Promise; } diff --git a/src/administration/types/admin/admin-common.ts b/src/administration/types/admin/admin-common.ts index 4bb9529a..6fdb1eec 100644 --- a/src/administration/types/admin/admin-common.ts +++ b/src/administration/types/admin/admin-common.ts @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -import type { WithTimeout } from '@/src/lib'; - /** * Represents the available cloud providers that Astra offers. * @@ -89,7 +87,7 @@ export type AstraAdminBlockingOptions = * * @public */ -export interface AstraPollBlockingOptions extends WithTimeout { +export interface AstraPollBlockingOptions { /** * True or omitted to block until the operation is complete. */ @@ -111,7 +109,7 @@ export interface AstraPollBlockingOptions extends WithTimeout { * * @public */ -export interface AstraNoBlockingOptions extends WithTimeout { +export interface AstraNoBlockingOptions { /** * False to not block until the operation is complete. */ diff --git a/src/administration/types/admin/create-database.ts b/src/administration/types/admin/create-database.ts index 4b711974..5c6f4a26 100644 --- a/src/administration/types/admin/create-database.ts +++ b/src/administration/types/admin/create-database.ts @@ -16,6 +16,7 @@ import { AstraAdminBlockingOptions, AstraDbCloudProvider } from '@/src/administr import { DbSpawnOptions } from '@/src/client'; +import { WithTimeout } from '@/src/lib'; /** * Represents the options for creating a database. @@ -46,11 +47,11 @@ export interface AstraDatabaseConfig { } /** - * Represents the options for creating a database (i.e. blocking options + database spawn options). + * Represents the options for creating a database (i.e. blocking options + timeout options + database spawn options). * * @public */ -export type CreateAstraDatabaseOptions = AstraAdminBlockingOptions & { +export type CreateAstraDatabaseOptions = AstraAdminBlockingOptions & WithTimeout<'databaseAdminTimeoutMs'> & { /** * Any options to override the default options set when creating the root {@link DataAPIClient}. */ diff --git a/src/administration/types/admin/drop-database.ts b/src/administration/types/admin/drop-database.ts new file mode 100644 index 00000000..bcc5192e --- /dev/null +++ b/src/administration/types/admin/drop-database.ts @@ -0,0 +1,23 @@ +// Copyright DataStax, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { AstraAdminBlockingOptions } from '@/src/administration/types'; +import { WithTimeout } from '@/src/lib'; + +/** + * Represents the options for dropping a database (i.e. blocking options + timeout options). + * + * @public + */ +export type DropAstraDatabaseOptions = AstraAdminBlockingOptions & WithTimeout<'databaseAdminTimeoutMs'>; diff --git a/src/administration/types/admin/list-databases.ts b/src/administration/types/admin/list-databases.ts index 94fec309..c3be169c 100644 --- a/src/administration/types/admin/list-databases.ts +++ b/src/administration/types/admin/list-databases.ts @@ -39,7 +39,7 @@ export type AstraDbCloudProviderFilter = AstraDbCloudProvider | 'ALL'; * * @public */ -export interface ListAstraDatabasesOptions extends WithTimeout { +export interface ListAstraDatabasesOptions extends WithTimeout<'databaseAdminTimeoutMs'> { /** * Allows filtering so that databases in listed states are returned. */ diff --git a/src/administration/types/db-admin/astra-create-keyspace.ts b/src/administration/types/db-admin/astra-create-keyspace.ts index 7e4e5069..efbf5691 100644 --- a/src/administration/types/db-admin/astra-create-keyspace.ts +++ b/src/administration/types/db-admin/astra-create-keyspace.ts @@ -13,6 +13,7 @@ // limitations under the License. import { AstraAdminBlockingOptions } from '@/src/administration/types'; +import { WithTimeout } from '@/src/lib'; /** * Represents the common options for creating a keyspace through the `astra-db-ts` client. @@ -42,4 +43,4 @@ import { AstraAdminBlockingOptions } from '@/src/administration/types'; * * @public */ -export type AstraCreateKeyspaceOptions = AstraAdminBlockingOptions & { updateDbKeyspace?: boolean }; +export type AstraCreateKeyspaceOptions = AstraAdminBlockingOptions & WithTimeout<'keyspaceAdminTimeoutMs'> & { updateDbKeyspace?: boolean }; diff --git a/src/administration/types/db-admin/astra-drop-keyspace.ts b/src/administration/types/db-admin/astra-drop-keyspace.ts new file mode 100644 index 00000000..d68f01be --- /dev/null +++ b/src/administration/types/db-admin/astra-drop-keyspace.ts @@ -0,0 +1,21 @@ +// Copyright DataStax, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { AstraAdminBlockingOptions } from '@/src/administration/types'; +import { WithTimeout } from '@/src/lib'; + +/** + * @public + */ +export type AstraDropKeyspaceOptions = AstraAdminBlockingOptions & WithTimeout<'keyspaceAdminTimeoutMs'>; diff --git a/src/administration/types/db-admin/local-create-keyspace.ts b/src/administration/types/db-admin/local-create-keyspace.ts index 9d313ef9..8759bbbc 100644 --- a/src/administration/types/db-admin/local-create-keyspace.ts +++ b/src/administration/types/db-admin/local-create-keyspace.ts @@ -19,7 +19,7 @@ import { WithTimeout } from '@/src/lib'; * * If no replication options are provided, it will default to `'SimpleStrategy'` with a replication factor of `1`. * - * See {@link AdminBlockingOptions} for more options about blocking behavior. + * See {@link AstraAdminBlockingOptions} for more options about blocking behavior. * * If `updateDbKeyspace` is set to true, the underlying `Db` instance used to create the `DbAdmin` will have its * current working keyspace set to the newly created keyspace immediately (even if the keyspace isn't technically @@ -42,7 +42,7 @@ import { WithTimeout } from '@/src/lib'; * * @public */ -export interface LocalCreateKeyspaceOptions extends WithTimeout { +export interface LocalCreateKeyspaceOptions extends WithTimeout<'keyspaceAdminTimeoutMs'> { replication?: KeyspaceReplicationOptions, updateDbKeyspace?: boolean, } diff --git a/src/administration/types/index.ts b/src/administration/types/index.ts index 0323c39c..9c4bee6d 100644 --- a/src/administration/types/index.ts +++ b/src/administration/types/index.ts @@ -25,6 +25,10 @@ export type { AstraDatabaseConfig, } from './admin/create-database'; +export type { + DropAstraDatabaseOptions, +} from './admin/drop-database'; + export type { AstraDbRegionInfo, AstraDbInfo, @@ -38,14 +42,14 @@ export type { ListAstraDatabasesOptions, } from './admin/list-databases'; -export type { - AdminSpawnOptions, -} from '../../client/types/spawn-admin'; - export type { AstraCreateKeyspaceOptions, } from './db-admin/astra-create-keyspace'; +export type { + AstraDropKeyspaceOptions, +} from './db-admin/astra-drop-keyspace'; + export type { KeyspaceReplicationOptions, LocalCreateKeyspaceOptions, diff --git a/src/client/parsers/spawn-admin.ts b/src/client/parsers/spawn-admin.ts index deb3baee..87fa6145 100644 --- a/src/client/parsers/spawn-admin.ts +++ b/src/client/parsers/spawn-admin.ts @@ -14,8 +14,8 @@ import { p, Parser } from '@/src/lib/validation'; import { TokenProvider } from '@/src/lib'; -import { AdminSpawnOptions } from '@/src/administration'; import { Logger } from '@/src/lib/logging/logger'; +import { AdminSpawnOptions } from '@/src/client'; export const parseAdminSpawnOpts: Parser = (raw, field) => { const opts = p.parse('object?')(raw, field); diff --git a/src/db/db.ts b/src/db/db.ts index 04d0f820..c804a700 100644 --- a/src/db/db.ts +++ b/src/db/db.ts @@ -17,7 +17,7 @@ import { DEFAULT_KEYSPACE, RawDataAPIResponse, WithTimeout } from '@/src/lib/api import { AstraDbAdmin } from '@/src/administration/astra-db-admin'; import { DataAPIEnvironment, nullish } from '@/src/lib/types'; import { extractDbIdFromUrl, extractRegionFromUrl } from '@/src/documents/utils'; -import { AdminSpawnOptions, DbAdmin } from '@/src/administration'; +import { DbAdmin } from '@/src/administration'; import { DataAPIDbAdmin } from '@/src/administration/data-api-db-admin'; import { CreateCollectionOptions } from '@/src/db/types/collections/create-collection'; import { TokenProvider } from '@/src/lib'; @@ -36,7 +36,7 @@ import { InferTableSchemaFromDefinition } from '@/src/db/types/tables/table-sche import { DropTableOptions } from '@/src/db/types/tables/drop-table'; import { FullTableInfo, ListTablesOptions } from '@/src/db/types/tables/list-tables'; import { parseDbSpawnOpts } from '@/src/client/parsers/spawn-db'; -import { DbSpawnOptions } from '@/src/client/types'; +import { AdminSpawnOptions, DbSpawnOptions } from '@/src/client/types'; import { InternalRootClientOpts } from '@/src/client/types/internal'; import { Logger } from '@/src/lib/logging/logger'; import { $CustomInspect } from '@/src/lib/constants'; @@ -436,7 +436,7 @@ export class Db { * * @throws Error - if the database is not an Astra database. */ - public async info(options?: WithTimeout): Promise { + public async info(options?: WithTimeout<'databaseAdminTimeoutMs'>): Promise { if (this.#defaultOpts.environment !== 'astra') { throw new InvalidEnvironmentError('db.info()', this.#defaultOpts.environment, ['astra'], 'info() is only available for Astra databases'); } @@ -1031,7 +1031,7 @@ export class Db { }); } - public async dropTableIndex(name: string, options?: WithTimeout): Promise { + public async dropTableIndex(name: string, options?: WithTimeout<'tableAdminTimeoutMs'>): Promise { await this.#httpClient.executeCommand({ dropIndex: { name } }, { timeoutManager: this.#httpClient.tm.single('tableAdminTimeoutMs', options), }); diff --git a/src/db/types/collections/create-collection.ts b/src/db/types/collections/create-collection.ts index d9026d2c..ac82fa87 100644 --- a/src/db/types/collections/create-collection.ts +++ b/src/db/types/collections/create-collection.ts @@ -13,7 +13,7 @@ // limitations under the License. import { SomeDoc } from '@/src/documents/collections'; -import type { WithTimeout } from '@/src/lib'; +import { TimeoutDescriptor } from '@/src/lib'; import { CollectionOptions, CollectionSpawnOptions } from '@/src/db'; /** @@ -31,4 +31,6 @@ import { CollectionOptions, CollectionSpawnOptions } from '@/src/db'; * * @public */ -export interface CreateCollectionOptions extends WithTimeout, CollectionOptions, CollectionSpawnOptions {} +export interface CreateCollectionOptions extends CollectionOptions, CollectionSpawnOptions { + timeout?: number | Pick, 'collectionAdminTimeoutMs'>; +} diff --git a/src/db/types/collections/drop-collection.ts b/src/db/types/collections/drop-collection.ts index c8ba0358..66764ea0 100644 --- a/src/db/types/collections/drop-collection.ts +++ b/src/db/types/collections/drop-collection.ts @@ -25,4 +25,4 @@ import { WithKeyspace } from '@/src/db'; * * @public */ -export interface DropCollectionOptions extends WithTimeout, WithKeyspace {} +export interface DropCollectionOptions extends WithTimeout<'collectionAdminTimeoutMs'>, WithKeyspace {} diff --git a/src/db/types/collections/list-collections.ts b/src/db/types/collections/list-collections.ts index 408764f5..1e4252aa 100644 --- a/src/db/types/collections/list-collections.ts +++ b/src/db/types/collections/list-collections.ts @@ -27,7 +27,7 @@ import { SomeDoc } from '@/src/documents'; * * @public */ -export interface ListCollectionsOptions extends WithTimeout, WithKeyspace { +export interface ListCollectionsOptions extends WithTimeout<'collectionAdminTimeoutMs'>, WithKeyspace { /** * If true, only the name of the collections is returned. * diff --git a/src/db/types/command.ts b/src/db/types/command.ts index 6a5fc124..29138179 100644 --- a/src/db/types/command.ts +++ b/src/db/types/command.ts @@ -24,7 +24,7 @@ import { WithTimeout } from '@/src/lib'; * * @public */ -export interface RunCommandOptions extends WithTimeout { +export interface RunCommandOptions extends WithTimeout<'generalMethodTimeoutMs'> { /** * The collections to run the command on. If not provided, the command is run on the database. */ diff --git a/src/db/types/tables/alter-table.ts b/src/db/types/tables/alter-table.ts index f811a5f0..685fb956 100644 --- a/src/db/types/tables/alter-table.ts +++ b/src/db/types/tables/alter-table.ts @@ -17,7 +17,7 @@ import { Cols2CqlTypes, CreateTableColumnDefinitions, Normalize, VectorizeServic import { WithTimeout } from '@/src/lib'; import { EmptyObj } from '@/src/lib/types'; -export interface AlterTableOptions extends WithTimeout { +export interface AlterTableOptions extends WithTimeout<'tableAdminTimeoutMs'> { operation: AlterTableOperations, // ifExists?: boolean, } diff --git a/src/db/types/tables/create-table.ts b/src/db/types/tables/create-table.ts index b562f9d4..f2b37cfd 100644 --- a/src/db/types/tables/create-table.ts +++ b/src/db/types/tables/create-table.ts @@ -33,7 +33,7 @@ import { SomeRow } from '@/src/documents'; * * @public */ -export interface CreateTableOptions extends WithTimeout, TableSpawnOptions { +export interface CreateTableOptions extends WithTimeout<'tableAdminTimeoutMs'>, TableSpawnOptions { definition: Def, ifNotExists?: boolean, } diff --git a/src/db/types/tables/drop-table.ts b/src/db/types/tables/drop-table.ts index 39b650ef..80577713 100644 --- a/src/db/types/tables/drop-table.ts +++ b/src/db/types/tables/drop-table.ts @@ -15,4 +15,4 @@ import type { WithTimeout } from '@/src/lib'; import { WithKeyspace } from '@/src/db'; -export interface DropTableOptions extends WithTimeout, WithKeyspace {} +export interface DropTableOptions extends WithTimeout<'tableAdminTimeoutMs'>, WithKeyspace {} diff --git a/src/db/types/tables/list-tables.ts b/src/db/types/tables/list-tables.ts index 7e45e014..c5f5e79c 100644 --- a/src/db/types/tables/list-tables.ts +++ b/src/db/types/tables/list-tables.ts @@ -15,7 +15,7 @@ import type { WithTimeout } from '@/src/lib'; import { FullCreateTablePrimaryKeyDefinition, StrictCreateTableColumnDefinition, WithKeyspace } from '@/src/db'; -export interface ListTablesOptions extends WithTimeout, WithKeyspace { +export interface ListTablesOptions extends WithTimeout<'tableAdminTimeoutMs'>, WithKeyspace { nameOnly?: boolean, } diff --git a/src/documents/collections/collection.ts b/src/documents/collections/collection.ts index 71c603ae..e7f03abc 100644 --- a/src/documents/collections/collection.ts +++ b/src/documents/collections/collection.ts @@ -273,7 +273,7 @@ export class Collection { * * @returns The ID of the inserted document. */ - public async insertOne(document: MaybeId, options?: WithTimeout): Promise> { + public async insertOne(document: MaybeId, options?: WithTimeout<'generalMethodTimeoutMs'>): Promise> { return this.#commands.insertOne(document, options); } @@ -678,7 +678,7 @@ export class Collection { * * @see StrictFilter */ - public async deleteMany(filter: Filter, options?: WithTimeout): Promise { + public async deleteMany(filter: Filter, options?: WithTimeout<'generalMethodTimeoutMs'>): Promise { return this.#commands.deleteMany(filter, options); } @@ -1288,7 +1288,7 @@ export class Collection { * * @see StrictFilter */ - public async countDocuments(filter: Filter, upperBound: number, options?: WithTimeout): Promise { + public async countDocuments(filter: Filter, upperBound: number, options?: WithTimeout<'generalMethodTimeoutMs'>): Promise { return this.#commands.countDocuments(filter, upperBound, options, TooManyDocumentsToCountError); } @@ -1313,7 +1313,7 @@ export class Collection { * * @returns The estimated number of documents in the collection */ - public async estimatedDocumentCount(options?: WithTimeout): Promise { + public async estimatedDocumentCount(options?: WithTimeout<'generalMethodTimeoutMs'>): Promise { return this.#commands.estimatedDocumentCount(options); } @@ -1562,7 +1562,7 @@ export class Collection { * * @returns The options that the collection was created with (i.e. the `vector` and `indexing` operations). */ - public async options(options?: WithTimeout): Promise> { + public async options(options?: WithTimeout<'collectionAdminTimeoutMs'>): Promise> { const results = await this.#db.listCollections({ timeout: options?.timeout, keyspace: this.keyspace }); const collection = results.find((c) => c.name === this.name); @@ -1597,7 +1597,7 @@ export class Collection { * * @remarks Use with caution. Wear your safety goggles. Don't say I didn't warn you. */ - public async drop(options?: WithTimeout): Promise { + public async drop(options?: WithTimeout<'collectionAdminTimeoutMs'>): Promise { await this.#db.dropCollection(this.name, { keyspace: this.keyspace, ...options }); } diff --git a/src/documents/collections/types/find/find-one-delete.ts b/src/documents/collections/types/find/find-one-delete.ts index f9efcfbf..72cf450c 100644 --- a/src/documents/collections/types/find/find-one-delete.ts +++ b/src/documents/collections/types/find/find-one-delete.ts @@ -26,7 +26,7 @@ import type { WithTimeout } from '@/src/lib'; * * @public */ -export interface CollectionFindOneAndDeleteOptions extends WithTimeout { +export interface CollectionFindOneAndDeleteOptions extends WithTimeout<'generalMethodTimeoutMs'> { /** * The order in which to apply the update if the filter selects multiple documents. * diff --git a/src/documents/collections/types/find/find-one-replace.ts b/src/documents/collections/types/find/find-one-replace.ts index 81313637..abf7b888 100644 --- a/src/documents/collections/types/find/find-one-replace.ts +++ b/src/documents/collections/types/find/find-one-replace.ts @@ -28,7 +28,7 @@ import type { WithTimeout } from '@/src/lib'; * * @public */ -export interface CollectionFindOneAndReplaceOptions extends WithTimeout { +export interface CollectionFindOneAndReplaceOptions extends WithTimeout<'generalMethodTimeoutMs'> { /** * Specifies whether to return the document before or after the update. * diff --git a/src/documents/collections/types/find/find-one-update.ts b/src/documents/collections/types/find/find-one-update.ts index b30396bc..98ea4457 100644 --- a/src/documents/collections/types/find/find-one-update.ts +++ b/src/documents/collections/types/find/find-one-update.ts @@ -29,7 +29,7 @@ import type { WithTimeout } from '@/src/lib'; * * @public */ -export interface CollectionFindOneAndUpdateOptions extends WithTimeout { +export interface CollectionFindOneAndUpdateOptions extends WithTimeout<'generalMethodTimeoutMs'> { /** * Specifies whether to return the document before or after the update. * diff --git a/src/documents/commands/command-impls.ts b/src/documents/commands/command-impls.ts index 16ddefc5..9598a4ea 100644 --- a/src/documents/commands/command-impls.ts +++ b/src/documents/commands/command-impls.ts @@ -57,7 +57,7 @@ export class CommandImpls { this.#tOrC = tOrC; } - public async insertOne(_document: SomeDoc, options: WithTimeout | nullish): Promise> { + public async insertOne(_document: SomeDoc, options: WithTimeout<'generalMethodTimeoutMs'> | nullish): Promise> { const document = this.#serdes.serializeRecord(_document); const command = mkBasicCmd('insertOne', { @@ -192,7 +192,7 @@ export class CommandImpls { }; } - public async deleteMany(_filter: SomeDoc, options?: WithTimeout): Promise { + public async deleteMany(_filter: SomeDoc, options?: WithTimeout<'generalMethodTimeoutMs'>): Promise { const filter = this.#serdes.serializeRecord(_filter); const command = mkBasicCmd('deleteMany', { @@ -334,7 +334,7 @@ export class CommandImpls { return ret; } - public async countDocuments(_filter: SomeDoc, upperBound: number, options: WithTimeout | undefined, error: new (count: number, hitLimit: boolean) => Error): Promise { + public async countDocuments(_filter: SomeDoc, upperBound: number, options: WithTimeout<'generalMethodTimeoutMs'> | undefined, error: new (count: number, hitLimit: boolean) => Error): Promise { if (!upperBound) { throw new Error('upperBound is required'); } @@ -365,7 +365,7 @@ export class CommandImpls { return resp.status?.count; } - public async estimatedDocumentCount(options?: WithTimeout): Promise { + public async estimatedDocumentCount(options?: WithTimeout<'generalMethodTimeoutMs'>): Promise { const command = mkBasicCmd('estimatedDocumentCount', {}); const resp = await this.#httpClient.executeCommand(command, { diff --git a/src/documents/commands/types/delete/delete-one.ts b/src/documents/commands/types/delete/delete-one.ts index ea1c8091..066907eb 100644 --- a/src/documents/commands/types/delete/delete-one.ts +++ b/src/documents/commands/types/delete/delete-one.ts @@ -19,6 +19,6 @@ export interface GenericDeleteOneResult { deletedCount: 0 | 1; } -export interface GenericDeleteOneOptions extends WithTimeout { +export interface GenericDeleteOneOptions extends WithTimeout<'generalMethodTimeoutMs'> { sort?: Sort, } diff --git a/src/documents/commands/types/find/find-one-delete.ts b/src/documents/commands/types/find/find-one-delete.ts index f3b47c6c..efa0ed26 100644 --- a/src/documents/commands/types/find/find-one-delete.ts +++ b/src/documents/commands/types/find/find-one-delete.ts @@ -15,7 +15,7 @@ import { Projection, Sort } from '@/src/documents'; import { WithTimeout } from '@/src/lib'; -export interface GenericFindOneAndDeleteOptions extends WithTimeout { +export interface GenericFindOneAndDeleteOptions extends WithTimeout<'generalMethodTimeoutMs'> { sort?: Sort, projection?: Projection, } diff --git a/src/documents/commands/types/find/find-one-replace.ts b/src/documents/commands/types/find/find-one-replace.ts index ac666309..3e41c3e5 100644 --- a/src/documents/commands/types/find/find-one-replace.ts +++ b/src/documents/commands/types/find/find-one-replace.ts @@ -15,7 +15,7 @@ import { Projection, Sort } from '@/src/documents'; import { WithTimeout } from '@/src/lib'; -export interface GenericFindOneAndReplaceOptions extends WithTimeout { +export interface GenericFindOneAndReplaceOptions extends WithTimeout<'generalMethodTimeoutMs'> { returnDocument?: 'before' | 'after', upsert?: boolean, sort?: Sort, diff --git a/src/documents/commands/types/find/find-one-update.ts b/src/documents/commands/types/find/find-one-update.ts index d34491a1..f964fab2 100644 --- a/src/documents/commands/types/find/find-one-update.ts +++ b/src/documents/commands/types/find/find-one-update.ts @@ -15,7 +15,7 @@ import { Projection, Sort } from '@/src/documents'; import { WithTimeout } from '@/src/lib'; -export interface GenericFindOneAndUpdateOptions extends WithTimeout { +export interface GenericFindOneAndUpdateOptions extends WithTimeout<'generalMethodTimeoutMs'> { returnDocument?: 'before' | 'after', upsert?: boolean, sort?: Sort, diff --git a/src/documents/commands/types/find/find-one.ts b/src/documents/commands/types/find/find-one.ts index 80235ef4..74a146df 100644 --- a/src/documents/commands/types/find/find-one.ts +++ b/src/documents/commands/types/find/find-one.ts @@ -15,7 +15,7 @@ import type { Projection, Sort } from '@/src/documents'; import { WithTimeout } from '@/src/lib'; -export interface GenericFindOneOptions extends WithTimeout { +export interface GenericFindOneOptions extends WithTimeout<'generalMethodTimeoutMs'> { sort?: Sort, projection?: Projection, includeSimilarity?: boolean, diff --git a/src/documents/commands/types/find/find.ts b/src/documents/commands/types/find/find.ts index 946e1f67..efe7b24f 100644 --- a/src/documents/commands/types/find/find.ts +++ b/src/documents/commands/types/find/find.ts @@ -15,7 +15,7 @@ import { Projection, Sort } from '@/src/documents'; import { WithTimeout } from '@/src/lib'; -export interface GenericFindOptions extends WithTimeout { +export interface GenericFindOptions extends WithTimeout<'generalMethodTimeoutMs'> { sort?: Sort, projection?: Projection, limit?: number, diff --git a/src/documents/commands/types/insert/insert-many.ts b/src/documents/commands/types/insert/insert-many.ts index 8943fb92..1199c62e 100644 --- a/src/documents/commands/types/insert/insert-many.ts +++ b/src/documents/commands/types/insert/insert-many.ts @@ -41,7 +41,7 @@ export type GenericInsertManyOptions = * * @public */ -export interface GenericInsertManyOrderedOptions extends WithTimeout { +export interface GenericInsertManyOrderedOptions extends WithTimeout<'generalMethodTimeoutMs'> { /** * If `true`, the records are inserted in the order provided. If an error occurs, the operation stops and the * remaining records are not inserted. @@ -70,7 +70,7 @@ export interface GenericInsertManyOrderedOptions extends WithTimeout { * * @public */ -export interface GenericInsertManyUnorderedOptions extends WithTimeout { +export interface GenericInsertManyUnorderedOptions extends WithTimeout<'generalMethodTimeoutMs'> { /** * If `false`, the records are inserted in an arbitrary order. If an error occurs, the operation does not stop * and the remaining records are inserted. This allows the operation to be parallelized for better performance. diff --git a/src/documents/commands/types/update/replace-one.ts b/src/documents/commands/types/update/replace-one.ts index 73d1ba0f..c311a3cd 100644 --- a/src/documents/commands/types/update/replace-one.ts +++ b/src/documents/commands/types/update/replace-one.ts @@ -15,7 +15,7 @@ import type { WithTimeout } from '@/src/lib'; import { Sort } from '@/src/documents'; -export interface GenericReplaceOneOptions extends WithTimeout { +export interface GenericReplaceOneOptions extends WithTimeout<'generalMethodTimeoutMs'> { upsert?: boolean, sort?: Sort, } diff --git a/src/documents/commands/types/update/update-many.ts b/src/documents/commands/types/update/update-many.ts index 393040b1..9bc55aaf 100644 --- a/src/documents/commands/types/update/update-many.ts +++ b/src/documents/commands/types/update/update-many.ts @@ -14,6 +14,6 @@ import type { WithTimeout } from '@/src/lib'; -export interface GenericUpdateManyOptions extends WithTimeout { +export interface GenericUpdateManyOptions extends WithTimeout<'generalMethodTimeoutMs'> { upsert?: boolean, } diff --git a/src/documents/commands/types/update/update-one.ts b/src/documents/commands/types/update/update-one.ts index 403f561a..8432fa3c 100644 --- a/src/documents/commands/types/update/update-one.ts +++ b/src/documents/commands/types/update/update-one.ts @@ -24,7 +24,7 @@ import type { Sort } from '@/src/documents'; * * @public */ -export interface GenericUpdateOneOptions extends WithTimeout { +export interface GenericUpdateOneOptions extends WithTimeout<'generalMethodTimeoutMs'> { /** * If true, perform an insert if no documents match the filter. * diff --git a/src/documents/tables/table.ts b/src/documents/tables/table.ts index 4e58f9a8..49b3fe2c 100644 --- a/src/documents/tables/table.ts +++ b/src/documents/tables/table.ts @@ -329,7 +329,7 @@ export class Table { * * @returns The ID of the inserted row. */ - public async insertOne(row: Schema, options?: WithTimeout): Promise> { + public async insertOne(row: Schema, options?: WithTimeout<'generalMethodTimeoutMs'>): Promise> { return this.#commands.insertOne(row, options); } @@ -345,7 +345,7 @@ export class Table { await this.#commands.deleteOne(filter, options); } - public async deleteMany(filter: Filter, options?: WithTimeout): Promise { + public async deleteMany(filter: Filter, options?: WithTimeout<'generalMethodTimeoutMs'>): Promise { await this.#commands.deleteMany(filter, options); } @@ -361,10 +361,6 @@ export class Table { return this.#commands.findOne(filter, options); } - public async drop(options?: WithTimeout): Promise { - await this.#db.dropCollection(this.name, { keyspace: this.keyspace, ...options }); - } - public async alter>(options: Spec): Promise>> public async alter(options: AlterTableOptions): Promise> @@ -422,7 +418,7 @@ export class Table { }); } - public async definition(options?: WithTimeout): Promise { + public async definition(options?: WithTimeout<'tableAdminTimeoutMs'>): Promise { const results = await this.#db.listTables({ timeout: options?.timeout, keyspace: this.keyspace, @@ -437,6 +433,10 @@ export class Table { return table.definition; } + public async drop(options?: WithTimeout<'tableAdminTimeoutMs'>): Promise { + await this.#db.dropTable(this.name, { keyspace: this.keyspace, ...options }); + } + public get _httpClient() { return this.#httpClient; } diff --git a/src/documents/tables/types/indexes/create-index.ts b/src/documents/tables/types/indexes/create-index.ts index 49b5535a..71934ed3 100644 --- a/src/documents/tables/types/indexes/create-index.ts +++ b/src/documents/tables/types/indexes/create-index.ts @@ -14,7 +14,7 @@ import { WithTimeout } from '@/src/lib'; -export interface CreateTableIndexOptions extends WithTimeout { +export interface CreateTableIndexOptions extends WithTimeout<'tableAdminTimeoutMs'> { caseSensitive?: boolean, normalize?: boolean, ascii?: boolean, diff --git a/src/documents/tables/types/indexes/create-vector-index.ts b/src/documents/tables/types/indexes/create-vector-index.ts index 78f20a03..2b67fe50 100644 --- a/src/documents/tables/types/indexes/create-vector-index.ts +++ b/src/documents/tables/types/indexes/create-vector-index.ts @@ -14,7 +14,7 @@ import { WithTimeout } from '@/src/lib'; -export interface CreateTableVectorIndexOptions extends WithTimeout { +export interface CreateTableVectorIndexOptions extends WithTimeout<'tableAdminTimeoutMs'> { metric: 'cosine' | 'euclidean' | 'dot_product', sourceModel?: string, ifNotExists?: boolean, diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts index 110b7792..b516b8de 100644 --- a/src/lib/api/index.ts +++ b/src/lib/api/index.ts @@ -35,4 +35,5 @@ export type { export type { TimeoutDescriptor, WithTimeout, + TimedOutTypes, } from './timeouts'; diff --git a/src/lib/api/timeouts.ts b/src/lib/api/timeouts.ts index 14d91422..dc60f12f 100644 --- a/src/lib/api/timeouts.ts +++ b/src/lib/api/timeouts.ts @@ -16,8 +16,14 @@ import { nullish, OneOrMany } from '@/src/lib'; import { HTTPRequestInfo } from '@/src/lib/api/clients'; import { toArray } from '@/src/lib/utils'; +/** + * @public + */ export type TimedOutTypes = OneOrMany | 'provided'; +/** + * @public + */ export interface TimeoutDescriptor { requestTimeoutMs: number, generalMethodTimeoutMs: number, @@ -27,26 +33,41 @@ export interface TimeoutDescriptor { keyspaceAdminTimeoutMs: number, } -export interface WithTimeout { - timeout?: number | Pick, Timeouts>; +/** + * @public + */ +export interface WithTimeout { + timeout?: number | Pick, 'requestTimeoutMs' | Timeouts>; } +/** + * @internal + */ export type MkTimeoutError = (info: HTTPRequestInfo, timeoutType: TimedOutTypes) => Error; +/** + * @internal + */ export interface TimeoutManager { initial(): Partial, advance(info: HTTPRequestInfo): [number, () => Error], } +/** + * @internal + */ export const EffectivelyInfinity = 2 ** 31 - 1; +/** + * @internal + */ export class Timeouts { constructor( private readonly _mkTimeoutError: MkTimeoutError, public readonly baseTimeouts: TimeoutDescriptor, ) {} - public single(key: Exclude, override: WithTimeout | nullish): TimeoutManager { + public single(key: Exclude, override: WithTimeout | nullish): TimeoutManager { if (typeof override?.timeout === 'number') { const timeout = override.timeout; @@ -79,7 +100,7 @@ export class Timeouts { }); } - public multipart(key: Exclude, override: WithTimeout | nullish): TimeoutManager { + public multipart(key: Exclude, override: WithTimeout | nullish): TimeoutManager { const requestTimeout = ((typeof override?.timeout === 'object') ? override.timeout?.requestTimeoutMs ?? this.baseTimeouts.requestTimeoutMs : this.baseTimeouts.requestTimeoutMs) From 8b7bd2409101e578a9d7abcec36b31225c9c103f Mon Sep 17 00:00:00 2001 From: toptobes Date: Mon, 18 Nov 2024 02:35:04 +0530 Subject: [PATCH 07/10] createCollection custom timeout impl --- src/db/db.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/db/db.ts b/src/db/db.ts index c804a700..424ba47c 100644 --- a/src/db/db.ts +++ b/src/db/db.ts @@ -773,7 +773,12 @@ export class Db { }; await this.#httpClient.executeCommand(command, { - timeoutManager: this.#httpClient.tm.custom({}, () => [720000, 'collectionAdminTimeoutMs']), + timeoutManager: this.#httpClient.tm.single('collectionAdminTimeoutMs', { + timeout: { + collectionAdminTimeoutMs: (typeof options?.timeout === 'number') ? options.timeout : options?.timeout?.collectionAdminTimeoutMs, + requestTimeoutMs: 0, + }, + }), keyspace: options?.keyspace, }); From b30ee0a2e0c66fec0327a61fc17f0293ec2bb6bc Mon Sep 17 00:00:00 2001 From: toptobes Date: Mon, 18 Nov 2024 12:41:16 +0530 Subject: [PATCH 08/10] more docs and stuff --- src/administration/errors.ts | 8 +- src/client/data-api-client.ts | 1 + src/client/parsers/spawn-admin.ts | 2 + src/client/parsers/spawn-db.ts | 4 +- src/client/types/client-opts.ts | 38 ++++ src/client/types/spawn-admin.ts | 38 ++++ src/client/types/spawn-db.ts | 38 ++++ src/db/types/collections/spawn-collection.ts | 38 ++++ src/db/types/tables/spawn-table.ts | 38 ++++ src/documents/errors.ts | 8 +- src/lib/api/index.ts | 2 +- src/lib/api/timeouts.ts | 220 ++++++++++++++++++- tests/unit/lib/api/timeouts.test.ts | 4 +- 13 files changed, 423 insertions(+), 16 deletions(-) diff --git a/src/administration/errors.ts b/src/administration/errors.ts index fc78f8f5..4b43c2a6 100644 --- a/src/administration/errors.ts +++ b/src/administration/errors.ts @@ -15,7 +15,7 @@ import { FetcherResponseInfo, type TimeoutDescriptor } from '@/src/lib/api'; import { SomeDoc } from '@/src/documents'; import { HTTPRequestInfo } from '@/src/lib/api/clients'; -import { TimedOutTypes, Timeouts } from '@/src/lib/api/timeouts'; +import { TimedOutCategories, Timeouts } from '@/src/lib/api/timeouts'; /** * A representation of what went wrong when interacting with the DevOps API. @@ -68,14 +68,14 @@ export class DevOpsAPITimeoutError extends DevOpsAPIError { */ public readonly timeout: Partial; - public readonly timedOutTypes: TimedOutTypes; + public readonly timedOutTypes: TimedOutCategories; /** * Shouldn't be instantiated directly. * * @internal */ - constructor(info: HTTPRequestInfo, types: TimedOutTypes) { + constructor(info: HTTPRequestInfo, types: TimedOutCategories) { super(Timeouts.fmtTimeoutMsg(info.timeoutManager, types)); this.url = info.url; this.timeout = info.timeoutManager.initial(); @@ -83,7 +83,7 @@ export class DevOpsAPITimeoutError extends DevOpsAPIError { this.name = 'DevOpsAPITimeoutError'; } - public static mk(info: HTTPRequestInfo, types: TimedOutTypes): DevOpsAPITimeoutError { + public static mk(info: HTTPRequestInfo, types: TimedOutCategories): DevOpsAPITimeoutError { return new DevOpsAPITimeoutError(info, types); } } diff --git a/src/client/data-api-client.ts b/src/client/data-api-client.ts index c0f811a2..1655d18f 100644 --- a/src/client/data-api-client.ts +++ b/src/client/data-api-client.ts @@ -355,5 +355,6 @@ const parseClientOpts: Parser = (raw, field) => adminOptions: parseAdminSpawnOpts(opts.adminOptions, `${field}.adminOptions`), caller: parseCaller(opts.caller, `${field}.caller`), httpOptions: parseHttpOpts(opts.httpOptions, `${field}.httpOptions`), + timeoutDefaults: Timeouts.parseConfig(opts.timeoutDefaults, `${field}.timeoutDefaults`), }; }; diff --git a/src/client/parsers/spawn-admin.ts b/src/client/parsers/spawn-admin.ts index 87fa6145..4e45fb93 100644 --- a/src/client/parsers/spawn-admin.ts +++ b/src/client/parsers/spawn-admin.ts @@ -16,6 +16,7 @@ import { p, Parser } from '@/src/lib/validation'; import { TokenProvider } from '@/src/lib'; import { Logger } from '@/src/lib/logging/logger'; import { AdminSpawnOptions } from '@/src/client'; +import { Timeouts } from '@/src/lib/api/timeouts'; export const parseAdminSpawnOpts: Parser = (raw, field) => { const opts = p.parse('object?')(raw, field); @@ -30,5 +31,6 @@ export const parseAdminSpawnOpts: Parser adminToken: TokenProvider.parseToken([opts.adminToken], `${field}.adminToken`), additionalHeaders: p.parse('object?')(opts.additionalHeaders, `${field}.additionalHeaders`), astraEnv: p.parse('string?')(opts.astraEnv, `${field}.astraEnv`), + timeoutDefaults: Timeouts.parseConfig(opts.timeoutDefaults, `${field}.timeoutDefaults`), }; }; diff --git a/src/client/parsers/spawn-db.ts b/src/client/parsers/spawn-db.ts index 696d1f31..6cd64785 100644 --- a/src/client/parsers/spawn-db.ts +++ b/src/client/parsers/spawn-db.ts @@ -18,6 +18,7 @@ import { TokenProvider } from '@/src/lib'; import { isNullish } from '@/src/lib/utils'; import { Logger } from '@/src/lib/logging/logger'; import { CollectionSerDesConfig, SomeDoc, TableColumnTypeParser, TableSerDesConfig } from '@/src/documents'; +import { Timeouts } from '@/src/lib/api/timeouts'; export const parseDbSpawnOpts: Parser = (raw, field) => { const opts = p.parse('object?')(raw, field); @@ -31,8 +32,9 @@ export const parseDbSpawnOpts: Parser = (ra keyspace: p.parse('string?', validateKeyspace)(opts.keyspace, `${field}.keyspace`), dataApiPath: p.parse('string?')(opts.dataApiPath, `${field}.dataApiPath`), token: TokenProvider.parseToken([opts.token], `${field}.token`), - serDes: p.parse('object?', parseSerDes)(opts.serdes, `${field}.serDes`), + serdes: p.parse('object?', parseSerDes)(opts.serdes, `${field}.serDes`), additionalHeaders: p.parse('object?')(opts.additionalHeaders, `${field}.additionalHeaders`), + timeoutDefaults: Timeouts.parseConfig(opts.timeoutDefaults, `${field}.timeoutDefaults`), }; }; diff --git a/src/client/types/client-opts.ts b/src/client/types/client-opts.ts index 42ef6dfc..50db7f27 100644 --- a/src/client/types/client-opts.ts +++ b/src/client/types/client-opts.ts @@ -115,5 +115,43 @@ export interface DataAPIClientOptions { * ``` */ caller?: OneOrMany, + /** + * ##### Overview + * + * The default timeout options for anything spawned by this {@link DataAPIClient} instance. + * + * See {@link TimeoutDescriptor} for much more information about timeouts. + * + * @example + * ```ts + * // The request timeout for all operations is set to 1000ms. + * const client = new DataAPIClient('...', { + *   timeoutDefaults: { requestTimeoutMs: 1000 }, + * }); + * + * // The request timeout for all operations borne from this Db is set to 2000ms. + * const db = client.db('...', { + *   timeoutDefaults: { requestTimeoutMs: 2000 }, + * }); + * ``` + * + * ##### Inheritance + * + * The timeout options are inherited by all child classes, and can be overridden at any level, including the individual method level. + * + * Individual-method-level overrides can vary in behavior depending on the method; again, see {@link TimeoutDescriptor}. + * + * ##### Defaults + * + * The default timeout options are as follows: + * - `requestTimeoutMs`: 10000 + * - `generalMethodTimeoutMs`: 30000 + * - `collectionAdminTimeoutMs`: 60000 + * - `tableAdminTimeoutMs`: 30000 + * - `databaseAdminTimeoutMs`: 600000 + * - `keyspaceAdminTimeoutMs`: 30000 + * + * @see TimeoutDescriptor + */ timeoutDefaults?: Partial, } diff --git a/src/client/types/spawn-admin.ts b/src/client/types/spawn-admin.ts index 711bd866..c51b8fb6 100644 --- a/src/client/types/spawn-admin.ts +++ b/src/client/types/spawn-admin.ts @@ -78,5 +78,43 @@ export interface AdminSpawnOptions { * In the case of {@link DataAPIDbAdmin}, it will simply ignore this value. */ astraEnv?: 'dev' | 'prod' | 'test', + /** + * ##### Overview + * + * The default timeout options for any operation on this admin instance. + * + * See {@link TimeoutDescriptor} for much more information about timeouts. + * + * @example + * ```ts + * // The request timeout for all operations is set to 1000ms. + * const client = new DataAPIClient('...', { + *   timeoutDefaults: { requestTimeoutMs: 1000 }, + * }); + * + * // The request timeout for all operations borne from this Db is set to 2000ms. + * const db = client.db('...', { + *   timeoutDefaults: { requestTimeoutMs: 2000 }, + * }); + * ``` + * + * ##### Inheritance + * + * The timeout options are inherited by all child classes, and can be overridden at any level, including the individual method level. + * + * Individual-method-level overrides can vary in behavior depending on the method; again, see {@link TimeoutDescriptor}. + * + * ##### Defaults + * + * The default timeout options are as follows: + * - `requestTimeoutMs`: 10000 + * - `generalMethodTimeoutMs`: 30000 + * - `collectionAdminTimeoutMs`: 60000 + * - `tableAdminTimeoutMs`: 30000 + * - `databaseAdminTimeoutMs`: 600000 + * - `keyspaceAdminTimeoutMs`: 30000 + * + * @see TimeoutDescriptor + */ timeoutDefaults?: Partial, } diff --git a/src/client/types/spawn-db.ts b/src/client/types/spawn-db.ts index ace2c50f..792a7975 100644 --- a/src/client/types/spawn-db.ts +++ b/src/client/types/spawn-db.ts @@ -111,6 +111,44 @@ export interface DbSpawnOptions { * as for enabling feature-flags or other non-standard headers. */ additionalHeaders?: Record, + /** + * ##### Overview + * + * The default timeout options for anything spawned by this {@link Db} instance. + * + * See {@link TimeoutDescriptor} for much more information about timeouts. + * + * @example + * ```ts + * // The request timeout for all operations is set to 1000ms. + * const client = new DataAPIClient('...', { + *   timeoutDefaults: { requestTimeoutMs: 1000 }, + * }); + * + * // The request timeout for all operations borne from this Db is set to 2000ms. + * const db = client.db('...', { + *   timeoutDefaults: { requestTimeoutMs: 2000 }, + * }); + * ``` + * + * ##### Inheritance + * + * The timeout options are inherited by all child classes, and can be overridden at any level, including the individual method level. + * + * Individual-method-level overrides can vary in behavior depending on the method; again, see {@link TimeoutDescriptor}. + * + * ##### Defaults + * + * The default timeout options are as follows: + * - `requestTimeoutMs`: 10000 + * - `generalMethodTimeoutMs`: 30000 + * - `collectionAdminTimeoutMs`: 60000 + * - `tableAdminTimeoutMs`: 30000 + * - `databaseAdminTimeoutMs`: 600000 + * - `keyspaceAdminTimeoutMs`: 30000 + * + * @see TimeoutDescriptor + */ timeoutDefaults?: Partial, } diff --git a/src/db/types/collections/spawn-collection.ts b/src/db/types/collections/spawn-collection.ts index 8fda0143..f50e7e9d 100644 --- a/src/db/types/collections/spawn-collection.ts +++ b/src/db/types/collections/spawn-collection.ts @@ -43,5 +43,43 @@ export interface CollectionSpawnOptions extends WithKeys */ logging?: DataAPILoggingConfig, serdes?: CollectionSerDesConfig, + /** + * ##### Overview + * + * The default timeout options for any operation performed on this {@link Collection} instance. + * + * See {@link TimeoutDescriptor} for much more information about timeouts. + * + * @example + * ```ts + * // The request timeout for all operations is set to 1000ms. + * const client = new DataAPIClient('...', { + *   timeoutDefaults: { requestTimeoutMs: 1000 }, + * }); + * + * // The request timeout for all operations borne from this Db is set to 2000ms. + * const db = client.db('...', { + *   timeoutDefaults: { requestTimeoutMs: 2000 }, + * }); + * ``` + * + * ##### Inheritance + * + * The timeout options are inherited by all child classes, and can be overridden at any level, including the individual method level. + * + * Individual-method-level overrides can vary in behavior depending on the method; again, see {@link TimeoutDescriptor}. + * + * ##### Defaults + * + * The default timeout options are as follows: + * - `requestTimeoutMs`: 10000 + * - `generalMethodTimeoutMs`: 30000 + * - `collectionAdminTimeoutMs`: 60000 + * - `tableAdminTimeoutMs`: 30000 + * - `databaseAdminTimeoutMs`: 600000 + * - `keyspaceAdminTimeoutMs`: 30000 + * + * @see TimeoutDescriptor + */ timeoutDefaults?: Partial, } diff --git a/src/db/types/tables/spawn-table.ts b/src/db/types/tables/spawn-table.ts index 4e66f27e..525ceb60 100644 --- a/src/db/types/tables/spawn-table.ts +++ b/src/db/types/tables/spawn-table.ts @@ -43,5 +43,43 @@ export interface TableSpawnOptions extends WithKeyspace */ logging?: DataAPILoggingConfig, serdes?: TableSerDesConfig, + /** + * ##### Overview + * + * The default timeout options for any operation performed on this {@link Table} instance. + * + * See {@link TimeoutDescriptor} for much more information about timeouts. + * + * @example + * ```ts + * // The request timeout for all operations is set to 1000ms. + * const client = new DataAPIClient('...', { + *   timeoutDefaults: { requestTimeoutMs: 1000 }, + * }); + * + * // The request timeout for all operations borne from this Db is set to 2000ms. + * const db = client.db('...', { + *   timeoutDefaults: { requestTimeoutMs: 2000 }, + * }); + * ``` + * + * ##### Inheritance + * + * The timeout options are inherited by all child classes, and can be overridden at any level, including the individual method level. + * + * Individual-method-level overrides can vary in behavior depending on the method; again, see {@link TimeoutDescriptor}. + * + * ##### Defaults + * + * The default timeout options are as follows: + * - `requestTimeoutMs`: 10000 + * - `generalMethodTimeoutMs`: 30000 + * - `collectionAdminTimeoutMs`: 60000 + * - `tableAdminTimeoutMs`: 30000 + * - `databaseAdminTimeoutMs`: 600000 + * - `keyspaceAdminTimeoutMs`: 30000 + * + * @see TimeoutDescriptor + */ timeoutDefaults?: Partial, } diff --git a/src/documents/errors.ts b/src/documents/errors.ts index 20b486fb..ffc6645d 100644 --- a/src/documents/errors.ts +++ b/src/documents/errors.ts @@ -21,7 +21,7 @@ import type { } from '@/src/documents/collections'; import type { TableInsertManyResult } from '@/src/documents/tables'; import { HTTPRequestInfo } from '@/src/lib/api/clients'; -import { TimedOutTypes, Timeouts } from '@/src/lib/api/timeouts'; +import { TimedOutCategories, Timeouts } from '@/src/lib/api/timeouts'; /** * An object representing a single "soft" (2XX) error returned from the Data API, typically with an error code and a @@ -182,21 +182,21 @@ export class DataAPITimeoutError extends DataAPIError { */ public readonly timeout: Partial; - public readonly timedOutTypes: TimedOutTypes; + public readonly timedOutTypes: TimedOutCategories; /** * Should not be instantiated by the user. * * @internal */ - constructor(info: HTTPRequestInfo, types: TimedOutTypes) { + constructor(info: HTTPRequestInfo, types: TimedOutCategories) { super(Timeouts.fmtTimeoutMsg(info.timeoutManager, types)); this.timeout = info.timeoutManager.initial(); this.timedOutTypes = types; this.name = 'DataAPITimeoutError'; } - public static mk(info: HTTPRequestInfo, types: TimedOutTypes): DataAPITimeoutError { + public static mk(info: HTTPRequestInfo, types: TimedOutCategories): DataAPITimeoutError { return new DataAPITimeoutError(info, types); } } diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts index b516b8de..e5663e7b 100644 --- a/src/lib/api/index.ts +++ b/src/lib/api/index.ts @@ -35,5 +35,5 @@ export type { export type { TimeoutDescriptor, WithTimeout, - TimedOutTypes, + TimedOutCategories, } from './timeouts'; diff --git a/src/lib/api/timeouts.ts b/src/lib/api/timeouts.ts index dc60f12f..725be346 100644 --- a/src/lib/api/timeouts.ts +++ b/src/lib/api/timeouts.ts @@ -15,25 +15,220 @@ import { nullish, OneOrMany } from '@/src/lib'; import { HTTPRequestInfo } from '@/src/lib/api/clients'; import { toArray } from '@/src/lib/utils'; +import { p, Parser } from '@/src/lib/validation'; /** + * The timeout categories that caused the timeout. + * + * If the timeout was caused by: + * - a single timeout category, the category name is returned. + * - multiple categories, an array of category names is returned. + * - a plain-number-timeout provided by the user in a single-call method, the string `'provided'` is returned. + * * @public */ -export type TimedOutTypes = OneOrMany | 'provided'; +export type TimedOutCategories = OneOrMany | 'provided'; /** + * #### Overview + * + * The generic timeout descriptor that is used to define the timeout for all the various operations supported by + * the {@link DataAPIClient} and its children. + * + * ###### Inheritance + * + * The {@link TimeoutDescriptor}, or a subset of it, may be specified at any level of the client hierarchy, all the + * way from the {@link DataAPIClient} down to the individual methods. The timeout specified at a lower level will + * override the timeout specified at a higher level (through a typical object merge). + * + * @example + * ```ts + * // The request timeout for all operations is set to 1000ms. + * const client = new DataAPIClient('...', { + *   timeoutDefaults: { requestTimeoutMs: 1000 }, + * }); + * + * // The request timeout for all operations borne from this Db is set to 2000ms. + * const db = client.db('...', { + *   timeoutDefaults: { requestTimeoutMs: 2000 }, + * }); + * ``` + * + * ###### The `WithTimeout` interface + * + * The {@link WithTimeout} interface lets you specify timeouts for individual methods, in two different formats: + * - A subtype of {@link TimeoutDescriptor}, which lets you specify the timeout for specific categories. + * - A number, which specifies the "happy path" timeout for the method. + * - In single-call methods, this sets both the request & overall method timeouts. + * - In multi-call methods, this sets the overall method timeout (request timeouts are kept as default). + * + * @example + * ```ts + * // Both `requestTimeoutMs` and `generalMethodTimeoutMs` are set to 1000ms. + * await coll.insertOne({ ... }, { timeout: 1000 }); + * + * // `requestTimeoutMs` is left as default, `generalMethodTimeoutMs` is set to 2000ms. + * await coll.insertOne({ ... }, { timeout: { generalMethodTimeoutMs: 2000 } }); + * + * // Both `requestTimeoutMs` and `generalMethodTimeoutMs` are set to 2000ms. + * await coll.insertMany([...], { + *   timeout: { requestTimeoutMs: 2000, generalMethodTimeoutMs: 2000 }, + * }); + * ``` + * + * @example + * ```ts + * // `requestTimeoutMs` is left as default, `generalMethodTimeoutMs` is set to 2000ms. + * await coll.insertMany([...], { timeout: 2000 }); + * + * // `requestTimeoutMs` is left as default, `generalMethodTimeoutMs` is set to 2000ms. + * await coll.insertMany([...], { timeout: { generalMethodTimeoutMs: 2000 } }); + * + * // Both `requestTimeoutMs` and `generalMethodTimeoutMs` are set to 2000ms. + * await coll.insertMany([...], { + *   timeout: { requestTimeoutMs: 2000, generalMethodTimeoutMs: 2000 }, + * }); + * ``` + * + * #### Timeout types + * + * There are 6 generalized categories of timeouts, each with its own default values. + * + * In general though, two types of timeouts are always in play: + * - `requestTimeoutMs`, which is the maximum time the client will wait for a response from the server. + * - The overall method timeout, which is the maximum time the client will wait for the entire method to complete. + * + * Timeout behavior depends on the type of method being called: + * - In single-call methods, the minimum of these two values is used as the timeout. + * - In multi-call methods, the `requestTimeoutMs` is used as the timeout for each individual call, and the overall method timeout is used as the timeout for the entire method. + * + * This two-part timeout system is used in all methods but, but for a special few, where the overall method timeout is the only one used (only `createCollection`, at the moment). This is because the method is a single call, but it takes a long time for the server to complete. + * + * If any timeout is set to `0`, that category is effectively disabled. + * + * ###### Timeout categories + * + * See each individual field for more information, but in general, the timeouts are as follows: + * - `requestTimeoutMs`: + * - The maximum time the client will wait for a response from the server. + * - Default: 10 seconds + * - `generalMethodTimeoutMs`: + * - The overall method timeout for methods that don't have a specific overall method timeout. + * - (mostly applies to document/row-level operations) + * - Default: 30 seconds + * - `collectionAdminTimeoutMs`: + * - The overall method timeout for collection admin operations. + * - (create, drop, list, etc.) + * - Default: 1 minute + * - `tableAdminTimeoutMs`: + * - The overall method timeout for table admin operations. + * - (create, drop, list, alter, create/dropIndex, etc.) + * - Default: 30 seconds + * - `databaseAdminTimeoutMs`: + * - The overall method timeout for database admin operations. + * - (create, drop, list, info, findEmbeddingProviders, etc.) + * - Default: 10 minutes + * - `keyspaceAdminTimeoutMs`: + * - The overall method timeout for keyspace admin operations. + * - (create, drop, list) + * - Default: 30 seconds + * + * @see WithTimeout + * * @public */ export interface TimeoutDescriptor { + /** + * The maximum time the client will wait for a response from the server. + * + * Note that it is technically possible for a request to time out, but still have the request be processed, and even succeed, on the server. + * + * Every HTTP call will use a `requestTimeout`, except for very special cases (at the moment, only `createCollection`, where the request may take a long time to return). + * + * Default: 10 seconds + */ requestTimeoutMs: number, + /** + * The overall method timeout for methods that don't have a specific overall method timeout. + * + * (mostly applies to document/row-level operations) + * + * Default: 30 seconds + */ generalMethodTimeoutMs: number, + /** + * The overall method timeout for collection admin operations. + * + * (create, drop, list, etc.) + * + * Default: 1 minute + */ collectionAdminTimeoutMs: number, + /** + * The overall method timeout for table admin operations. + * + * (create, drop, list, alter, create/dropIndex, etc.) + * + * Default: 30 seconds + */ tableAdminTimeoutMs: number, + /** + * The overall method timeout for database admin operations. + * + * (create, drop, list, info, findEmbeddingProviders, etc.) + * + * Default: 10 minutes + */ databaseAdminTimeoutMs: number, + /** + * The overall method timeout for keyspace admin operations. + * + * (create, drop, list) + * + * Default: 30 seconds + */ keyspaceAdminTimeoutMs: number, } /** + * ##### Overview + * + * Lets you specify timeouts for individual methods, in two different formats: + * - A subtype of {@link TimeoutDescriptor}, which lets you specify the timeout for specific categories. + * - A number, which specifies the "happy path" timeout for the method. + * - In single-call methods, this sets both the request & overall method timeouts. + * - In multi-call methods, this sets the overall method timeout (request timeouts are kept as default). + * + * @example + * ```ts + * // Both `requestTimeoutMs` and `generalMethodTimeoutMs` are set to 1000ms. + * await coll.insertOne({ ... }, { timeout: 1000 }); + * + * // `requestTimeoutMs` is left as default, `generalMethodTimeoutMs` is set to 2000ms. + * await coll.insertOne({ ... }, { timeout: { generalMethodTimeoutMs: 2000 } }); + * + * // Both `requestTimeoutMs` and `generalMethodTimeoutMs` are set to 2000ms. + * await coll.insertMany([...], { + *   timeout: { requestTimeoutMs: 2000, generalMethodTimeoutMs: 2000 }, + * }); + * ``` + * + * @example + * ```ts + * // `requestTimeoutMs` is left as default, `generalMethodTimeoutMs` is set to 2000ms. + * await coll.insertMany([...], { timeout: 2000 }); + * + * // `requestTimeoutMs` is left as default, `generalMethodTimeoutMs` is set to 2000ms. + * await coll.insertMany([...], { timeout: { generalMethodTimeoutMs: 2000 } }); + * + * // Both `requestTimeoutMs` and `generalMethodTimeoutMs` are set to 2000ms. + * await coll.insertMany([...], { + *   timeout: { requestTimeoutMs: 2000, generalMethodTimeoutMs: 2000 }, + * }); + * ``` + * + * @see TimeoutDescriptor + * * @public */ export interface WithTimeout { @@ -43,7 +238,7 @@ export interface WithTimeout { /** * @internal */ -export type MkTimeoutError = (info: HTTPRequestInfo, timeoutType: TimedOutTypes) => Error; +export type MkTimeoutError = (info: HTTPRequestInfo, timeoutType: TimedOutCategories) => Error; /** * @internal @@ -138,7 +333,7 @@ export class Timeouts { }); } - public custom(peek: Partial, advance: () => [number, TimedOutTypes]): TimeoutManager { + public custom(peek: Partial, advance: () => [number, TimedOutCategories]): TimeoutManager { return { initial() { return peek; @@ -176,7 +371,7 @@ export class Timeouts { }; } - public static fmtTimeoutMsg = (tm: TimeoutManager, timeoutTypes: TimedOutTypes) => { + public static fmtTimeoutMsg = (tm: TimeoutManager, timeoutTypes: TimedOutCategories) => { const timeout = (timeoutTypes === 'provided') ? Object.values(tm.initial())[0]! : tm.initial()[toArray(timeoutTypes)[0]]; @@ -190,4 +385,21 @@ export class Timeouts { return `Command timed out after ${timeout}ms (${types})`; }; + + public static parseConfig: Parser | undefined> = (raw, field) => { + const opts = p.parse('object?')(raw, field); + + if (!opts) { + return undefined; + } + + return { + requestTimeoutMs: p.parse('number?')(opts.requestTimeoutMs, `${field}.requestTimeoutMs`), + generalMethodTimeoutMs: p.parse('number?')(opts.generalMethodTimeoutMs, `${field}.generalMethodTimeoutMs`), + collectionAdminTimeoutMs: p.parse('number?')(opts.collectionAdminTimeoutMs, `${field}.collectionAdminTimeoutMs`), + tableAdminTimeoutMs: p.parse('number?')(opts.tableAdminTimeoutMs, `${field}.tableAdminTimeoutMs`), + databaseAdminTimeoutMs: p.parse('number?')(opts.databaseAdminTimeoutMs, `${field}.databaseAdminTimeoutMs`), + keyspaceAdminTimeoutMs: p.parse('number?')(opts.keyspaceAdminTimeoutMs, `${field}.keyspaceAdminTimeoutMs`), + }; + }; } diff --git a/tests/unit/lib/api/timeouts.test.ts b/tests/unit/lib/api/timeouts.test.ts index 4cbc12b8..9c3b4fb1 100644 --- a/tests/unit/lib/api/timeouts.test.ts +++ b/tests/unit/lib/api/timeouts.test.ts @@ -15,12 +15,12 @@ import assert from 'assert'; import { describe, it, parallel } from '@/tests/testlib'; -import { TimedOutTypes, TimeoutManager, Timeouts } from '@/src/lib/api/timeouts'; +import { TimedOutCategories, TimeoutManager, Timeouts } from '@/src/lib/api/timeouts'; import { HTTPRequestInfo } from '@/src/lib/api/clients'; describe('unit.lib.api.timeouts', () => { class TimeoutError extends Error { - constructor(public readonly info: HTTPRequestInfo, public readonly timeoutType: TimedOutTypes) { + constructor(public readonly info: HTTPRequestInfo, public readonly timeoutType: TimedOutCategories) { super(Timeouts.fmtTimeoutMsg(info.timeoutManager, timeoutType)); } } From 9190937f2578f0b9bd2a5784c02d03481cab261e Mon Sep 17 00:00:00 2001 From: toptobes Date: Mon, 18 Nov 2024 13:03:56 +0530 Subject: [PATCH 09/10] more docks! --- src/db/db.ts | 2 +- src/db/types/tables/drop-table.ts | 4 ++- src/lib/api/timeouts.ts | 41 +++++++++++++++++++++++++++---- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/db/db.ts b/src/db/db.ts index 424ba47c..361cb82e 100644 --- a/src/db/db.ts +++ b/src/db/db.ts @@ -1030,7 +1030,7 @@ export class Db { * @remarks Use with caution. Wear a mask. Don't say I didn't warn you. */ public async dropTable(name: string, options?: DropTableOptions): Promise { - await this.#httpClient.executeCommand({ dropTable: { name } }, { + await this.#httpClient.executeCommand({ dropTable: { name, options: { ifExists: options?.ifExists } } }, { timeoutManager: this.#httpClient.tm.single('tableAdminTimeoutMs', options), keyspace: options?.keyspace, }); diff --git a/src/db/types/tables/drop-table.ts b/src/db/types/tables/drop-table.ts index 80577713..8989042a 100644 --- a/src/db/types/tables/drop-table.ts +++ b/src/db/types/tables/drop-table.ts @@ -15,4 +15,6 @@ import type { WithTimeout } from '@/src/lib'; import { WithKeyspace } from '@/src/db'; -export interface DropTableOptions extends WithTimeout<'tableAdminTimeoutMs'>, WithKeyspace {} +export interface DropTableOptions extends WithTimeout<'tableAdminTimeoutMs'>, WithKeyspace { + ifExists?: boolean; +} diff --git a/src/lib/api/timeouts.ts b/src/lib/api/timeouts.ts index 725be346..67d1a40f 100644 --- a/src/lib/api/timeouts.ts +++ b/src/lib/api/timeouts.ts @@ -151,7 +151,11 @@ export interface TimeoutDescriptor { /** * The overall method timeout for methods that don't have a specific overall method timeout. * - * (mostly applies to document/row-level operations) + * Mostly applies to document/row-level operations. DDL-esque operations (working with collections, tables, databases, keyspaces, indexes, etc.) have their own overall method timeouts. + * + * In single-call methods, such as `insertOne`, the minimum of `requestTimeoutMs` and `generalMethodTimeoutMs` is used as the timeout. + * + * In multi-call methods, such as `insertMany`, the `requestTimeoutMs` is used as the timeout for each individual call, and the `generalMethodTimeoutMs` is used as the timeout for the entire method. * * Default: 30 seconds */ @@ -159,7 +163,11 @@ export interface TimeoutDescriptor { /** * The overall method timeout for collection admin operations. * - * (create, drop, list, etc.) + * Such methods include (but may not be limited to): + * - `db.createCollection()` + * - `db.dropCollection()` + * - `db.listCollections()` + * - `collection.options()` * * Default: 1 minute */ @@ -167,7 +175,15 @@ export interface TimeoutDescriptor { /** * The overall method timeout for table admin operations. * - * (create, drop, list, alter, create/dropIndex, etc.) + * Such methods include (but may not be limited to): + * - `db.createTable()` + * - `db.dropTable()` + * - `db.listTables()` + * - `table.alter()` + * - `table.createIndex()` + * - `db.dropTableIndex()` + * - `table.definition()` + * * * Default: 30 seconds */ @@ -175,7 +191,12 @@ export interface TimeoutDescriptor { /** * The overall method timeout for database admin operations. * - * (create, drop, list, info, findEmbeddingProviders, etc.) + * Such methods include (but may not be limited to): + * - `admin.createDatabase()` + * - `admin.dropDatabase()` + * - `admin.listDatabases()` + * - `dbAdmin.info()` + * - `dbAdmin.findEmbeddingProviders()` * * Default: 10 minutes */ @@ -183,7 +204,10 @@ export interface TimeoutDescriptor { /** * The overall method timeout for keyspace admin operations. * - * (create, drop, list) + * Such methods include (but may not be limited to): + * - `admin.createKeyspace()` + * - `admin.dropKeyspace()` + * - `admin.listKeyspaces()` * * Default: 30 seconds */ @@ -227,11 +251,18 @@ export interface TimeoutDescriptor { * }); * ``` * + * See {@link TimeoutDescriptor} for much more information. + * * @see TimeoutDescriptor * * @public */ export interface WithTimeout { + /** + * The method timeout override. + * + * See {@link TimeoutDescriptor} for much more information. + */ timeout?: number | Pick, 'requestTimeoutMs' | Timeouts>; } From e077e4d4c786c06cb1a3b107bb62e3dca4bff679 Mon Sep 17 00:00:00 2001 From: toptobes Date: Mon, 18 Nov 2024 14:03:04 +0530 Subject: [PATCH 10/10] more teststs --- tests/unit/lib/api/timeouts.test.ts | 74 ++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/tests/unit/lib/api/timeouts.test.ts b/tests/unit/lib/api/timeouts.test.ts index 9c3b4fb1..d8e9d51c 100644 --- a/tests/unit/lib/api/timeouts.test.ts +++ b/tests/unit/lib/api/timeouts.test.ts @@ -15,7 +15,7 @@ import assert from 'assert'; import { describe, it, parallel } from '@/tests/testlib'; -import { TimedOutCategories, TimeoutManager, Timeouts } from '@/src/lib/api/timeouts'; +import { TimedOutCategories, TimeoutDescriptor, TimeoutManager, Timeouts } from '@/src/lib/api/timeouts'; import { HTTPRequestInfo } from '@/src/lib/api/clients'; describe('unit.lib.api.timeouts', () => { @@ -257,4 +257,76 @@ describe('unit.lib.api.timeouts', () => { assert.strictEqual(e3.message, 'Command timed out after 100ms (keyspaceAdminTimeoutMs timed out)'); }); }); + + parallel('custom', () => { + it('should return what it was given', () => { + const tm = timeouts.custom({ databaseAdminTimeoutMs: 3, requestTimeoutMs: 5 }, () => [1, 'requestTimeoutMs']); + let [timeout, mkError] = tm.advance(info(tm)); + assert.strictEqual(timeout, 1); + + const e = mkError(); + assert.ok(e instanceof TimeoutError); + assert.deepStrictEqual(e.info, info(tm)); + assert.strictEqual(e.timeoutType, 'requestTimeoutMs'); + assert.strictEqual(e.message, 'Command timed out after 5ms (requestTimeoutMs timed out)'); + + assert.deepStrictEqual(tm.initial(), { + databaseAdminTimeoutMs: 3, + requestTimeoutMs: 5, + }); + + [timeout, mkError] = tm.advance(info(tm)); + assert.strictEqual(timeout, 1); + + const e2 = mkError(); + assert.ok(e2 instanceof TimeoutError); + assert.deepStrictEqual(e2.info, info(tm)); + assert.strictEqual(e2.timeoutType, 'requestTimeoutMs'); + assert.strictEqual(e2.message, 'Command timed out after 5ms (requestTimeoutMs timed out)'); + }); + }); + + describe('merge', () => { + it('should return the base config if new config is nullish', () => { + const base = { a: 1, b: 2 } as unknown as TimeoutDescriptor; + assert.strictEqual(Timeouts.merge(base, null), base); + assert.strictEqual(Timeouts.merge(base, undefined), base); + }); + + it('should merge the config', () => { + const merged = Timeouts.merge(Timeouts.Default, { requestTimeoutMs: 1, databaseAdminTimeoutMs: 3 }); + assert.deepStrictEqual(merged, { + requestTimeoutMs: 1, + generalMethodTimeoutMs: Timeouts.Default.generalMethodTimeoutMs, + keyspaceAdminTimeoutMs: Timeouts.Default.keyspaceAdminTimeoutMs, + tableAdminTimeoutMs: Timeouts.Default.tableAdminTimeoutMs, + collectionAdminTimeoutMs: Timeouts.Default.collectionAdminTimeoutMs, + databaseAdminTimeoutMs: 3, + }); + }); + }); + + describe('parseConfig', () => { + it('should accept a nullish config', () => { + assert.strictEqual(Timeouts.parseConfig(null!, ''), undefined); + assert.strictEqual(Timeouts.parseConfig(undefined, ''), undefined); + }); + + it('should error on a non-object config', () => { + assert.throws(() => Timeouts.parseConfig(1 as any, 'timeoutDefaults'), { message: 'Expected timeoutDefaults to be of type object? (or nullish), but got number' }); + }); + + it('should parse timeout config', () => { + const config = { + requestTimeoutMs: -1, + generalMethodTimeoutMs: Infinity, + keyspaceAdminTimeoutMs: 3, + tableAdminTimeoutMs: 4.3, + collectionAdminTimeoutMs: undefined, + databaseAdminTimeoutMs: undefined, + }; + + assert.deepStrictEqual(Timeouts.parseConfig(config, 'timeoutDefaults'), config); + }); + }); });