Skip to content

Commit 6fb4b8a

Browse files
authored
feat(node-runtime-worker-thread): Advanced serialization for evaluation result COMPASS-4557 (#572)
* refactor(node-runtime-worker-thread): Simplify promisified type * feat(node-runtime-worker-thread): Better serialization for errors thrown inside an rpc helper * feat(node-runtime-worker-thread): Serialize errors thrown or returned by worker runtime * refactor(node-runtime-worker-thread): Propagate serialization errors back to the client * feat(node-runtime-worker-thread): Inspect complex, non-distinguishable shell result values before returning them * fix(node-runtime-worker-thread): Use caller defined in beforeEach * feat(node-runtime-worker-thread): Serialize shell-api return types for better output * test(node-runtime-worker-thread): Add more tests for different types of return values * fix(node-runtime-worker-thread): Fix bson missing from package-lock * test(node-runtime-worker-thread): Fix anonymous fn inspection check for older node versions * refactor(browser-runtime-core, node-runtime-worker-thread): Runtimes should return only serializable parts of shell result on evaluation * refactor(node-runtime-worker-thread): Simplify {de}serialization implementation * refactor(node-runtime-worker-thread): Move v8 {de}serialize to rpc helper as the sole user of those methods * refactor(shell-api, browser-repl, cli-repl, node-runtime-worker-thread): Do not rely on non-enumerable, non-serializable properties in Cursor printable * fix(shell-api): Fix failing tests * refactor(node-runtime-worker-thread): Remove custom bson assertion * fix(browser-repl): Fix more failing tests that depended on cursor printable * refactor(node-runtime-worker-thread): Pick properties instead of omitting them * refactor(node-runtime-worker-thread): Explicitly pass typed payloads in rpc helper internally * chore(node-runtime-worker-thread): Remove confusing comments and replace them with (hopefully) less confusing ones * refactor(node-runtime-worker-thread): Use enums instead of hardcoded strings
1 parent b61f6fb commit 6fb4b8a

29 files changed

+845
-155
lines changed

packages/browser-repl/src/components/types/cursor-iteration-result-output.spec.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,18 @@ import { ObjectOutput } from './object-output';
77

88
describe('CursorIterationResultOutput', () => {
99
it('renders no ObjectOutput if value is empty', () => {
10-
const wrapper = shallow(<CursorIterationResultOutput value={[]} />);
10+
const printable = { documents: [], cursorHasMore: false };
11+
const wrapper = shallow(<CursorIterationResultOutput value={printable} />);
1112

1213
expect(wrapper.text()).to.contain('no cursor');
1314
});
1415

1516
it('renders a ObjectOutput for each element in value', () => {
16-
const wrapper = shallow(<CursorIterationResultOutput value={[{ doc: 1 }, { doc: 2 }]} />);
17+
const printable = {
18+
documents: [{ doc: 1 }, { doc: 2 }],
19+
cursorHasMore: false
20+
};
21+
const wrapper = shallow(<CursorIterationResultOutput value={printable} />);
1722

1823
expect(wrapper.find(ObjectOutput)).to.have.lengthOf(2);
1924
});

packages/browser-repl/src/components/types/cursor-iteration-result-output.tsx

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export interface Document {
88
}
99

1010
interface CursorIterationResultOutputProps {
11-
value: Document[] & { cursorHasMore: boolean };
11+
value: { documents: Document[]; cursorHasMore: boolean };
1212
}
1313

1414
export class CursorIterationResultOutput extends Component<CursorIterationResultOutputProps> {
@@ -17,17 +17,22 @@ export class CursorIterationResultOutput extends Component<CursorIterationResult
1717
};
1818

1919
render(): JSX.Element {
20-
if (!this.props.value.length) {
21-
return <div>{i18n.__('shell-api.classes.Cursor.iteration.no-cursor')}</div>;
20+
if (!this.props.value.documents.length) {
21+
return (
22+
<div>{i18n.__('shell-api.classes.Cursor.iteration.no-cursor')}</div>
23+
);
2224
}
2325

2426
const more = this.props.value.cursorHasMore ?
2527
(<pre>{i18n.__('shell-api.classes.Cursor.iteration.type-it-for-more')}</pre>) :
2628
'';
27-
return (<div>
28-
{this.props.value.map(this.renderDocument)}
29-
{more}
30-
</div>);
29+
30+
return (
31+
<div>
32+
{this.props.value.documents.map(this.renderDocument)}
33+
{more}
34+
</div>
35+
);
3136
}
3237

3338
renderDocument = (document: Document, i: number): JSX.Element => {

packages/browser-repl/src/components/types/cursor-output.spec.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,23 @@ import { CursorIterationResultOutput } from './cursor-iteration-result-output';
77

88
describe('CursorOutput', () => {
99
it('renders "no cursor" if value is empty', () => {
10-
const wrapper = shallow(<CursorOutput value={[]} />);
10+
const docs = { documents: [], cursorHasMore: false };
11+
const wrapper = shallow(<CursorOutput value={docs} />);
1112

1213
expect(wrapper.find(CursorIterationResultOutput)).to.have.lengthOf(0);
1314
});
1415

1516

1617
it('renders a CursorIterationResultOutput if value contains elements', () => {
17-
const docs = [{ doc: 1 }, { doc: 2 }];
18+
const docs = { documents: [{ doc: 1 }, { doc: 2 }], cursorHasMore: false };
1819
const wrapper = shallow(<CursorOutput value={docs} />);
1920

2021
expect(wrapper.find(CursorIterationResultOutput).prop('value')).to.deep.equal(docs);
2122
});
2223

2324
context('when value has more elements available', () => {
2425
it('prompts to type "it"', () => {
25-
const docs = Object.assign([{}], { cursorHasMore: true });
26+
const docs = { documents: [{}], cursorHasMore: true };
2627
const wrapper = mount(<CursorOutput value={docs} />);
2728

2829
expect(wrapper.find(CursorIterationResultOutput).text()).to.contain('Type "it" for more');
@@ -31,7 +32,7 @@ describe('CursorOutput', () => {
3132

3233
context('when value does not have more elements available', () => {
3334
it('does not prompt to type "it"', () => {
34-
const docs = Object.assign([{}], { cursorHasMore: false });
35+
const docs = { documents: [{}], cursorHasMore: false };
3536
const wrapper = mount(<CursorOutput value={docs} />);
3637

3738
expect(wrapper.find(CursorIterationResultOutput).text()).not.to.contain('Type "it" for more');

packages/browser-repl/src/components/types/cursor-output.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
33
import { CursorIterationResultOutput, Document } from './cursor-iteration-result-output';
44

55
interface CursorOutputProps {
6-
value: Document[] & { cursorHasMore: boolean };
6+
value: { documents: Document[], cursorHasMore: boolean };
77
}
88

99
export class CursorOutput extends Component<CursorOutputProps> {
@@ -12,7 +12,7 @@ export class CursorOutput extends Component<CursorOutputProps> {
1212
};
1313

1414
render(): JSX.Element {
15-
if (!this.props.value.length) {
15+
if (!this.props.value.documents.length) {
1616
return <pre/>;
1717
}
1818

packages/browser-repl/src/iframe-runtime/iframe-runtime.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,27 @@ import {
44

55
import {
66
Runtime,
7+
RuntimeEvaluationListener,
8+
RuntimeEvaluationResult,
79
Completion,
810
OpenContextRuntime
911
} from '@mongosh/browser-runtime-core';
1012

1113
import { ServiceProvider } from '@mongosh/service-provider-core';
12-
import { ShellResult, EvaluationListener } from '@mongosh/shell-evaluator';
1314

1415
export class IframeRuntime implements Runtime {
1516
private openContextRuntime: OpenContextRuntime | null = null;
1617
private readyPromise: Promise<void> | null = null;
1718
private iframe: HTMLIFrameElement | null = null;
1819
private container: HTMLDivElement | null = null;
1920
private serviceProvider: ServiceProvider;
20-
private evaluationListener: EvaluationListener | null = null;
21+
private evaluationListener: RuntimeEvaluationListener | null = null;
2122

2223
constructor(serviceProvider: ServiceProvider) {
2324
this.serviceProvider = serviceProvider;
2425
}
2526

26-
setEvaluationListener(listener: EvaluationListener): EvaluationListener | null {
27+
setEvaluationListener(listener: RuntimeEvaluationListener): RuntimeEvaluationListener | null {
2728
const prev = this.evaluationListener;
2829
this.evaluationListener = listener;
2930
if (this.openContextRuntime) {
@@ -32,7 +33,7 @@ export class IframeRuntime implements Runtime {
3233
return prev;
3334
}
3435

35-
async evaluate(code: string): Promise<ShellResult> {
36+
async evaluate(code: string): Promise<RuntimeEvaluationResult> {
3637
const runtime = await this.initialize();
3738
return await runtime.evaluate(code);
3839
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export { Runtime } from './runtime';
1+
export { Runtime, RuntimeEvaluationListener, RuntimeEvaluationResult } from './runtime';
22
export { ContextValue, InterpreterEnvironment } from './interpreter';
33
export { OpenContextRuntime } from './open-context-runtime';
44
export { Autocompleter, Completion } from './autocompleter/autocompleter';

packages/browser-runtime-core/src/open-context-runtime.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@ import { Completion } from './autocompleter/autocompleter';
22
import { ServiceProvider } from '@mongosh/service-provider-core';
33
import { ShellApiAutocompleter } from './autocompleter/shell-api-autocompleter';
44
import { Interpreter, InterpreterEnvironment } from './interpreter';
5-
import { Runtime } from './runtime';
5+
import {
6+
Runtime,
7+
RuntimeEvaluationResult,
8+
RuntimeEvaluationListener
9+
} from './runtime';
610
import { EventEmitter } from 'events';
7-
import { ShellInternalState, ShellResult } from '@mongosh/shell-api';
11+
import { ShellInternalState } from '@mongosh/shell-api';
812

9-
import { ShellEvaluator, EvaluationListener } from '@mongosh/shell-evaluator';
13+
import { ShellEvaluator } from '@mongosh/shell-evaluator';
1014
import type { MongoshBus } from '@mongosh/types';
1115

1216
/**
@@ -23,7 +27,7 @@ export class OpenContextRuntime implements Runtime {
2327
private autocompleter: ShellApiAutocompleter | null = null;
2428
private shellEvaluator: ShellEvaluator;
2529
private internalState: ShellInternalState;
26-
private evaluationListener: EvaluationListener | null = null;
30+
private evaluationListener: RuntimeEvaluationListener | null = null;
2731
private updatedConnectionInfo = false;
2832

2933
constructor(
@@ -48,17 +52,18 @@ export class OpenContextRuntime implements Runtime {
4852
return this.autocompleter.getCompletions(code);
4953
}
5054

51-
async evaluate(code: string): Promise<ShellResult> {
55+
async evaluate(code: string): Promise<RuntimeEvaluationResult> {
5256
const evalFn = this.interpreter.evaluate.bind(this.interpreter);
53-
return await this.shellEvaluator.customEval(
57+
const { type, printable, source } = await this.shellEvaluator.customEval(
5458
evalFn,
5559
code,
5660
this.interpreterEnvironment.getContextObject(),
5761
''
5862
);
63+
return { type, printable, source };
5964
}
6065

61-
setEvaluationListener(listener: EvaluationListener): EvaluationListener | null {
66+
setEvaluationListener(listener: RuntimeEvaluationListener): RuntimeEvaluationListener | null {
6267
const prev = this.evaluationListener;
6368
this.evaluationListener = listener;
6469
this.internalState.setEvaluationListener(listener);

packages/browser-runtime-core/src/runtime.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,34 @@ import { ShellResult, EvaluationListener } from '@mongosh/shell-evaluator';
33

44
export type ContextValue = any;
55

6+
export type RuntimeEvaluationResult = Pick<
7+
ShellResult,
8+
'type' | 'printable' | 'source'
9+
>;
10+
11+
export interface RuntimeEvaluationListener extends EvaluationListener {
12+
onPrint?: (value: RuntimeEvaluationResult[]) => Promise<void> | void;
13+
}
14+
615
export interface Runtime {
716
/**
817
* Sets a listener for certain events, e.g. onPrint() when print() is called
918
* in the shell.
1019
*
11-
* @param {EvaluationListener} listener - The new listener.
12-
* @return {EvaluationListener | null} The previous listener, if any.
20+
* @param {RuntimeEvaluationListener} listener - The new listener.
21+
* @return {RuntimeEvaluationListener | null} The previous listener, if any.
1322
*/
14-
setEvaluationListener(listener: EvaluationListener): EvaluationListener | null;
23+
setEvaluationListener(
24+
listener: RuntimeEvaluationListener
25+
): RuntimeEvaluationListener | null;
1526

1627
/**
1728
* Evaluates code
1829
*
1930
* @param {string} code - A string of code
20-
* @return {Promise<ShellResult>} the result of the evaluation
31+
* @return {Promise<RuntimeEvaluationResult>} the result of the evaluation
2132
*/
22-
evaluate(code: string): Promise<ShellResult>;
33+
evaluate(code: string): Promise<RuntimeEvaluationResult>;
2334

2435
/**
2536
* Get shell api completions give a code prefix

packages/browser-runtime-electron/src/electron-runtime.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ import { CliServiceProvider } from '@mongosh/service-provider-server';
88
import { bson } from '@mongosh/service-provider-core';
99
import { ElectronRuntime } from './electron-runtime';
1010
import { EventEmitter } from 'events';
11-
import { EvaluationListener } from '@mongosh/shell-evaluator';
11+
import { RuntimeEvaluationListener } from '@mongosh/browser-runtime-core';
1212

1313
describe('Electron runtime', function() {
1414
let serviceProvider: SinonStubbedInstance<CliServiceProvider>;
1515
let messageBus: SinonStubbedInstance<EventEmitter>;
16-
let evaluationListener: SinonStubbedInstance<EvaluationListener>;
16+
let evaluationListener: SinonStubbedInstance<RuntimeEvaluationListener>;
1717
let electronRuntime: ElectronRuntime;
1818

1919
beforeEach(async() => {

packages/browser-runtime-electron/src/electron-runtime.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ import {
66
import {
77
Runtime,
88
OpenContextRuntime,
9-
Completion
9+
Completion,
10+
RuntimeEvaluationListener,
11+
RuntimeEvaluationResult
1012
} from '@mongosh/browser-runtime-core';
1113

1214
import { ServiceProvider } from '@mongosh/service-provider-core';
13-
import { ShellResult, EvaluationListener } from '@mongosh/shell-evaluator';
1415
import type { MongoshBus } from '@mongosh/types';
1516

1617
declare const __webpack_require__: any;
@@ -42,11 +43,11 @@ export class ElectronRuntime implements Runtime {
4243
);
4344
}
4445

45-
setEvaluationListener(listener: EvaluationListener): EvaluationListener | null {
46+
setEvaluationListener(listener: RuntimeEvaluationListener): RuntimeEvaluationListener | null {
4647
return this.openContextRuntime.setEvaluationListener(listener);
4748
}
4849

49-
async evaluate(code: string): Promise<ShellResult> {
50+
async evaluate(code: string): Promise<RuntimeEvaluationResult> {
5051
return await this.openContextRuntime.evaluate(code);
5152
}
5253

0 commit comments

Comments
 (0)