Skip to content

Commit 8ea415c

Browse files
authored
Client using support + minor typing bug fix (#31)
* Fixed export bug + added using support * Fixed a couple now-broken tests * made cursor not disposable; improved clients disposal
1 parent 6b0ead3 commit 8ea415c

File tree

8 files changed

+93
-44
lines changed

8 files changed

+93
-44
lines changed

etc/astra-db-ts.api.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,14 @@ export type ArrayUpdate<Schema> = {
100100

101101
// @public
102102
export class AstraAdmin {
103-
// Warning: (ae-forgotten-export) The symbol "AdminOptions" needs to be exported by the entry point index.d.ts
103+
// Warning: (ae-forgotten-export) The symbol "InternalRootClientOpts" needs to be exported by the entry point index.d.ts
104104
//
105105
// @internal
106-
constructor(options: AdminOptions);
106+
constructor(options: InternalRootClientOpts & {
107+
adminOptions: {
108+
adminToken: string;
109+
};
110+
});
107111
createDatabase(config: DatabaseConfig, options?: CreateDatabaseOptions): Promise<AstraDbAdmin>;
108112
db(endpoint: string, options?: DbSpawnOptions): Db;
109113
db(id: string, region: string, options?: DbSpawnOptions): Db;
@@ -115,8 +119,6 @@ export class AstraAdmin {
115119

116120
// @public
117121
export class AstraDbAdmin extends DbAdmin {
118-
// Warning: (ae-forgotten-export) The symbol "InternalRootClientOpts" needs to be exported by the entry point index.d.ts
119-
//
120122
// @internal
121123
constructor(_db: Db, options: InternalRootClientOpts);
122124
createNamespace(namespace: string, options?: AdminBlockingOptions): Promise<void>;
@@ -324,6 +326,7 @@ export class CursorIsStartedError extends DataAPIError {
324326

325327
// @public
326328
export class DataAPIClient extends DataAPIClientEventEmitterBase {
329+
[Symbol.asyncDispose]: () => Promise<void>;
327330
constructor(token: string, options?: DataAPIClientOptions | null);
328331
admin(options?: AdminSpawnOptions): AstraAdmin;
329332
close(): Promise<void>;
@@ -628,7 +631,7 @@ export class FindCursor<T, TRaw extends SomeDoc = SomeDoc> {
628631
constructor(namespace: string, httpClient: DataAPIHttpClient, filter: Filter<SomeDoc>, options?: FindOptions);
629632
bufferedCount(): number;
630633
clone(): FindCursor<TRaw, TRaw>;
631-
close(): Promise<void>;
634+
close(): void;
632635
get closed(): boolean;
633636
filter(filter: Filter<TRaw>): FindCursor<T, TRaw>;
634637
// @deprecated

src/api/data-api-http-client.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
HttpMethods,
2424
RawDataAPIResponse,
2525
} from '@/src/api';
26-
import { DataAPIResponseError, DataAPITimeoutError, ObjectId, UUID } from '@/src/data-api';
26+
import { DataAPIResponseError, DataAPITimeoutError, ObjectId, UUID, WithNamespace } from '@/src/data-api';
2727
import { MkTimeoutError, TimeoutManager, TimeoutOptions } from '@/src/api/timeout-managers';
2828
import { CommandFailedEvent, CommandStartedEvent, CommandSucceededEvent } from '@/src/data-api/events';
2929
import { CollectionNotFoundError, DataAPIHttpError, mkRespErrorFromResponse } from '@/src/data-api/errors';
@@ -39,24 +39,20 @@ export interface DataAPIRequestInfo {
3939
timeoutManager: TimeoutManager;
4040
}
4141

42-
type ExecuteCommandOptions = TimeoutOptions & {
42+
type ExecuteCommandOptions = {
4343
collection?: string;
4444
namespace?: string;
4545
}
4646

47-
type DataAPIHttpClientOptions = HTTPClientOptions & {
48-
namespace: string;
49-
}
50-
5147
/**
5248
* @internal
5349
*/
5450
export class DataAPIHttpClient extends HttpClient {
5551
public collection?: string;
5652
public namespace?: string;
57-
readonly #props: DataAPIHttpClientOptions;
53+
readonly #props: HTTPClientOptions & WithNamespace;
5854

59-
constructor(props: DataAPIHttpClientOptions) {
55+
constructor(props: HTTPClientOptions & WithNamespace) {
6056
super({
6157
...props,
6258
mkAuthHeader: (token) => ({ [DEFAULT_DATA_API_AUTH_HEADER]: token }),
@@ -80,7 +76,7 @@ export class DataAPIHttpClient extends HttpClient {
8076
return mkTimeoutManager(timeoutMs);
8177
}
8278

83-
public async executeCommand(command: Record<string, any>, options: ExecuteCommandOptions | undefined) {
79+
public async executeCommand(command: Record<string, any>, options: TimeoutOptions & ExecuteCommandOptions | undefined) {
8480
const timeoutManager = options?.timeoutManager ?? mkTimeoutManager(options?.maxTimeMS);
8581

8682
return await this._requestDataAPI({

src/client/data-api-client.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,10 @@ export class DataAPIClient extends DataAPIClientEventEmitterBase {
139139
},
140140
emitter: this,
141141
};
142+
143+
if (Symbol.asyncDispose) {
144+
this[Symbol.asyncDispose] = this.close;
145+
}
142146
}
143147

144148
/**
@@ -266,6 +270,33 @@ export class DataAPIClient extends DataAPIClientEventEmitterBase {
266270
await this.#options.fetchCtx.preferred.disconnectAll();
267271
await this.#options.fetchCtx.http1.disconnectAll();
268272
}
273+
274+
/**
275+
* Allows for the `await using` syntax (if your typescript version \>= 5.2) to automatically close the client when
276+
* it's out of scope.
277+
*
278+
* Equivalent to wrapping the client usage in a `try`/`finally` block and calling `client.close()` in the `finally`
279+
* block.
280+
*
281+
* @example
282+
* ```typescript
283+
* async function main() {
284+
*   // Will unconditionally close the client when the function exits
285+
*   await using client = new DataAPIClient('*TOKEN*');
286+
*
287+
*   // Using the client as normal
288+
*   const db = client.db('*ENDPOINT*');
289+
*   console.log(await db.listCollections());
290+
*
291+
*   // Or pass it to another function to run your app
292+
*   app(client);
293+
* }
294+
* main();
295+
* ```
296+
*
297+
* *This will only be defined if the `Symbol.asyncDispose` symbol is actually defined.*
298+
*/
299+
public [Symbol.asyncDispose]!: () => Promise<void>;
269300
}
270301

271302
function validateRootOpts(opts: DataAPIClientOptions | undefined | null) {

src/data-api/cursor.ts

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export class FindCursor<T, TRaw extends SomeDoc = SomeDoc> {
106106
*
107107
* @returns The namespace of the collection that's being iterated over.
108108
*/
109-
get namespace(): string {
109+
public get namespace(): string {
110110
return this._namespace;
111111
}
112112

@@ -115,7 +115,7 @@ export class FindCursor<T, TRaw extends SomeDoc = SomeDoc> {
115115
*
116116
* @returns Whether or not the cursor is closed.
117117
*/
118-
get closed(): boolean {
118+
public get closed(): boolean {
119119
return this._state === CursorStatus.Closed;
120120
}
121121

@@ -124,7 +124,7 @@ export class FindCursor<T, TRaw extends SomeDoc = SomeDoc> {
124124
*
125125
* @returns The number of documents in the buffer.
126126
*/
127-
bufferedCount(): number {
127+
public bufferedCount(): number {
128128
return this._buffer.length;
129129
}
130130

@@ -140,7 +140,7 @@ export class FindCursor<T, TRaw extends SomeDoc = SomeDoc> {
140140
*
141141
* @returns The cursor.
142142
*/
143-
filter(filter: Filter<TRaw>): FindCursor<T, TRaw> {
143+
public filter(filter: Filter<TRaw>): FindCursor<T, TRaw> {
144144
this._assertUninitialized();
145145
this._filter = filter as any;
146146
return this;
@@ -158,7 +158,7 @@ export class FindCursor<T, TRaw extends SomeDoc = SomeDoc> {
158158
*
159159
* @returns The cursor.
160160
*/
161-
sort(sort: Sort): FindCursor<T, TRaw> {
161+
public sort(sort: Sort): FindCursor<T, TRaw> {
162162
this._assertUninitialized();
163163
this._options.sort = normalizeSort(sort);
164164
return this;
@@ -175,7 +175,7 @@ export class FindCursor<T, TRaw extends SomeDoc = SomeDoc> {
175175
*
176176
* @returns The cursor.
177177
*/
178-
limit(limit: number): FindCursor<T, TRaw> {
178+
public limit(limit: number): FindCursor<T, TRaw> {
179179
this._assertUninitialized();
180180
this._options.limit = limit || Infinity;
181181
return this;
@@ -190,7 +190,7 @@ export class FindCursor<T, TRaw extends SomeDoc = SomeDoc> {
190190
*
191191
* @returns The cursor.
192192
*/
193-
skip(skip: number): FindCursor<T, TRaw> {
193+
public skip(skip: number): FindCursor<T, TRaw> {
194194
this._assertUninitialized();
195195
this._options.skip = skip;
196196
return this;
@@ -232,7 +232,7 @@ export class FindCursor<T, TRaw extends SomeDoc = SomeDoc> {
232232
*
233233
* @returns The cursor.
234234
*/
235-
project<R = any, RRaw extends SomeDoc = SomeDoc>(projection: Projection): FindCursor<R, RRaw> {
235+
public project<R = any, RRaw extends SomeDoc = SomeDoc>(projection: Projection): FindCursor<R, RRaw> {
236236
this._assertUninitialized();
237237
this._options.projection = projection;
238238
return this as any;
@@ -247,7 +247,7 @@ export class FindCursor<T, TRaw extends SomeDoc = SomeDoc> {
247247
*
248248
* @returns The cursor.
249249
*/
250-
includeSimilarity(includeSimilarity: boolean = true): FindCursor<T, TRaw> {
250+
public includeSimilarity(includeSimilarity: boolean = true): FindCursor<T, TRaw> {
251251
this._assertUninitialized();
252252
this._options.includeSimilarity = includeSimilarity;
253253
return this;
@@ -265,7 +265,7 @@ export class FindCursor<T, TRaw extends SomeDoc = SomeDoc> {
265265
*
266266
* @returns The cursor.
267267
*/
268-
map<R>(mapping: (doc: T) => R): FindCursor<R, TRaw> {
268+
public map<R>(mapping: (doc: T) => R): FindCursor<R, TRaw> {
269269
this._assertUninitialized();
270270

271271
if (this._mapping) {
@@ -291,7 +291,7 @@ export class FindCursor<T, TRaw extends SomeDoc = SomeDoc> {
291291
*
292292
* @returns A behavioral clone of this cursor.
293293
*/
294-
clone(): FindCursor<TRaw, TRaw> {
294+
public clone(): FindCursor<TRaw, TRaw> {
295295
return new FindCursor<TRaw, TRaw>(this._namespace, this._httpClient, this._filter, this._options);
296296
}
297297

@@ -304,7 +304,7 @@ export class FindCursor<T, TRaw extends SomeDoc = SomeDoc> {
304304
*
305305
* @returns The documents read from the buffer.
306306
*/
307-
readBufferedDocuments(max?: number): TRaw[] {
307+
public readBufferedDocuments(max?: number): TRaw[] {
308308
const toRead = Math.min(max ?? this._buffer.length, this._buffer.length);
309309
return this._buffer.splice(0, toRead);
310310
}
@@ -314,7 +314,7 @@ export class FindCursor<T, TRaw extends SomeDoc = SomeDoc> {
314314
* cursor will remain, but iteration will start from the beginning, sending new queries to the server, even if the
315315
* resultant data was already fetched by this cursor.
316316
*/
317-
rewind(): void {
317+
public rewind(): void {
318318
this._buffer.length = 0;
319319
this._nextPageState = undefined;
320320
this._state = CursorStatus.Uninitialized;
@@ -327,7 +327,7 @@ export class FindCursor<T, TRaw extends SomeDoc = SomeDoc> {
327327
*
328328
* @returns The next document, or `null` if there are no more documents.
329329
*/
330-
async next(): Promise<T | null> {
330+
public async next(): Promise<T | null> {
331331
return this._next(false);
332332
}
333333

@@ -338,7 +338,7 @@ export class FindCursor<T, TRaw extends SomeDoc = SomeDoc> {
338338
*
339339
* @returns Whether or not there is a next document.
340340
*/
341-
async hasNext(): Promise<boolean> {
341+
public async hasNext(): Promise<boolean> {
342342
if (this._buffer.length > 0) {
343343
return true;
344344
}
@@ -370,7 +370,7 @@ export class FindCursor<T, TRaw extends SomeDoc = SomeDoc> {
370370
* }
371371
* ```
372372
*/
373-
async *[Symbol.asyncIterator](): AsyncGenerator<T, void, void> {
373+
public async *[Symbol.asyncIterator](): AsyncGenerator<T, void, void> {
374374
try {
375375
while (true) {
376376
const doc = await this.next();
@@ -382,7 +382,7 @@ export class FindCursor<T, TRaw extends SomeDoc = SomeDoc> {
382382
yield doc;
383383
}
384384
} finally {
385-
await this.close();
385+
this.close();
386386
}
387387
}
388388

@@ -404,7 +404,7 @@ export class FindCursor<T, TRaw extends SomeDoc = SomeDoc> {
404404
*
405405
* @deprecated - Prefer the `for await (const doc of cursor) { ... }` syntax instead.
406406
*/
407-
async forEach(consumer: (doc: T) => boolean | void): Promise<void> {
407+
public async forEach(consumer: (doc: T) => boolean | void): Promise<void> {
408408
for await (const doc of this) {
409409
if (consumer(doc) === false) {
410410
break;
@@ -423,7 +423,7 @@ export class FindCursor<T, TRaw extends SomeDoc = SomeDoc> {
423423
*
424424
* @returns An array of all documents in the cursor.
425425
*/
426-
async toArray(): Promise<T[]> {
426+
public async toArray(): Promise<T[]> {
427427
const docs: T[] = [];
428428
for await (const doc of this) {
429429
docs.push(doc);
@@ -434,8 +434,9 @@ export class FindCursor<T, TRaw extends SomeDoc = SomeDoc> {
434434
/**
435435
* Closes the cursor. The cursor will be unusable after this method is called, or until {@link FindCursor.rewind} is called.
436436
*/
437-
async close(): Promise<void> {
437+
public close(): void {
438438
this._state = CursorStatus.Closed;
439+
this._buffer = [];
439440
}
440441

441442
private _assertUninitialized(): void {
@@ -460,7 +461,7 @@ export class FindCursor<T, TRaw extends SomeDoc = SomeDoc> {
460461
? this._mapping(doc)
461462
: doc;
462463
} catch (err) {
463-
await this.close();
464+
this.close();
464465
throw err;
465466
}
466467
}
@@ -472,7 +473,7 @@ export class FindCursor<T, TRaw extends SomeDoc = SomeDoc> {
472473
try {
473474
await this._getMore();
474475
} catch (err) {
475-
await this.close();
476+
this.close();
476477
throw err;
477478
}
478479
} while (this._buffer.length !== 0);

src/data-api/types/utils.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import type { SomeId } from '@/src/data-api/types';
2222
* IsNum<string | number> === true
2323
* ```
2424
*
25-
* @internal
25+
* @public
2626
*/
2727
export type IsNum<T> = number extends T ? true : bigint extends T ? true : false
2828

@@ -34,14 +34,14 @@ export type IsNum<T> = number extends T ? true : bigint extends T ? true : false
3434
* IsDate<string | Date> === boolean
3535
* ```
3636
*
37-
* @internal
37+
* @public
3838
*/
3939
export type IsDate<T> = IsAny<T> extends true ? true : T extends Date | { $date: number } ? true : false
4040

4141
/**
4242
* Checks if a type is any
4343
*
44-
* @internal
44+
* @public
4545
*/
4646
export type IsAny<T> = true extends false & T ? true : false
4747

src/devops/astra-admin.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ import { AdminSpawnOptions, DbSpawnOptions, InternalRootClientOpts } from '@/src
1212
import { validateOption } from '@/src/data-api/utils';
1313
import { mkDb } from '@/src/data-api/db';
1414

15-
type AdminOptions = InternalRootClientOpts & { adminOptions: { adminToken: string } };
16-
1715
/**
1816
* An administrative class for managing Astra databases, including creating, listing, and deleting databases.
1917
*
@@ -50,7 +48,7 @@ export class AstraAdmin {
5048
*
5149
* @internal
5250
*/
53-
constructor(options: AdminOptions) {
51+
constructor(options: InternalRootClientOpts & { adminOptions: { adminToken: string } }) {
5452
const adminOpts = options.adminOptions ?? {};
5553

5654
this.#defaultOpts = options;

0 commit comments

Comments
 (0)