From 68b7c96acae19c57972c4c52e79ece63bf080cde Mon Sep 17 00:00:00 2001 From: Eduarda Scharnhorst Date: Wed, 29 Jul 2026 09:56:35 +0100 Subject: [PATCH 1/9] chore: match mongodb-schema version with latest version from current package --- package-lock.json | 10 +++++----- packages/mongodb-schema/package.json | 2 +- packages/mongodb-ts-autocomplete/package.json | 2 +- packages/mql-typescript/package.json | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1d94a15b..b8ace9da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29331,7 +29331,7 @@ }, "packages/mongodb-schema": { "name": "@mongodb-js/mongodb-schema", - "version": "0.2.0", + "version": "12.7.0", "license": "Apache-2.0", "dependencies": { "reservoir": "^0.1.2" @@ -29690,7 +29690,7 @@ "version": "0.9.0", "license": "Apache-2.0", "dependencies": { - "@mongodb-js/mongodb-schema": "^0.2.0", + "@mongodb-js/mongodb-schema": "^12.7.0", "@mongodb-js/ts-autocomplete": "^0.5.13", "@mongosh/shell-api": "^5.1.6", "debug": "^4.4.0", @@ -30084,7 +30084,7 @@ }, "devDependencies": { "@mongodb-js/eslint-config-devtools": "^0.11.8", - "@mongodb-js/mongodb-schema": "^0.2.0", + "@mongodb-js/mongodb-schema": "^12.7.0", "@mongodb-js/prettier-config-devtools": "^1.0.4", "@mongodb-js/shell-bson-parser": "^1.5.14", "@mongodb-js/tsconfig-devtools": "^1.1.3", @@ -37312,7 +37312,7 @@ "requires": { "@mongodb-js/eslint-config-devtools": "^0.11.8", "@mongodb-js/mocha-config-devtools": "^1.1.3", - "@mongodb-js/mongodb-schema": "^0.2.0", + "@mongodb-js/mongodb-schema": "^12.7.0", "@mongodb-js/mql-typescript": "^0.7.0", "@mongodb-js/prettier-config-devtools": "^1.0.4", "@mongodb-js/ts-autocomplete": "^0.5.13", @@ -37568,7 +37568,7 @@ "version": "file:packages/mql-typescript", "requires": { "@mongodb-js/eslint-config-devtools": "^0.11.8", - "@mongodb-js/mongodb-schema": "^0.2.0", + "@mongodb-js/mongodb-schema": "^12.7.0", "@mongodb-js/prettier-config-devtools": "^1.0.4", "@mongodb-js/shell-bson-parser": "^1.5.14", "@mongodb-js/tsconfig-devtools": "^1.1.3", diff --git a/packages/mongodb-schema/package.json b/packages/mongodb-schema/package.json index a9b2323e..e3a88a6c 100644 --- a/packages/mongodb-schema/package.json +++ b/packages/mongodb-schema/package.json @@ -13,7 +13,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/devtools-shared", - "version": "0.2.0", + "version": "12.7.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/devtools-shared.git" diff --git a/packages/mongodb-ts-autocomplete/package.json b/packages/mongodb-ts-autocomplete/package.json index 2b078d51..58d66e50 100644 --- a/packages/mongodb-ts-autocomplete/package.json +++ b/packages/mongodb-ts-autocomplete/package.json @@ -53,7 +53,7 @@ "extract-types": "ts-node scripts/extract-types.ts" }, "dependencies": { - "@mongodb-js/mongodb-schema": "^0.2.0", + "@mongodb-js/mongodb-schema": "^12.7.0", "@mongodb-js/ts-autocomplete": "^0.5.13", "@mongosh/shell-api": "^5.1.6", "debug": "^4.4.0", diff --git a/packages/mql-typescript/package.json b/packages/mql-typescript/package.json index 62fa5aaa..8adebd72 100644 --- a/packages/mql-typescript/package.json +++ b/packages/mql-typescript/package.json @@ -71,7 +71,7 @@ }, "devDependencies": { "@mongodb-js/eslint-config-devtools": "^0.11.8", - "@mongodb-js/mongodb-schema": "^0.2.0", + "@mongodb-js/mongodb-schema": "^12.7.0", "@mongodb-js/prettier-config-devtools": "^1.0.4", "@mongodb-js/shell-bson-parser": "^1.5.14", "@mongodb-js/tsconfig-devtools": "^1.1.3", From a66292baac1811fc20e3b1b6b2eb2db215f140b2 Mon Sep 17 00:00:00 2001 From: Eduarda Scharnhorst Date: Wed, 29 Jul 2026 10:25:46 +0100 Subject: [PATCH 2/9] fix: return allowAbort with signal in allowAbortDuringAnalysis method --- packages/mongodb-schema/src/schema-analyzer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mongodb-schema/src/schema-analyzer.ts b/packages/mongodb-schema/src/schema-analyzer.ts index e8b373d2..570a4312 100644 --- a/packages/mongodb-schema/src/schema-analyzer.ts +++ b/packages/mongodb-schema/src/schema-analyzer.ts @@ -603,7 +603,7 @@ export class SchemaAnalyzer { allowAbortDuringAnalysis(): Promise { // Allow aborting the analysis. if (this.fieldAndTypeAnalysisCounter++ % ALLOW_ABORT_INTERVAL_COUNT === 0) { - void allowAbort(); + return allowAbort(this.options.signal); } return Promise.resolve(); } From e9d0be234bb7c8aec1bb80b05b8e26f7320550ce Mon Sep 17 00:00:00 2001 From: Eduarda Scharnhorst Date: Wed, 29 Jul 2026 10:31:31 +0100 Subject: [PATCH 3/9] test: add abort signal tests for SchemaAnalyzer --- packages/mongodb-schema/test/bloated.test.ts | 42 ++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/packages/mongodb-schema/test/bloated.test.ts b/packages/mongodb-schema/test/bloated.test.ts index eeec6d34..91808816 100644 --- a/packages/mongodb-schema/test/bloated.test.ts +++ b/packages/mongodb-schema/test/bloated.test.ts @@ -2,6 +2,7 @@ import assert from 'assert'; import { Binary, Code } from 'bson'; import type { PrimitiveSchemaType } from '../src/schema-analyzer'; +import { SchemaAnalyzer } from '../src/schema-analyzer'; import getSchema from '../src'; function generateRandomString(length: number) { @@ -139,4 +140,45 @@ describe('bloated documents', function () { } }); }); + + describe('abort signal', function () { + // `analyzeDoc` is exercised directly here: `getCompletedSchemaAnalyzer` + // checks the signal in between documents, which would hide whether the + // signal is honoured *during* the analysis of a single document. + const document = { + field1: { + field2: 'abc', + }, + field3: 'bca', + }; + + it('aborts during the analysis of a document with the signal reason', async function () { + const controller = new AbortController(); + const reason = new Error('Analysis no longer needed'); + controller.abort(reason); + + const analyzer = new SchemaAnalyzer({ signal: controller.signal }); + await assert.rejects(analyzer.analyzeDoc(document), reason); + }); + + it("aborts during the analysis of a document with the signal's default reason", async function () { + const controller = new AbortController(); + controller.abort(); + + const analyzer = new SchemaAnalyzer({ signal: controller.signal }); + await assert.rejects(analyzer.analyzeDoc(document), { + name: 'AbortError', + }); + }); + + it('does not abort while the signal is not aborted', async function () { + const controller = new AbortController(); + + const analyzer = new SchemaAnalyzer({ signal: controller.signal }); + await analyzer.analyzeDoc(document); + + const fieldNames = analyzer.getResult().fields.map((v) => v.name); + assert.deepStrictEqual(fieldNames, ['field1', 'field3']); + }); + }); }); From e6692fd65012a97deb88ac76c527179b7b4de663 Mon Sep 17 00:00:00 2001 From: Eduarda Scharnhorst Date: Wed, 29 Jul 2026 10:38:41 +0100 Subject: [PATCH 4/9] fix: pass abort signal to parseTypes in parseType function --- .../mongodb-schema/src/schema-converters/internal-to-mongodb.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mongodb-schema/src/schema-converters/internal-to-mongodb.ts b/packages/mongodb-schema/src/schema-converters/internal-to-mongodb.ts index 9ff1dd83..3e2f7be0 100644 --- a/packages/mongodb-schema/src/schema-converters/internal-to-mongodb.ts +++ b/packages/mongodb-schema/src/schema-converters/internal-to-mongodb.ts @@ -59,7 +59,7 @@ async function parseType( }; if (type.bsonType === 'Array') { - const items = await parseTypes((type as ArraySchemaType).types); + const items = await parseTypes((type as ArraySchemaType).types, signal); // Don't include empty bson type arrays (it's invalid). if (!items.bsonType || items.bsonType?.length > 0) { schema.items = items; From 85e38ef7c45ad2de77ba26b63a930383225212e6 Mon Sep 17 00:00:00 2001 From: Eduarda Scharnhorst Date: Wed, 29 Jul 2026 10:53:59 +0100 Subject: [PATCH 5/9] test: add case stability tests for field order comparator --- packages/mongodb-schema/src/schema-analyzer.ts | 5 ++++- packages/mongodb-schema/test/field-order.test.ts | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/mongodb-schema/src/schema-analyzer.ts b/packages/mongodb-schema/src/schema-analyzer.ts index 570a4312..3c3c6048 100644 --- a/packages/mongodb-schema/src/schema-analyzer.ts +++ b/packages/mongodb-schema/src/schema-analyzer.ts @@ -256,7 +256,10 @@ function fieldComparator( return 1; } // Otherwise sort case-insensitively. - return aName.toLowerCase() < bName.toLowerCase() ? -1 : 1; + const aLower = aName.toLowerCase(); + const bLower = bName.toLowerCase(); + + return aLower < bLower ? -1 : aLower > bLower ? 1 : 0; } /** diff --git a/packages/mongodb-schema/test/field-order.test.ts b/packages/mongodb-schema/test/field-order.test.ts index 538438d9..ed79a6ab 100644 --- a/packages/mongodb-schema/test/field-order.test.ts +++ b/packages/mongodb-schema/test/field-order.test.ts @@ -34,4 +34,19 @@ describe('order of fields', function () { ['a', 'b', 'Ca', 'cb', 'cC'], ); }); + it('should keep keys that only differ in case in the order they appear in', async function () { + // The comparator treats these as equal, so the sort - being stable - has to + // leave them in the order the documents introduced them in. + const upperFirst = await getSchema([{ cb: 1, Ca: 1, ca: 1, a: 1 }]); + assert.deepEqual( + upperFirst.fields.map((v) => v.name), + ['a', 'Ca', 'ca', 'cb'], + ); + + const lowerFirst = await getSchema([{ cb: 1, ca: 1, Ca: 1, a: 1 }]); + assert.deepEqual( + lowerFirst.fields.map((v) => v.name), + ['a', 'ca', 'Ca', 'cb'], + ); + }); }); From 29cef76eaad300ae016246fc85b0abe8d7d60504 Mon Sep 17 00:00:00 2001 From: Eduarda Scharnhorst Date: Wed, 29 Jul 2026 11:03:35 +0100 Subject: [PATCH 6/9] fix: ensure correct syntax for array type in TypeScript definition --- .../mongodb-schema/src/to-typescript.spec.ts | 18 +++++++++++++++++- packages/mongodb-schema/src/to-typescript.ts | 2 +- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/mongodb-schema/src/to-typescript.spec.ts b/packages/mongodb-schema/src/to-typescript.spec.ts index 6b85e83f..6e38a2da 100644 --- a/packages/mongodb-schema/src/to-typescript.spec.ts +++ b/packages/mongodb-schema/src/to-typescript.spec.ts @@ -22,6 +22,7 @@ import { } from 'bson'; import { inspect } from 'util'; +import * as ts from 'typescript'; function convertAndCompare(schema: MongoDBJSONSchema, expected: string) { const code = toTypescriptTypeDefinition(schema); @@ -42,6 +43,21 @@ function convertAndCompare(schema: MongoDBJSONSchema, expected: string) { throw err; } + + // Comparing strings cannot tell us whether the generated code is actually + // valid TypeScript, so parse it as well. This is a syntax-only check, which + // means unresolved names like `bson.Double` are not reported. + const { diagnostics } = ts.transpileModule(`type Generated = ${code};`, { + reportDiagnostics: true, + compilerOptions: {}, + }); + assert.deepEqual( + (diagnostics ?? []).map((diagnostic) => + ts.flattenDiagnosticMessageText(diagnostic.messageText, ' '), + ), + [], + 'generated type definition is not valid TypeScript', + ); } describe('toTypescriptTypeDefinition', function () { @@ -104,7 +120,7 @@ describe('toTypescriptTypeDefinition', function () { schema, `{ _id?: bson.ObjectId; - array?: bson.Double | number)[]; + array?: (bson.Double | number)[]; binaries?: { binaryOld?: bson.Binary; compressedTimeSeries?: bson.Binary; diff --git a/packages/mongodb-schema/src/to-typescript.ts b/packages/mongodb-schema/src/to-typescript.ts index 7b2c181e..db6e6468 100644 --- a/packages/mongodb-schema/src/to-typescript.ts +++ b/packages/mongodb-schema/src/to-typescript.ts @@ -105,7 +105,7 @@ function arrayType(types: string[]) { if (types.length === 1) { return `${types[0]}[]`; } - return `${types.join(' | ')})[]`; + return `(${types.join(' | ')})[]`; } function toTypescriptType( From 64a51c3e12c94da3d247e78d6ee617a316c4dbf8 Mon Sep 17 00:00:00 2001 From: Eduarda Scharnhorst Date: Wed, 29 Jul 2026 11:17:23 +0100 Subject: [PATCH 7/9] feat: add support for quoting invalid TypeScript identifiers in type definitions --- .../mongodb-schema/src/to-typescript.spec.ts | 36 +++++++++++++++++++ packages/mongodb-schema/src/to-typescript.ts | 16 ++++++--- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/packages/mongodb-schema/src/to-typescript.spec.ts b/packages/mongodb-schema/src/to-typescript.spec.ts index 6e38a2da..8b091d61 100644 --- a/packages/mongodb-schema/src/to-typescript.spec.ts +++ b/packages/mongodb-schema/src/to-typescript.spec.ts @@ -291,6 +291,42 @@ describe('toTypescriptTypeDefinition', function () { }; }; b?: any[]; +}`, + ); + }); + + it('quotes field names that are not valid identifiers', async function () { + const docs = [ + { + // Field names the analyzer already supports elsewhere, see + // test/nested-document-path.test.ts and test/basic.test.ts. + 'bar.with.dot': 'foo', + 'name[]': 'Annabeth Frankie', + 'has space': 1, + '2leading': 'foo', + 'quote"inside': 'foo', + valid_one: 'foo', + nested: { + 'a.b': [1, 2, 3], + }, + }, + ]; + + const analyzedDocuments = await analyzeDocuments(docs); + const schema = await analyzedDocuments.getMongoDBJsonSchema(); + + convertAndCompare( + schema, + `{ + "2leading"?: string; + "bar.with.dot"?: string; + "has space"?: bson.Double | number; + "name[]"?: string; + nested?: { + "a.b"?: (bson.Double | number)[]; + }; + "quote\\"inside"?: string; + valid_one?: string; }`, ); }); diff --git a/packages/mongodb-schema/src/to-typescript.ts b/packages/mongodb-schema/src/to-typescript.ts index db6e6468..f28c3532 100644 --- a/packages/mongodb-schema/src/to-typescript.ts +++ b/packages/mongodb-schema/src/to-typescript.ts @@ -108,6 +108,10 @@ function arrayType(types: string[]) { return `(${types.join(' | ')})[]`; } +function propertyKey(name: string): string { + return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name); +} + function toTypescriptType( properties: Record, indent: number, @@ -117,20 +121,22 @@ function toTypescriptType( switch (getBSONType(schema)) { case 'array': assertIsDefined(schema.items, 'schema.items must be defined'); - return `${indentSpaces(indent)}${propertyName}?: ${arrayType([ - ...uniqueTypes(schema.items), - ])}`; + return `${indentSpaces(indent)}${propertyKey( + propertyName, + )}?: ${arrayType([...uniqueTypes(schema.items)])}`; case 'object': assertIsDefined( schema.properties, 'schema.properties must be defined', ); - return `${indentSpaces(indent)}${propertyName}?: ${toTypescriptType( + return `${indentSpaces(indent)}${propertyKey( + propertyName, + )}?: ${toTypescriptType( schema.properties as Record, indent + 1, )}`; default: - return `${indentSpaces(indent)}${propertyName}?: ${[ + return `${indentSpaces(indent)}${propertyKey(propertyName)}?: ${[ ...uniqueTypes(schema), ].join(' | ')}`; } From fbda90fd104ffa79dc31742ed4308a584ec7dcc9 Mon Sep 17 00:00:00 2001 From: Eduarda Scharnhorst Date: Wed, 29 Jul 2026 15:50:44 +0100 Subject: [PATCH 8/9] update package lock after main merge --- package-lock.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index e3ee72d0..132f46b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29690,7 +29690,7 @@ "version": "0.9.1", "license": "Apache-2.0", "dependencies": { - "@mongodb-js/mongodb-schema": "^0.2.0", + "@mongodb-js/mongodb-schema": "^12.7.1", "@mongodb-js/ts-autocomplete": "^0.5.13", "@mongosh/shell-api": "^5.1.6", "debug": "^4.4.0", @@ -30084,7 +30084,7 @@ }, "devDependencies": { "@mongodb-js/eslint-config-devtools": "^0.11.8", - "@mongodb-js/mongodb-schema": "^0.2.0", + "@mongodb-js/mongodb-schema": "^12.7.1", "@mongodb-js/prettier-config-devtools": "^1.0.4", "@mongodb-js/shell-bson-parser": "^1.5.14", "@mongodb-js/tsconfig-devtools": "^1.1.3", @@ -37312,8 +37312,8 @@ "requires": { "@mongodb-js/eslint-config-devtools": "^0.11.8", "@mongodb-js/mocha-config-devtools": "^1.1.3", - "@mongodb-js/mongodb-schema": "^0.2.0", - "@mongodb-js/mql-typescript": "^0.7.0", + "@mongodb-js/mongodb-schema": "^12.7.1", + "@mongodb-js/mql-typescript": "^0.7.1", "@mongodb-js/prettier-config-devtools": "^1.0.4", "@mongodb-js/ts-autocomplete": "^0.5.13", "@mongodb-js/tsconfig-devtools": "^1.1.3", @@ -37568,7 +37568,7 @@ "version": "file:packages/mql-typescript", "requires": { "@mongodb-js/eslint-config-devtools": "^0.11.8", - "@mongodb-js/mongodb-schema": "^0.2.0", + "@mongodb-js/mongodb-schema": "^12.7.1", "@mongodb-js/prettier-config-devtools": "^1.0.4", "@mongodb-js/shell-bson-parser": "^1.5.14", "@mongodb-js/tsconfig-devtools": "^1.1.3", From d0e2e05ceeda6f08b1c9f5d632a941381b4418ad Mon Sep 17 00:00:00 2001 From: Eduarda Scharnhorst Date: Wed, 29 Jul 2026 16:03:10 +0100 Subject: [PATCH 9/9] test: increase timeout for MongoDB cluster tests to 30 seconds --- .../test/integration/generate-and-validate.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/mongodb-schema/test/integration/generate-and-validate.test.ts b/packages/mongodb-schema/test/integration/generate-and-validate.test.ts index 2d71a6ca..f2b8ea99 100644 --- a/packages/mongodb-schema/test/integration/generate-and-validate.test.ts +++ b/packages/mongodb-schema/test/integration/generate-and-validate.test.ts @@ -54,6 +54,8 @@ describe('Documents -> Generate schema -> Validate Documents against the schema' }); describe('With a MongoDB Cluster', function () { + this.timeout(30_000); + let client: MongoClient; let db: Db; const cluster = mochaTestServer();