Skip to content

Commit 2428a91

Browse files
committed
use DeepInspectServiceProviderWrapper, install our own inspect function on results
1 parent 40a8727 commit 2428a91

File tree

3 files changed

+146
-37
lines changed

3 files changed

+146
-37
lines changed
Lines changed: 138 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import type {
22
ServiceProvider,
3-
ServiceProviderAnyCursor,
43
ServiceProviderAbstractCursor,
54
} from '@mongosh/service-provider-core';
65
import { ServiceProviderCore } from '@mongosh/service-provider-core';
6+
import type { InspectOptions, inspect as _inspect } from 'util';
7+
import type { Document } from '@mongosh/service-provider-core';
78

89
export class DeepInspectServiceProviderWrapper
910
extends ServiceProviderCore
@@ -24,26 +25,28 @@ export class DeepInspectServiceProviderWrapper
2425

2526
aggregate = cursorMethod('aggregate');
2627
aggregateDb = cursorMethod('aggregateDb');
27-
count = bsonMethod('count');
28-
estimatedDocumentCount = bsonMethod('estimatedDocumentCount');
29-
countDocuments = bsonMethod('countDocuments');
28+
count = forwardedMethod('count');
29+
estimatedDocumentCount = forwardedMethod('estimatedDocumentCount');
30+
countDocuments = forwardedMethod('countDocuments');
3031
distinct = bsonMethod('distinct');
3132
find = cursorMethod('find');
3233
findOneAndDelete = bsonMethod('findOneAndDelete');
3334
findOneAndReplace = bsonMethod('findOneAndReplace');
3435
findOneAndUpdate = bsonMethod('findOneAndUpdate');
35-
getTopology = forwardedMethod('getTopology');
36+
getTopologyDescription = forwardedMethod('getTopologyDescription');
3637
getIndexes = bsonMethod('getIndexes');
3738
listCollections = bsonMethod('listCollections');
3839
readPreferenceFromOptions = forwardedMethod('readPreferenceFromOptions');
39-
watch = cursorMethod('watch');
40+
// TODO: this should be a cursor method, but the types are incompatible
41+
watch = forwardedMethod('watch');
4042
getSearchIndexes = bsonMethod('getSearchIndexes');
4143
runCommand = bsonMethod('runCommand');
4244
runCommandWithCheck = bsonMethod('runCommandWithCheck');
4345
runCursorCommand = cursorMethod('runCursorCommand');
4446
dropDatabase = bsonMethod('dropDatabase');
45-
dropCollection = bsonMethod('dropCollection');
47+
dropCollection = forwardedMethod('dropCollection');
4648
bulkWrite = bsonMethod('bulkWrite');
49+
clientBulkWrite = bsonMethod('clientBulkWrite');
4750
deleteMany = bsonMethod('deleteMany');
4851
updateMany = bsonMethod('updateMany');
4952
updateOne = bsonMethod('updateOne');
@@ -53,15 +56,15 @@ export class DeepInspectServiceProviderWrapper
5356
insertOne = bsonMethod('insertOne');
5457
replaceOne = bsonMethod('replaceOne');
5558
initializeBulkOp = bsonMethod('initializeBulkOp');
56-
createSearchIndexes = bsonMethod('createSearchIndexes');
59+
createSearchIndexes = forwardedMethod('createSearchIndexes');
5760
close = forwardedMethod('close');
5861
suspend = forwardedMethod('suspend');
59-
renameCollection = bsonMethod('renameCollection');
60-
dropSearchIndex = bsonMethod('dropSearchIndex');
61-
updateSearchIndex = bsonMethod('updateSearchIndex');
62+
renameCollection = forwardedMethod('renameCollection');
63+
dropSearchIndex = forwardedMethod('dropSearchIndex');
64+
updateSearchIndex = forwardedMethod('updateSearchIndex');
6265
listDatabases = bsonMethod('listDatabases');
63-
authenticate = bsonMethod('authenticate');
64-
createCollection = bsonMethod('createCollection');
66+
authenticate = forwardedMethod('authenticate');
67+
createCollection = forwardedMethod('createCollection');
6568
getReadPreference = forwardedMethod('getReadPreference');
6669
getReadConcern = forwardedMethod('getReadConcern');
6770
getWriteConcern = forwardedMethod('getWriteConcern');
@@ -72,6 +75,7 @@ export class DeepInspectServiceProviderWrapper
7275
get initialDb() {
7376
return this._sp.initialDb;
7477
}
78+
7579
getURI = forwardedMethod('getURI');
7680
getConnectionInfo = forwardedMethod('getConnectionInfo');
7781
resetConnectionOptions = forwardedMethod('resetConnectionOptions');
@@ -84,20 +88,11 @@ export class DeepInspectServiceProviderWrapper
8488
async getNewConnection(
8589
...args: Parameters<ServiceProvider['getNewConnection']>
8690
): Promise<ServiceProvider> {
87-
return new DeepInspectServiceProviderWrapper(
88-
await this._sp.getNewConnection(...args)
89-
);
91+
const sp = await this._sp.getNewConnection(...args);
92+
return new DeepInspectServiceProviderWrapper(sp as ServiceProvider);
9093
}
9194
}
9295

93-
const cursorBsonMethods: (keyof Partial<ServiceProviderAnyCursor>)[] = [
94-
'next',
95-
'tryNext',
96-
'readBufferedDocuments',
97-
'toArray',
98-
'',
99-
];
100-
10196
type PickMethodsByReturnType<T, R> = {
10297
[k in keyof T as NonNullable<T[k]> extends (...args: any[]) => R
10398
? k
@@ -107,33 +102,104 @@ type PickMethodsByReturnType<T, R> = {
107102
function cursorMethod<
108103
K extends keyof PickMethodsByReturnType<
109104
ServiceProvider,
110-
ServiceProviderAnyCursor
105+
ServiceProviderAbstractCursor
111106
>
112107
>(
113108
key: K
114109
): (
115110
...args: Parameters<Required<ServiceProvider>[K]>
116111
) => ReturnType<Required<ServiceProvider>[K]> {
117112
return function (
118-
this: ServiceProvider,
113+
this: DeepInspectServiceProviderWrapper,
119114
...args: Parameters<ServiceProvider[K]>
120115
): ReturnType<ServiceProvider[K]> {
121-
return this[key](...args);
116+
// The problem here is that ReturnType<ServiceProvider[K]> results in
117+
// ServiceProviderAnyCursor which includes ServiceProviderChangeStream which
118+
// doesn't have readBufferedDocuments or toArray. We can try cast things to
119+
// ServiceProviderAbstractCursor, but then that's not assignable to
120+
// ServiceProviderAnyCursor. And that's why there's so much casting below.
121+
const cursor = (this._sp[key] as any)(...args) as any;
122+
123+
cursor.next = cursorNext(
124+
cursor.next.bind(cursor) as () => Promise<Document | null>
125+
);
126+
cursor.tryNext = cursorTryNext(
127+
cursor.tryNext.bind(cursor) as () => Promise<Document | null>
128+
);
129+
130+
if (cursor.readBufferedDocuments) {
131+
cursor.readBufferedDocuments = cursorReadBufferedDocuments(
132+
cursor.readBufferedDocuments.bind(cursor) as (
133+
number?: number
134+
) => Document[]
135+
);
136+
}
137+
if (cursor.toArray) {
138+
cursor.toArray = cursorToArray(
139+
cursor.toArray.bind(cursor) as () => Promise<Document[]>
140+
);
141+
}
142+
143+
return cursor;
144+
};
145+
}
146+
147+
const customInspectSymbol = Symbol.for('nodejs.util.inspect.custom');
148+
149+
function cursorNext(
150+
original: () => Promise<Document | null>
151+
): () => Promise<Document | null> {
152+
return async function (): Promise<Document | null> {
153+
const result = await original();
154+
if (result) {
155+
replaceWithCustomInspect(result);
156+
}
157+
return result;
158+
};
159+
}
160+
161+
const cursorTryNext = cursorNext;
162+
163+
function cursorReadBufferedDocuments(
164+
original: (number?: number) => Document[]
165+
): (number?: number) => Document[] {
166+
return function (number?: number): Document[] {
167+
const results = original(number);
168+
169+
replaceWithCustomInspect(results);
170+
171+
return results;
172+
};
173+
}
174+
175+
function cursorToArray(
176+
original: () => Promise<Document[]>
177+
): () => Promise<Document[]> {
178+
return async function (): Promise<Document[]> {
179+
const results = await original();
180+
181+
replaceWithCustomInspect(results);
182+
183+
return results;
122184
};
123185
}
124186

125187
function bsonMethod<
126-
K extends keyof PickMethodsByReturnType<ServiceProvider, any>
188+
K extends keyof PickMethodsByReturnType<ServiceProvider, Promise<any>>
127189
>(
128190
key: K
129191
): (
130192
...args: Parameters<Required<ServiceProvider>[K]>
131193
) => ReturnType<Required<ServiceProvider>[K]> {
132-
return function (
133-
this: ServiceProvider,
194+
return async function (
195+
this: DeepInspectServiceProviderWrapper,
134196
...args: Parameters<Required<ServiceProvider>[K]>
135-
): ReturnType<Required<ServiceProvider>[K]> {
136-
return this[key](...args);
197+
): // eslint-disable-next-line @typescript-eslint/ban-ts-comment
198+
// @ts-ignore The returntype already contains a promise
199+
ReturnType<Required<ServiceProvider>[K]> {
200+
const result = await (this._sp[key] as any)(...args);
201+
replaceWithCustomInspect(result);
202+
return result;
137203
};
138204
}
139205

@@ -145,9 +211,47 @@ function forwardedMethod<
145211
...args: Parameters<Required<ServiceProvider>[K]>
146212
) => ReturnType<Required<ServiceProvider>[K]> {
147213
return function (
148-
this: ServiceProvider,
214+
this: DeepInspectServiceProviderWrapper,
149215
...args: Parameters<Required<ServiceProvider>[K]>
150216
): ReturnType<Required<ServiceProvider>[K]> {
151-
return this[key](...args);
217+
// not wrapping the result at all because forwardedMethod() is for simple
218+
// values only
219+
return (this._sp[key] as any)(...args);
152220
};
153221
}
222+
223+
function customDocumentInspect(
224+
this: Document,
225+
depth: number,
226+
inspectOptions: InspectOptions,
227+
inspect: typeof _inspect
228+
) {
229+
const newInspectOptions = {
230+
...inspectOptions,
231+
depth: Infinity,
232+
maxArrayLength: Infinity,
233+
maxStringLength: Infinity,
234+
};
235+
236+
// reuse the standard inpect logic for an object without causing infinite
237+
// recursion
238+
const inspectBackup = (this as any)[customInspectSymbol];
239+
delete (this as any)[customInspectSymbol];
240+
const result = inspect(this, newInspectOptions);
241+
(this as any)[customInspectSymbol] = inspectBackup;
242+
return result;
243+
}
244+
245+
function replaceWithCustomInspect(obj: any) {
246+
if (Array.isArray(obj)) {
247+
(obj as any)[customInspectSymbol] = customDocumentInspect;
248+
for (const item of obj) {
249+
replaceWithCustomInspect(item);
250+
}
251+
} else if (obj && typeof obj === 'object' && obj !== null) {
252+
obj[customInspectSymbol] = customDocumentInspect;
253+
for (const value of Object.values(obj)) {
254+
replaceWithCustomInspect(value);
255+
}
256+
}
257+
}

packages/shell-api/src/mongo.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ export default class Mongo<
103103
super();
104104
this._connectionId = nextId++;
105105
this._instanceState = instanceState;
106+
106107
if (sp) {
107108
this.__serviceProvider = sp;
108109
}
@@ -147,6 +148,7 @@ export default class Mongo<
147148
this._connectionInfo.driverOptions.autoEncryption = spFleOptions;
148149
}
149150
}
151+
150152
if (otherOptions?.api) {
151153
if (typeof otherOptions.api === 'string') {
152154
this._connectionInfo.driverOptions.serverApi = {

packages/shell-api/src/shell-instance-state.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import type { AutocompletionContext } from '@mongodb-js/mongodb-ts-autocomplete'
5151
import type { JSONSchema } from 'mongodb-schema';
5252
import { analyzeDocuments } from 'mongodb-schema';
5353
import type { BaseCursor } from './abstract-cursor';
54+
import { DeepInspectServiceProviderWrapper } from './deep-inspect-service-provider-wrapper';
5455

5556
/**
5657
* The subset of CLI options that is relevant for the shell API's behavior itself.
@@ -203,7 +204,9 @@ export class ShellInstanceState {
203204
cliOptions: ShellCliOptions = {},
204205
bsonLibrary: BSONLibrary = initialServiceProvider.bsonLibrary
205206
) {
206-
this.initialServiceProvider = initialServiceProvider;
207+
this.initialServiceProvider = new DeepInspectServiceProviderWrapper(
208+
initialServiceProvider
209+
);
207210
this.bsonLibrary = bsonLibrary;
208211
this.messageBus = messageBus;
209212
this.shellApi = new ShellApi(this);
@@ -220,11 +223,11 @@ export class ShellInstanceState {
220223
undefined,
221224
undefined,
222225
undefined,
223-
initialServiceProvider
226+
this.initialServiceProvider
224227
);
225228
this.mongos.push(mongo);
226229
this.currentDb = mongo.getDB(
227-
initialServiceProvider.initialDb || DEFAULT_DB
230+
this.initialServiceProvider.initialDb || DEFAULT_DB
228231
);
229232
} else {
230233
this.currentDb = new NoDatabase() as DatabaseWithSchema;

0 commit comments

Comments
 (0)