diff --git a/APIReference.md b/APIReference.md index 5e4e72b..9d77b95 100644 --- a/APIReference.md +++ b/APIReference.md @@ -632,7 +632,7 @@ for tables and CollectionsDb class for collections.

**Kind**: global class * [BaseDb](#BaseDb) - * [new BaseDb()](#new_BaseDb_new) + * [.isTable](#BaseDb+isTable) * [.createTable(name, definition)](#BaseDb+createTable) * [.dropCollection(name)](#BaseDb+dropCollection) * [.dropTable(name)](#BaseDb+dropTable) @@ -645,13 +645,14 @@ for tables and CollectionsDb class for collections.

* [.syncTypes(types)](#BaseDb+syncTypes) ⇒ * [.command(command)](#BaseDb+command) - + -### new BaseDb() +### baseDb.isTable

Whether we're using "tables mode" or "collections mode". If tables mode, then collection() returns a Table instance, not a Collection instance. Also, if tables mode, createCollection() throws an error for Mongoose syncIndexes() compatibility reasons.

+**Kind**: instance property of [BaseDb](#BaseDb) ### baseDb.createTable(name, definition) @@ -1033,7 +1034,7 @@ the one exception being strings.

**Extends**: [BaseDb](#BaseDb) * [BaseDb](#BaseDb) ⇐ [BaseDb](#BaseDb) - * [new BaseDb()](#new_BaseDb_new) + * [.isTable](#BaseDb+isTable) * [.createTable(name, definition)](#BaseDb+createTable) * [.dropCollection(name)](#BaseDb+dropCollection) * [.dropTable(name)](#BaseDb+dropTable) @@ -1046,13 +1047,14 @@ the one exception being strings.

* [.syncTypes(types)](#BaseDb+syncTypes) ⇒ * [.command(command)](#BaseDb+command) - + -### new BaseDb() +### baseDb.isTable

Whether we're using "tables mode" or "collections mode". If tables mode, then collection() returns a Table instance, not a Collection instance. Also, if tables mode, createCollection() throws an error for Mongoose syncIndexes() compatibility reasons.

+**Kind**: instance property of [BaseDb](#BaseDb) ### baseDb.createTable(name, definition) diff --git a/bin/warm-up-tests.ts b/bin/warm-up-tests.ts index 1a0943a..9450d47 100644 --- a/bin/warm-up-tests.ts +++ b/bin/warm-up-tests.ts @@ -57,7 +57,7 @@ async function main() { } } - const collection = connection.db!.collection(collectionName, {}); + const collection = connection.astraDb!.collection(collectionName, {}); await retryNotEnoughReplicas(async () => { await collection.insertOne({ ping: true, ts: new Date() }); await collection.deleteMany({}); diff --git a/jsdoc2md.json b/jsdoc2md.json index 6a8f81e..ea0244b 100644 --- a/jsdoc2md.json +++ b/jsdoc2md.json @@ -8,6 +8,6 @@ "extensions": ["ts", "tsx"], "ignore": ["**/*.(test|spec).ts"], "babelrc": false, - "presets": [["@babel/preset-env", { "targets": { "node": true } }], "@babel/preset-typescript"] + "presets": [["@babel/preset-env", { "targets": { "node": true } }], ["@babel/preset-typescript", { "allowDeclareFields": true }]] } } diff --git a/src/driver/collection.ts b/src/driver/collection.ts index 7bc65eb..1493c3c 100644 --- a/src/driver/collection.ts +++ b/src/driver/collection.ts @@ -63,7 +63,7 @@ import { OperationNotSupportedError } from '../operationNotSupportedError'; import { SchemaOptions } from 'mongoose'; import { Writable } from 'stream'; import deserializeDoc from '../deserializeDoc'; -import { IndexSpecification, Sort as MongoDBSort, WithId, InferIdType } from 'mongodb'; +import { FindCursor, IndexSpecification, ListIndexesCursor, Sort as MongoDBSort, WithId, InferIdType } from 'mongodb'; import { inspect } from 'util'; import { serialize } from '../serialize'; import { setDefaultIdForUpdate, setDefaultIdForReplace } from '../setDefaultIdForUpsert'; @@ -77,7 +77,7 @@ type FindOptions = (Omit | Omit | Omit) & { sort?: MongooseSortOption, maxTimeMS?: number, timeout?: boolean }; -type FindOneAndUpdateOptions = Omit +type FindOneAndUpdateOptions = Omit & { sort?: MongooseSortOption, includeResultMetadata?: boolean }; type FindOneAndDeleteOptions = Omit & { sort?: MongooseSortOption, includeResultMetadata?: boolean, maxTimeMS?: number }; @@ -90,15 +90,6 @@ type ReplaceOneOptions = Omit type UpdateOneOptions = (Omit | Omit) & { sort?: MongooseSortOption, maxTimeMS?: number }; -interface AstraMongooseIndexDescription { - name: string, - definition: { - column: string | ({ [key: string]: '$keys' | '$values' }), - options?: TableIndexOptions | TableVectorIndexOptions | TableTextIndexOptions; - }, - key: Record -} - interface AstraIndexDescription { name: string; definition: { @@ -128,8 +119,7 @@ export class Collection = Record | AstraTable; _closed: boolean; connection: Connection; - // @ts-expect-error options is technically a function on Mongoose collections - options?: (TableOptions | CollectionOptions) & MongooseCollectionOptions; + _options?: (TableOptions | CollectionOptions) & MongooseCollectionOptions; name: string; constructor(name: string, conn: Connection, options?: (TableOptions | CollectionOptions) & MongooseCollectionOptions) { @@ -138,7 +128,7 @@ export class Collection = Record = Record` method below doesn't know whether we're creating a // Astra Table or Astra Collection until runtime - ? { serdes: this.options.schemaUserProvidedOptions.serdes } as unknown as Record + ? { serdes: this._options.schemaUserProvidedOptions.serdes } as unknown as Record : {}; // Cache because @datastax/astra-db-ts doesn't - const collection = this.connection.db!.collection(this.name, collectionOptions); + const collection = this.connection.astraDb!.collection(this.name, collectionOptions); this._collection = collection; // Bubble up collection-level events from astra-db-ts to the main connection @@ -166,11 +156,11 @@ export class Collection = Record = Record = Record deserializeDoc(doc) as DocType); + return this.collection + .find(filter, requestOptions) + .map(doc => deserializeDoc(doc) as DocType) as unknown as FindCursor>; } /** @@ -268,12 +259,12 @@ export class Collection = Record | Record[], - options: FindOneAndUpdateOptions - ) { + update: CollectionUpdateFilter | TableUpdateFilter | Record[], + options?: FindOneAndUpdateOptions + ): Promise { + options = options ?? {}; if (Array.isArray(update)) { throw new AstraMongooseError('Astra-mongoose does not support update pipelines', { update }); } @@ -289,10 +280,10 @@ export class Collection = Record(filter, update, requestOptions); - update = serialize(update); + update = serialize(update as Record); return await this.collection.findOneAndUpdate(filter, update, requestOptions).then((value: Record | null) => { - if (options?.includeResultMetadata) { + if (options.includeResultMetadata) { return { value: deserializeDoc(value) }; } return deserializeDoc(value); @@ -486,7 +477,7 @@ export class Collection = Record, requestOptions); } - update = serialize(update, this.isTable); + update = serialize(update as Record, this.isTable); return await this.collection.updateOne(filter as TableFilter, update, requestOptions).then(res => { // Mongoose currently has a bug where null response from updateOne() throws an error that we can't // catch here for unknown reasons. See Automattic/mongoose#15126. Tables API returns null here. @@ -503,8 +494,11 @@ export class Collection = Record | Record[], options: CollectionUpdateManyOptions) { + async updateMany( + filter: Filter, + update: CollectionUpdateFilter | TableUpdateFilter | Record[], + options?: Omit + ): Promise { if (Array.isArray(update)) { throw new AstraMongooseError('Astra-mongoose does not support update pipelines', { update }); } @@ -514,10 +508,13 @@ export class Collection = Record ({ ...res, acknowledged: true })); + update = serialize(update as Record, this.isTable); + return await this.collection + .updateMany(filter, update, options) + .then(res => ({ ...res, acknowledged: true })); } /** @@ -617,7 +614,7 @@ export class Collection = Record, options?: Omit) { // eslint-disable-next-line prefer-rest-params _logFunctionCall(this, this.connection.debug, this.name, 'runCommand', arguments); - return await this.connection.db!.astraDb.command( + return await this.connection.astraDb!.astraDb.command( command, this.isTable ? { table: this.name, ...options } : { collection: this.name, ...options } ); @@ -650,8 +647,7 @@ export class Collection = Record Promise } { + listIndexes(): ListIndexesCursor { // eslint-disable-next-line prefer-rest-params _logFunctionCall(this, this.connection.debug, this.name, 'listIndexes', arguments); if (this.collection instanceof AstraCollection) { @@ -669,7 +665,7 @@ export class Collection = Record ({ ...index, key: typeof index.definition.column === 'string' ? { [index.definition.column]: 1 } : index.definition.column })); }) - }; + } as unknown as ListIndexesCursor; } /** @@ -734,7 +730,7 @@ export class Collection = Record | null = null; client: DataAPIClient | null = null; admin: AstraDbAdmin | DataAPIDbAdmin | null = null; - // @ts-expect-error astra-mongoose Db classes don't fully extend from Mongoose Db in a TypeScript-compatible way. - db: CollectionsDb | TablesDb | null = null; + declare db: undefined; + astraDb: CollectionsDb | TablesDb | undefined; keyspaceName: string | null = null; - config?: Partial; + config: Partial = {}; baseUrl: string | null = null; baseApiPath: string | null = null; models: Record> = {}; - // @ts-expect-error astra-mongoose collection currently doesn't fully extend from Mongoose collection in a TypeScript-compatible way. collections: Record = {}; _debug?: boolean | { color?: boolean, shell?: boolean } | Writable | ((name: string, fn: string, ...args: unknown[]) => void) | null | undefined; _connectionString: string | null = null; @@ -147,7 +147,7 @@ export class Connection extends MongooseConnection { // @ts-expect-error _waitForConnect not part of public API await this._waitForConnect(); // Cannot happen, but this helps TypeScript infer the correct return type - const db = this.db; + const db = this.astraDb; const admin = this.admin; assert.ok(db); assert.ok(admin); @@ -157,7 +157,7 @@ export class Connection extends MongooseConnection { } // Cannot happen, but this helps TypeScript infer the correct return type - const db = this.db; + const db = this.astraDb; const admin = this.admin; assert.ok(db); assert.ok(admin); @@ -170,7 +170,6 @@ export class Connection extends MongooseConnection { * @param options */ - // @ts-expect-error astra-mongoose collection currently doesn't fully extend from Mongoose collection in a TypeScript-compatible way. collection = Record>(name: string, options?: MongooseCollectionOptions): Collection { if (!(name in this.collections)) { // @ts-expect-error astra-mongoose collection currently doesn't fully extend from Mongoose collection in a TypeScript-compatible way. @@ -186,11 +185,10 @@ export class Connection extends MongooseConnection { * @param name The keyspace name * @param options */ - // @ts-expect-error astra-mongoose connection currently doesn't fully extend from Mongoose connection in a TypeScript-compatible way because of collections useDb(name: string, options?: UseDbOptions): Connection { options = options ?? {}; if (options.useCache && this.relatedDbs[name]) { - const cachedDb = this.relatedDbs[name].db; + const cachedDb = this.relatedDbs[name].astraDb; if (options?.isTable != null && cachedDb != null && options.isTable !== cachedDb.isTable) { throw new AstraMongooseError(`Cannot use cached connection for ${name} with isTable=${options.isTable} (cached connection is isTable=${cachedDb.isTable})`); } @@ -222,7 +220,7 @@ export class Connection extends MongooseConnection { const wireup = () => { const client = this.client; - const parentDb = this.db; + const parentDb = this.astraDb; const admin = this.admin; const baseUrl = this.baseUrl; assert.ok(client); @@ -249,7 +247,7 @@ export class Connection extends MongooseConnection { }); this.initialConnection?.catch(err => rejectInitialConnection?.(err)); - if (this.db) { + if (this.astraDb) { wireup(); } else { // @ts-expect-error _queue is an internal Mongoose property. @@ -273,13 +271,12 @@ export class Connection extends MongooseConnection { * @param options */ - // @ts-expect-error astra-mongoose collection currently doesn't fully extend from Mongoose collection in a TypeScript-compatible way. async createCollection = Record>( name: string, - options?: CreateCollectionOptions - ) { + options?: MongoCreateCollectionOptions + ): Promise> { const { db } = await this._waitForClient(); - return await db.createCollection(name, options); + return await db.createCollection(name, options as unknown as CreateCollectionOptions) as unknown as MongoDBCollection; } /** @@ -461,7 +458,6 @@ export class Connection extends MongooseConnection { * @param options */ - // @ts-expect-error astra-mongoose connection currently doesn't fully extend from Mongoose connection in a TypeScript-compatible way because of collections async openUri(uri: string, options?: ConnectOptionsInternal) { let _fireAndForget: boolean | undefined = false; if (options && '_fireAndForget' in options) { @@ -607,7 +603,7 @@ export class Connection extends MongooseConnection { collection._collection = undefined; } - this.db = db; + this.astraDb = db; this.admin = admin; // Bubble up db-level events from astra-db-ts to the main connection. @@ -624,8 +620,8 @@ export class Connection extends MongooseConnection { } _clearDbEventListeners() { - if (this.db && this._dbEventListeners) { - const dbEmitter = this.db.astraDb; + if (this.astraDb && this._dbEventListeners) { + const dbEmitter = this.astraDb.astraDb; dbEmitter.off('commandStarted', this._dbEventListeners.commandStarted); dbEmitter.off('commandFailed', this._dbEventListeners.commandFailed); dbEmitter.off('commandSucceeded', this._dbEventListeners.commandSucceeded); @@ -645,8 +641,8 @@ export class Connection extends MongooseConnection { */ async doClose() { // Remove db-level event listeners if present - if (this.db && this._dbEventListeners) { - const dbEmitter = this.db.astraDb; + if (this.astraDb && this._dbEventListeners) { + const dbEmitter = this.astraDb.astraDb; dbEmitter.off('commandStarted', this._dbEventListeners.commandStarted); dbEmitter.off('commandFailed', this._dbEventListeners.commandFailed); dbEmitter.off('commandSucceeded', this._dbEventListeners.commandSucceeded); diff --git a/tests/driver/collections.api.test.ts b/tests/driver/collections.api.test.ts index b95df77..9b76414 100644 --- a/tests/driver/collections.api.test.ts +++ b/tests/driver/collections.api.test.ts @@ -240,7 +240,7 @@ describe('COLLECTIONS: mongoose Model API level tests with collections', async ( let collections = await Product.db.listCollections().then(collections => collections.map(coll => coll.name)); assert.ok(collections.includes(Product.collection.collectionName)); - await Product.db.db!.dropCollection(Product.collection.collectionName); + await (Product.db as unknown as AstraMongooseDriver.Connection).astraDb!.dropCollection(Product.collection.collectionName); collections = await Product.db.listCollections().then(collections => collections.map(coll => coll.name)); assert.ok(!collections.includes(Product.collection.collectionName)); @@ -315,7 +315,7 @@ describe('COLLECTIONS: mongoose Model API level tests with collections', async ( it('API ops tests Model.db', async () => { const conn = Product.db as unknown as AstraMongooseDriver.Connection; assert.strictEqual(conn.keyspaceName, parseUri(testClient!.uri).keyspaceName); - assert.strictEqual(conn.db!.name, parseUri(testClient!.uri).keyspaceName); + assert.strictEqual(conn.astraDb!.name, parseUri(testClient!.uri).keyspaceName); }); it('API ops tests Model.deleteMany()', async function() { const product1 = new Product({name: 'Product 1', price: 10, isCertified: true, category: 'cat 1'}); @@ -874,7 +874,7 @@ describe('COLLECTIONS: mongoose Model API level tests with collections', async ( const { keyspaceName } = parseUri(testClient!.uri); const childConnection = connection.useDb(keyspaceName, { isTable: true }); - assert.strictEqual(childConnection.db!.isTable, true); + assert.strictEqual(childConnection.astraDb!.isTable, true); await assert.rejects( childConnection.createCollection('use_db_is_table_child'), /Cannot createCollection in tables mode/ @@ -888,7 +888,7 @@ describe('COLLECTIONS: mongoose Model API level tests with collections', async ( const { keyspaceName } = parseUri(testClient!.uri); const childConnection = connection.useDb(keyspaceName, { useCache: true, isTable: true }); - assert.strictEqual(childConnection.db!.isTable, true); + assert.strictEqual(childConnection.astraDb!.isTable, true); assert.strictEqual(connection.useDb(keyspaceName, { useCache: true, isTable: true }), childConnection); assert.throws( () => connection.useDb(keyspaceName, { useCache: true, isTable: false }), @@ -909,7 +909,7 @@ describe('COLLECTIONS: mongoose Model API level tests with collections', async ( await childConnection.asPromise(); assert.strictEqual(childConnection.client, connection.client); - assert.notStrictEqual(childConnection.db, connection.db); + assert.notStrictEqual(childConnection.astraDb, connection.astraDb); assert.strictEqual(childConnection.keyspaceName, keyspaceName); assert.ok((await promise.then(res => res.map(obj => obj.name))).includes(Product.collection.collectionName)); @@ -923,7 +923,7 @@ describe('COLLECTIONS: mongoose Model API level tests with collections', async ( await connection.openUri(testClient!.uri, testClient!.options); await childConnection.asPromise(); - assert.strictEqual(childConnection.db!.isTable, true); + assert.strictEqual(childConnection.astraDb!.isTable, true); await assert.rejects( childConnection.createCollection('use_db_is_table_child'), /Cannot createCollection in tables mode/ diff --git a/tests/driver/collections.driver.test.ts b/tests/driver/collections.driver.test.ts index 4e1999f..80bcad8 100644 --- a/tests/driver/collections.driver.test.ts +++ b/tests/driver/collections.driver.test.ts @@ -226,7 +226,7 @@ describe('COLLECTIONS: driver based tests', async () => { } const _id = new mongoose.Types.ObjectId(); - const collection = mongooseInstance.connection.db!.collection( + const collection = mongooseInstance.connection.astraDb!.collection( Product.collection.collectionName, { serdes: { enableBigNumbers: () => 'number_or_string' } } ); diff --git a/tests/driver/tables.api.test.ts b/tests/driver/tables.api.test.ts index bc38aed..ceb847b 100644 --- a/tests/driver/tables.api.test.ts +++ b/tests/driver/tables.api.test.ts @@ -206,7 +206,7 @@ describe('TABLES: Mongoose Model API level tests', async () => { it('API ops tests Model.db', async () => { const conn = Product.db as unknown as AstraMongooseDriver.Connection; assert.strictEqual(conn.keyspaceName, parseUri(testClient!.uri).keyspaceName); - assert.strictEqual(conn.db!.name, parseUri(testClient!.uri).keyspaceName); + assert.strictEqual(conn.astraDb!.name, parseUri(testClient!.uri).keyspaceName); }); it('API ops tests Model.deleteOne()', async () => { const product1 = new Product({name: 'Product 1', price: 10, isCertified: true, category: 'cat 1'}); @@ -678,8 +678,8 @@ describe('TABLES: Mongoose Model API level tests', async () => { const { keyspaceName } = parseUri(testClient!.uri); const childConnection = connection.useDb(keyspaceName, { isTable: false }); - assert.strictEqual(connection.db!.isTable, true); - assert.strictEqual(childConnection.db!.isTable, false); + assert.strictEqual(connection.astraDb!.isTable, true); + assert.strictEqual(childConnection.astraDb!.isTable, false); await connection.close(); }); @@ -695,7 +695,7 @@ describe('TABLES: Mongoose Model API level tests', async () => { await childConnection.asPromise(); assert.strictEqual(childConnection.client, connection.client); - assert.notStrictEqual(childConnection.db, connection.db); + assert.notStrictEqual(childConnection.astraDb, connection.astraDb); assert.strictEqual(childConnection.keyspaceName, keyspaceName); assert.ok((await promise.then(res => res.map(obj => obj.name))).includes(Product.collection.collectionName)); @@ -709,8 +709,8 @@ describe('TABLES: Mongoose Model API level tests', async () => { await connection.openUri(testClient!.uri, { ...testClient!.options, isTable: true }); await childConnection.asPromise(); - assert.strictEqual(connection.db!.isTable, true); - assert.strictEqual(childConnection.db!.isTable, false); + assert.strictEqual(connection.astraDb!.isTable, true); + assert.strictEqual(childConnection.astraDb!.isTable, false); await connection.close(); }); diff --git a/tests/e2e/tsconfig.json b/tests/e2e/tsconfig.json index cc3d9ed..9eda111 100644 --- a/tests/e2e/tsconfig.json +++ b/tests/e2e/tsconfig.json @@ -1,13 +1,12 @@ { "compilerOptions": { "target": "es2020", - "module": "node16", - "moduleResolution": "node16", + "module": "NodeNext", + "moduleResolution": "NodeNext", "strict": true, "noEmit": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, - "skipLibCheck": true, "types": ["node"] }, "include": ["smoke.ts"] diff --git a/tests/mongooseFixtures.ts b/tests/mongooseFixtures.ts index cb31242..a172e38 100644 --- a/tests/mongooseFixtures.ts +++ b/tests/mongooseFixtures.ts @@ -165,7 +165,7 @@ export async function createMongooseCollections(isTable: boolean) { } if (testDebug) { - mongooseInstance.connection.db!.astraDb.on('commandStarted', ev => { + mongooseInstance.connection.astraDb!.astraDb.on('commandStarted', ev => { console.log(ev.target.url, JSON.stringify(ev.command, null, ' ')); }); }