From 30d8ee2e022f679c16a22a6e60df7414d37316ba Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Mon, 16 Feb 2026 20:16:53 +0100 Subject: [PATCH 01/25] =?UTF-8?q?refactor:=20rename=20mongo=E2=86=92data-a?= =?UTF-8?q?pi,=20MongoBSONTypes=E2=86=92BSONTypes=20+=20fix=20array=20stat?= =?UTF-8?q?s=20bugs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Group A of SchemaAnalyzer refactor: - Fix A1: array element stats overwrite bug (isNewTypeEntry) - Fix A2: probability >100% for array-embedded objects (x-documentsInspected) - Rename folder: src/utils/json/mongo/ → src/utils/json/data-api/ - Rename enum: MongoBSONTypes → BSONTypes - Rename file: MongoValueFormatters → ValueFormatters - Add 9 new tests for array stats and probability --- .gitignore | 3 + src/documentdb/ClusterSession.ts | 2 +- src/utils/json/data-api/BSONTypes.ts | 200 ++++++++ .../SchemaAnalyzer.arrayStats.test.ts | 459 ++++++++++++++++++ .../SchemaAnalyzer.test.ts | 0 .../{mongo => data-api}/SchemaAnalyzer.ts | 130 ++--- .../ValueFormatters.ts} | 52 +- .../basicMongoFindFilterSchema.json | 0 .../generateMongoFindJsonSchema.ts | 0 .../autocomplete/getKnownFields.ts | 0 .../{mongo => data-api}/mongoTestDocuments.ts | 0 src/utils/json/mongo/MongoBSONTypes.ts | 200 -------- src/utils/slickgrid/mongo/toSlickGridTable.ts | 12 +- src/utils/slickgrid/mongo/toSlickGridTree.ts | 12 +- .../collectionView/collectionViewRouter.ts | 6 +- .../components/queryEditor/QueryEditor.tsx | 2 +- 16 files changed, 775 insertions(+), 303 deletions(-) create mode 100644 src/utils/json/data-api/BSONTypes.ts create mode 100644 src/utils/json/data-api/SchemaAnalyzer.arrayStats.test.ts rename src/utils/json/{mongo => data-api}/SchemaAnalyzer.test.ts (100%) rename src/utils/json/{mongo => data-api}/SchemaAnalyzer.ts (85%) rename src/utils/json/{mongo/MongoValueFormatters.ts => data-api/ValueFormatters.ts} (63%) rename src/utils/json/{mongo => data-api}/autocomplete/basicMongoFindFilterSchema.json (100%) rename src/utils/json/{mongo => data-api}/autocomplete/generateMongoFindJsonSchema.ts (100%) rename src/utils/json/{mongo => data-api}/autocomplete/getKnownFields.ts (100%) rename src/utils/json/{mongo => data-api}/mongoTestDocuments.ts (100%) delete mode 100644 src/utils/json/mongo/MongoBSONTypes.ts diff --git a/.gitignore b/.gitignore index 7bc4f395e..ea5798951 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. +/docs/analysis/ +/docs/plan/ + # User-specific files *.suo *.user diff --git a/src/documentdb/ClusterSession.ts b/src/documentdb/ClusterSession.ts index da81218fe..b4ac8f3b1 100644 --- a/src/documentdb/ClusterSession.ts +++ b/src/documentdb/ClusterSession.ts @@ -7,7 +7,7 @@ import * as l10n from '@vscode/l10n'; import { EJSON } from 'bson'; import { ObjectId, type Document, type Filter, type WithId } from 'mongodb'; import { type JSONSchema } from '../utils/json/JSONSchema'; -import { getPropertyNamesAtLevel, updateSchemaWithDocument } from '../utils/json/mongo/SchemaAnalyzer'; +import { getPropertyNamesAtLevel, updateSchemaWithDocument } from '../utils/json/data-api/SchemaAnalyzer'; import { getDataAtPath } from '../utils/slickgrid/mongo/toSlickGridTable'; import { toSlickGridTree, type TreeData } from '../utils/slickgrid/mongo/toSlickGridTree'; import { ClustersClient, type FindQueryParams } from './ClustersClient'; diff --git a/src/utils/json/data-api/BSONTypes.ts b/src/utils/json/data-api/BSONTypes.ts new file mode 100644 index 000000000..ef52911e7 --- /dev/null +++ b/src/utils/json/data-api/BSONTypes.ts @@ -0,0 +1,200 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { + Binary, + BSONSymbol, + Code, + DBRef, + Decimal128, + Double, + Int32, + Long, + MaxKey, + MinKey, + ObjectId, + Timestamp, + UUID, +} from 'mongodb'; + +/** + * Represents the different data types that can be stored in a MongoDB document. + * The string representation is casesensitive and should match the MongoDB documentation. + * https://www.mongodb.com/docs/manual/reference/bson-types/ + */ +export enum BSONTypes { + String = 'string', + Number = 'number', + Int32 = 'int32', + Double = 'double', + Decimal128 = 'decimal128', + Long = 'long', + Boolean = 'boolean', + Object = 'object', + Array = 'array', + Null = 'null', + Undefined = 'undefined', + Date = 'date', + RegExp = 'regexp', + Binary = 'binary', + ObjectId = 'objectid', + Symbol = 'symbol', + Timestamp = 'timestamp', + UUID = 'uuid', + UUID_LEGACY = 'uuid-legacy', // old UUID subtype, used in some legacy data + MinKey = 'minkey', + MaxKey = 'maxkey', + DBRef = 'dbref', + Code = 'code', + CodeWithScope = 'codewithscope', + Map = 'map', + // Add any deprecated types if necessary + _UNKNOWN_ = '_unknown_', // Catch-all for unknown types +} + +export namespace BSONTypes { + const displayStringMap: Record = { + [BSONTypes.String]: 'String', + [BSONTypes.Number]: 'Number', + [BSONTypes.Int32]: 'Int32', + [BSONTypes.Double]: 'Double', + [BSONTypes.Decimal128]: 'Decimal128', + [BSONTypes.Long]: 'Long', + [BSONTypes.Boolean]: 'Boolean', + [BSONTypes.Object]: 'Object', + [BSONTypes.Array]: 'Array', + [BSONTypes.Null]: 'Null', + [BSONTypes.Undefined]: 'Undefined', + [BSONTypes.Date]: 'Date', + [BSONTypes.RegExp]: 'RegExp', + [BSONTypes.Binary]: 'Binary', + [BSONTypes.ObjectId]: 'ObjectId', + [BSONTypes.Symbol]: 'Symbol', + [BSONTypes.Timestamp]: 'Timestamp', + [BSONTypes.MinKey]: 'MinKey', + [BSONTypes.MaxKey]: 'MaxKey', + [BSONTypes.DBRef]: 'DBRef', + [BSONTypes.Code]: 'Code', + [BSONTypes.CodeWithScope]: 'CodeWithScope', + [BSONTypes.Map]: 'Map', + [BSONTypes._UNKNOWN_]: 'Unknown', + [BSONTypes.UUID]: 'UUID', + [BSONTypes.UUID_LEGACY]: 'UUID (Legacy)', + }; + + export function toDisplayString(type: BSONTypes): string { + return displayStringMap[type] || 'Unknown'; + } + + export function toString(type: BSONTypes): string { + return type; + } + + /** + * Converts a MongoDB data type to a case sensitive JSON data type + * @param type The MongoDB data type + * @returns A corresponding JSON data type (please note: it's case sensitive) + */ + export function toJSONType(type: BSONTypes): string { + switch (type) { + case BSONTypes.String: + case BSONTypes.Symbol: + case BSONTypes.Date: + case BSONTypes.Timestamp: + case BSONTypes.ObjectId: + case BSONTypes.RegExp: + case BSONTypes.Binary: + case BSONTypes.Code: + case BSONTypes.UUID: + case BSONTypes.UUID_LEGACY: + return 'string'; + + case BSONTypes.Boolean: + return 'boolean'; + + case BSONTypes.Int32: + case BSONTypes.Long: + case BSONTypes.Double: + case BSONTypes.Decimal128: + return 'number'; + + case BSONTypes.Object: + case BSONTypes.Map: + case BSONTypes.DBRef: + case BSONTypes.CodeWithScope: + return 'object'; + + case BSONTypes.Array: + return 'array'; + + case BSONTypes.Null: + case BSONTypes.Undefined: + case BSONTypes.MinKey: + case BSONTypes.MaxKey: + return 'null'; + + default: + return 'string'; // Default to string for unknown types + } + } + + /** + * Accepts a value from a MongoDB 'Document' object and returns the inferred type. + * @param value The value of a field in a MongoDB 'Document' object + * @returns + */ + export function inferType(value: unknown): BSONTypes { + if (value === null) return BSONTypes.Null; + if (value === undefined) return BSONTypes.Undefined; + + switch (typeof value) { + case 'string': + return BSONTypes.String; + case 'number': + return BSONTypes.Double; // JavaScript numbers are doubles + case 'boolean': + return BSONTypes.Boolean; + case 'object': + if (Array.isArray(value)) { + return BSONTypes.Array; + } + + // Check for common BSON types first + if (value instanceof ObjectId) return BSONTypes.ObjectId; + if (value instanceof Int32) return BSONTypes.Int32; + if (value instanceof Double) return BSONTypes.Double; + if (value instanceof Date) return BSONTypes.Date; + if (value instanceof Timestamp) return BSONTypes.Timestamp; + + // Less common types + if (value instanceof Decimal128) return BSONTypes.Decimal128; + if (value instanceof Long) return BSONTypes.Long; + if (value instanceof MinKey) return BSONTypes.MinKey; + if (value instanceof MaxKey) return BSONTypes.MaxKey; + if (value instanceof BSONSymbol) return BSONTypes.Symbol; + if (value instanceof DBRef) return BSONTypes.DBRef; + if (value instanceof Map) return BSONTypes.Map; + if (value instanceof UUID && value.sub_type === Binary.SUBTYPE_UUID) return BSONTypes.UUID; + if (value instanceof UUID && value.sub_type === Binary.SUBTYPE_UUID_OLD) + return BSONTypes.UUID_LEGACY; + if (value instanceof Buffer || value instanceof Binary) return BSONTypes.Binary; + if (value instanceof RegExp) return BSONTypes.RegExp; + if (value instanceof Code) { + if (value.scope) { + return BSONTypes.CodeWithScope; + } else { + return BSONTypes.Code; + } + } + + // Default to Object if none of the above match + return BSONTypes.Object; + default: + // This should never happen, but if it does, we'll catch it here + // TODO: add telemetry somewhere to know when it happens (not here, this could get hit too often) + return BSONTypes._UNKNOWN_; + } + } +} diff --git a/src/utils/json/data-api/SchemaAnalyzer.arrayStats.test.ts b/src/utils/json/data-api/SchemaAnalyzer.arrayStats.test.ts new file mode 100644 index 000000000..6933315da --- /dev/null +++ b/src/utils/json/data-api/SchemaAnalyzer.arrayStats.test.ts @@ -0,0 +1,459 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ObjectId, type Document, type WithId } from 'mongodb'; +import { type JSONSchema } from '../JSONSchema'; +import { updateSchemaWithDocument } from './SchemaAnalyzer'; + +/** + * This test file investigates the array element occurrence/stats problem. + * + * The core issue: When an array contains mixed types (e.g., strings AND objects), + * `x-typeOccurrence` on the items' type entries counts individual elements across + * ALL documents, not occurrences-per-document. This makes "field presence probability" + * for nested object properties inside arrays hard to interpret. + * + * Example scenario: + * doc1.data = ["a", "b", "c", {"value": 23}] → 3 strings, 1 object + * doc2.data = ["x", "y", {"value": 42, "flag": true}] → 2 strings, 1 object + * doc3.data = ["z"] → 1 string, 0 objects + * + * After processing 3 docs: + * - items.anyOf[string].x-typeOccurrence = 6 (total string elements across all docs) + * - items.anyOf[object].x-typeOccurrence = 2 (total object elements across all docs) + * - items.anyOf[object].properties.value.x-occurrence = 2 (from 2 object elements) + * - items.anyOf[object].properties.flag.x-occurrence = 1 (from 1 object element) + * + * The problem: what is items.anyOf[object].properties.value's "probability"? + * - 2/2? (present in every object element → makes sense) + * - 2/3? (present in 2 of 3 documents → misleading, doc3 has no objects at all) + * - 2/6? (present in 2 of 6 total elements → nonsensical, mixes types) + * + * There's no x-documentsInspected equivalent at the array level to anchor + * the occurrence count. + */ +describe('Array element occurrence analysis', () => { + it('counts element types across multiple documents', () => { + const schema: JSONSchema = {}; + + const doc1: WithId = { + _id: new ObjectId(), + data: ['a', 'b', 'c', { value: 23 }], + }; + const doc2: WithId = { + _id: new ObjectId(), + data: ['x', 'y', { value: 42, flag: true }], + }; + const doc3: WithId = { + _id: new ObjectId(), + data: ['z'], + }; + + updateSchemaWithDocument(schema, doc1); + updateSchemaWithDocument(schema, doc2); + updateSchemaWithDocument(schema, doc3); + + // data field: array seen in 3 docs + const dataField = schema.properties?.['data'] as JSONSchema; + expect(dataField['x-occurrence']).toBe(3); + + // The array type entry + const arrayTypeEntry = dataField.anyOf?.find((e) => (e as JSONSchema)['x-bsonType'] === 'array') as JSONSchema; + expect(arrayTypeEntry).toBeDefined(); + expect(arrayTypeEntry['x-typeOccurrence']).toBe(3); + + // Array items + const itemsSchema = arrayTypeEntry.items as JSONSchema; + const stringEntry = itemsSchema.anyOf?.find((e) => (e as JSONSchema)['x-bsonType'] === 'string') as JSONSchema; + const objectEntry = itemsSchema.anyOf?.find((e) => (e as JSONSchema)['x-bsonType'] === 'object') as JSONSchema; + + // String elements: "a","b","c","x","y","z" = 6 total + expect(stringEntry['x-typeOccurrence']).toBe(6); + + // Object elements: {value:23}, {value:42,flag:true} = 2 total + expect(objectEntry['x-typeOccurrence']).toBe(2); + + // Properties inside the object elements + const valueField = objectEntry.properties?.['value'] as JSONSchema; + const flagField = objectEntry.properties?.['flag'] as JSONSchema; + + // "value" appeared in both objects → x-occurrence = 2 + expect(valueField['x-occurrence']).toBe(2); + + // "flag" appeared in 1 object → x-occurrence = 1 + expect(flagField['x-occurrence']).toBe(1); + + // THE CORE QUESTION: What is the denominator for probability? + // + // We know objectEntry['x-typeOccurrence'] = 2 (2 objects total across all arrays). + // So valueField probability = 2/2 = 100% (correct: every object had "value") + // And flagField probability = 1/2 = 50% (correct: half of objects had "flag") + // + // BUT: there is NO x-documentsInspected on objectEntry to formally define + // the denominator. The consumer has to know to use x-typeOccurrence as the + // denominator for nested properties inside array elements. + // + // This actually WORKS — the semantics are: + // "of the N objects observed inside this array, M had this property" + // + // It just isn't obvious from the schema structure. + }); + + it('tracks min/max array lengths across documents', () => { + const schema: JSONSchema = {}; + + const doc1: WithId = { + _id: new ObjectId(), + tags: ['a', 'b', 'c'], + }; + const doc2: WithId = { + _id: new ObjectId(), + tags: ['x'], + }; + const doc3: WithId = { + _id: new ObjectId(), + tags: ['p', 'q', 'r', 's', 't'], + }; + + updateSchemaWithDocument(schema, doc1); + updateSchemaWithDocument(schema, doc2); + updateSchemaWithDocument(schema, doc3); + + const tagsField = schema.properties?.['tags'] as JSONSchema; + const arrayEntry = tagsField.anyOf?.find((e) => (e as JSONSchema)['x-bsonType'] === 'array') as JSONSchema; + + expect(arrayEntry['x-minItems']).toBe(1); + expect(arrayEntry['x-maxItems']).toBe(5); + }); + + it('accumulates nested object properties from objects inside arrays across documents', () => { + const schema: JSONSchema = {}; + + // doc1 has two objects with different properties in the items array + const doc1: WithId = { + _id: new ObjectId(), + items: [ + { name: 'Laptop', price: 999 }, + { name: 'Mouse', price: 29, discount: true }, + ], + }; + + // doc2 has one object with yet another property + const doc2: WithId = { + _id: new ObjectId(), + items: [{ name: 'Desk', weight: 50 }], + }; + + updateSchemaWithDocument(schema, doc1); + updateSchemaWithDocument(schema, doc2); + + const itemsField = schema.properties?.['items'] as JSONSchema; + const arrayEntry = itemsField.anyOf?.find((e) => (e as JSONSchema)['x-bsonType'] === 'array') as JSONSchema; + const objEntry = (arrayEntry.items as JSONSchema).anyOf?.find( + (e) => (e as JSONSchema)['x-bsonType'] === 'object', + ) as JSONSchema; + + const props = objEntry.properties as JSONSchema; + + // "name" appeared in all 3 object elements + expect((props['name'] as JSONSchema)['x-occurrence']).toBe(3); + + // "price" appeared in 2 of 3 object elements + expect((props['price'] as JSONSchema)['x-occurrence']).toBe(2); + + // "discount" appeared in 1 of 3 object elements + expect((props['discount'] as JSONSchema)['x-occurrence']).toBe(1); + + // "weight" appeared in 1 of 3 object elements + expect((props['weight'] as JSONSchema)['x-occurrence']).toBe(1); + + // Total object elements = 3 (2 from doc1 + 1 from doc2) + expect(objEntry['x-typeOccurrence']).toBe(3); + + // So probability interpretations: + // name: 3/3 = 100% + // price: 2/3 = 67% + // discount: 1/3 = 33% + // weight: 1/3 = 33% + // + // This is correct! x-typeOccurrence serves as the denominator. + }); + + it('handles arrays that ONLY contain primitives (no occurrence complexity)', () => { + const schema: JSONSchema = {}; + + const doc1: WithId = { + _id: new ObjectId(), + scores: [90, 85, 78], + }; + const doc2: WithId = { + _id: new ObjectId(), + scores: [100, 55], + }; + + updateSchemaWithDocument(schema, doc1); + updateSchemaWithDocument(schema, doc2); + + const scoresField = schema.properties?.['scores'] as JSONSchema; + const arrayEntry = scoresField.anyOf?.find((e) => (e as JSONSchema)['x-bsonType'] === 'array') as JSONSchema; + + const numEntry = (arrayEntry.items as JSONSchema).anyOf?.find( + (e) => (e as JSONSchema)['x-bsonType'] === 'double', + ) as JSONSchema; + + // 5 total numeric elements + expect(numEntry['x-typeOccurrence']).toBe(5); + + // Stats across all elements + expect(numEntry['x-minValue']).toBe(55); + expect(numEntry['x-maxValue']).toBe(100); + + // Array length stats + expect(arrayEntry['x-minItems']).toBe(2); + expect(arrayEntry['x-maxItems']).toBe(3); + }); + + it('verifies that encounteredMongoTypes map is per-document', () => { + // The encounteredMongoTypes map is created inside the Array case handler. + // It controls whether initializeStatsForValue or aggregateStatsForValue is called. + // If it's per-array-occurrence (per document), stats should initialize fresh for each doc. + // + // BUT WAIT: The map is local to the switch case, which processes ONE array per queue item. + // Multiple documents contribute different queue items, and the map is re-created for each. + // However, the stats update goes to the SAME itemEntry across documents (because + // findTypeEntry finds the existing entry). So: + // + // doc1.scores = [10, 20] → first array processing, encounteredMongoTypes fresh + // - element 10: initializeStatsForValue (sets x-minValue=10, x-maxValue=10) + // - element 20: aggregateStatsForValue (updates x-maxValue=20) + // + // doc2.scores = [5, 30] → second array processing, encounteredMongoTypes fresh + // - element 5: initializeStatsForValue ← BUT x-minValue is already 10 from doc1! + // initializeStatsForValue OVERWRITES x-minValue to 5 (correct by accident here) + // Actually let's check... initializeStatsForValue sets x-maxValue = 5 + // and x-minValue = 5. So the 20 from doc1 would be lost! + // + // This is a REAL BUG: initializeStatsForValue is called for the first occurrence + // per array, but the typeEntry already has stats from previous arrays. + + const schema: JSONSchema = {}; + + const doc1: WithId = { + _id: new ObjectId(), + scores: [10, 20, 30], + }; + const doc2: WithId = { + _id: new ObjectId(), + scores: [5, 15], + }; + + updateSchemaWithDocument(schema, doc1); + updateSchemaWithDocument(schema, doc2); + + const scoresField = schema.properties?.['scores'] as JSONSchema; + const arrayEntry = scoresField.anyOf?.find((e) => (e as JSONSchema)['x-bsonType'] === 'array') as JSONSchema; + + const numEntry = (arrayEntry.items as JSONSchema).anyOf?.find( + (e) => (e as JSONSchema)['x-bsonType'] === 'double', + ) as JSONSchema; + + // Expected correct values: + // All 5 elements: 10, 20, 30, 5, 15 + // Global min = 5, global max = 30 + + // If there's a bug, doc2 processing re-initializes: + // after doc1: min=10, max=30 + // doc2 first element (5): initializeStatsForValue → sets min=5, max=5 + // doc2 second element (15): aggregateStatsForValue → max becomes 15 + // final: min=5, max=15 ← WRONG (lost 30 from doc1) + + // Let's check what actually happens: + console.log('numEntry x-minValue:', numEntry['x-minValue']); + console.log('numEntry x-maxValue:', numEntry['x-maxValue']); + + // This test documents the actual behavior (might be buggy): + expect(numEntry['x-minValue']).toBe(5); + // If the bug exists, this will be 15 instead of 30: + expect(numEntry['x-maxValue']).toBe(30); // should be 30 if correct + }); +}); + +describe('Array probability denominator problem', () => { + it('reproduces the >100% probability bug: empty array + large array', () => { + // User scenario: + // doc1: a = [] → 0 objects + // doc2: a = [{b:1}, {b:2}, ..., {b:100}] → 100 objects + // + // Naively computing probability as: + // occurrence_of_b / root.x-documentsInspected = 100 / 2 = 5000% + // + // The correct probability should be: + // occurrence_of_b / objectEntry.x-typeOccurrence = 100 / 100 = 100% + // + // FIX: Set x-documentsInspected on the object type entry so the uniform + // formula `x-occurrence / parent.x-documentsInspected` works at every + // nesting level. + + const schema: JSONSchema = {}; + + const doc1: WithId = { + _id: new ObjectId(), + a: [], // empty array + }; + + // doc2: 100 objects, each with property "b" + const objectElements: Record[] = []; + for (let i = 1; i <= 100; i++) { + objectElements.push({ b: i }); + } + const doc2: WithId = { + _id: new ObjectId(), + a: objectElements, + }; + + updateSchemaWithDocument(schema, doc1); + updateSchemaWithDocument(schema, doc2); + + // Root level + expect(schema['x-documentsInspected']).toBe(2); + + // Navigate to the object type entry inside the array + const aField = schema.properties?.['a'] as JSONSchema; + expect(aField['x-occurrence']).toBe(2); // both docs have 'a' + + const arrayEntry = aField.anyOf?.find((e) => (e as JSONSchema)['x-bsonType'] === 'array') as JSONSchema; + const objectEntry = (arrayEntry.items as JSONSchema).anyOf?.find( + (e) => (e as JSONSchema)['x-bsonType'] === 'object', + ) as JSONSchema; + + // 100 object elements total + expect(objectEntry['x-typeOccurrence']).toBe(100); + + // Property "b" appears in all 100 objects + const bField = objectEntry.properties?.['b'] as JSONSchema; + expect(bField['x-occurrence']).toBe(100); + + // THE FIX: objectEntry should have x-documentsInspected = 100 + // so that the uniform formula works: + // probability = b.x-occurrence / objectEntry.x-documentsInspected + // = 100 / 100 = 100% + expect(objectEntry['x-documentsInspected']).toBe(100); + }); + + it('correctly computes probability for sparse properties in array objects', () => { + // doc1: items = [{name:"A", price:10}, {name:"B"}] → 2 objects, name in both, price in 1 + // doc2: items = [{name:"C", discount:true}] → 1 object + // + // Total objects = 3 + // name: 3/3 = 100% + // price: 1/3 = 33% + // discount: 1/3 = 33% + + const schema: JSONSchema = {}; + + const doc1: WithId = { + _id: new ObjectId(), + items: [{ name: 'A', price: 10 }, { name: 'B' }], + }; + const doc2: WithId = { + _id: new ObjectId(), + items: [{ name: 'C', discount: true }], + }; + + updateSchemaWithDocument(schema, doc1); + updateSchemaWithDocument(schema, doc2); + + const itemsField = schema.properties?.['items'] as JSONSchema; + const arrayEntry = itemsField.anyOf?.find((e) => (e as JSONSchema)['x-bsonType'] === 'array') as JSONSchema; + const objectEntry = (arrayEntry.items as JSONSchema).anyOf?.find( + (e) => (e as JSONSchema)['x-bsonType'] === 'object', + ) as JSONSchema; + + // The object type entry should have x-documentsInspected = 3 + expect(objectEntry['x-documentsInspected']).toBe(3); + + const props = objectEntry.properties as Record; + + // Probability = x-occurrence / x-documentsInspected (uniform formula) + expect(props['name']['x-occurrence']).toBe(3); // 3/3 = 100% + expect(props['price']['x-occurrence']).toBe(1); // 1/3 = 33% + expect(props['discount']['x-occurrence']).toBe(1); // 1/3 = 33% + }); + + it('sets x-documentsInspected on nested objects at all levels', () => { + // items: [{address: {city: "NY", zip: "10001"}}, {address: {city: "LA"}}] + // + // At items.anyOf[object] level: x-documentsInspected = 2 + // At address.anyOf[object] level: x-documentsInspected = 2 + // city: 2/2 = 100%, zip: 1/2 = 50% + + const schema: JSONSchema = {}; + + const doc: WithId = { + _id: new ObjectId(), + items: [{ address: { city: 'NY', zip: '10001' } }, { address: { city: 'LA' } }], + }; + + updateSchemaWithDocument(schema, doc); + + const itemsField = schema.properties?.['items'] as JSONSchema; + const arrayEntry = itemsField.anyOf?.find((e) => (e as JSONSchema)['x-bsonType'] === 'array') as JSONSchema; + const objectEntry = (arrayEntry.items as JSONSchema).anyOf?.find( + (e) => (e as JSONSchema)['x-bsonType'] === 'object', + ) as JSONSchema; + + // 2 objects in the array + expect(objectEntry['x-documentsInspected']).toBe(2); + + // address.anyOf[object] — the nested object type + const addressProp = objectEntry.properties?.['address'] as JSONSchema; + const addressObjEntry = addressProp.anyOf?.find( + (e) => (e as JSONSchema)['x-bsonType'] === 'object', + ) as JSONSchema; + + // Both objects had address, and both addresses were objects + expect(addressObjEntry['x-documentsInspected']).toBe(2); + + const addrProps = addressObjEntry.properties as Record; + expect(addrProps['city']['x-occurrence']).toBe(2); // 2/2 = 100% + expect(addrProps['zip']['x-occurrence']).toBe(1); // 1/2 = 50% + }); + + it('does NOT change x-documentsInspected at root level (root keeps document count)', () => { + const schema: JSONSchema = {}; + + const doc1: WithId = { + _id: new ObjectId(), + name: 'Alice', + address: { city: 'NY' }, + }; + const doc2: WithId = { + _id: new ObjectId(), + name: 'Bob', + address: { city: 'LA', zip: '90001' }, + }; + + updateSchemaWithDocument(schema, doc1); + updateSchemaWithDocument(schema, doc2); + + // Root x-documentsInspected is document count, not affected by the fix + expect(schema['x-documentsInspected']).toBe(2); + + // Root-level probability still works: name.occurrence(2) / documentsInspected(2) = 100% + const nameField = schema.properties?.['name'] as JSONSchema; + expect(nameField['x-occurrence']).toBe(2); + + // Nested object: address.anyOf[object] should have x-documentsInspected = 2 + const addressField = schema.properties?.['address'] as JSONSchema; + const addressObjEntry = addressField.anyOf?.find( + (e) => (e as JSONSchema)['x-bsonType'] === 'object', + ) as JSONSchema; + expect(addressObjEntry['x-documentsInspected']).toBe(2); + + const addrProps = addressObjEntry.properties as Record; + expect(addrProps['city']['x-occurrence']).toBe(2); // 2/2 = 100% + expect(addrProps['zip']['x-occurrence']).toBe(1); // 1/2 = 50% + }); +}); diff --git a/src/utils/json/mongo/SchemaAnalyzer.test.ts b/src/utils/json/data-api/SchemaAnalyzer.test.ts similarity index 100% rename from src/utils/json/mongo/SchemaAnalyzer.test.ts rename to src/utils/json/data-api/SchemaAnalyzer.test.ts diff --git a/src/utils/json/mongo/SchemaAnalyzer.ts b/src/utils/json/data-api/SchemaAnalyzer.ts similarity index 85% rename from src/utils/json/mongo/SchemaAnalyzer.ts rename to src/utils/json/data-api/SchemaAnalyzer.ts index 278f51fc4..bc97dff49 100644 --- a/src/utils/json/mongo/SchemaAnalyzer.ts +++ b/src/utils/json/data-api/SchemaAnalyzer.ts @@ -60,7 +60,7 @@ import { assert } from 'console'; import Denque from 'denque'; import { type Document, type WithId } from 'mongodb'; import { type JSONSchema } from '../JSONSchema'; -import { MongoBSONTypes } from './MongoBSONTypes'; +import { BSONTypes } from './BSONTypes'; export function updateSchemaWithDocument(schema: JSONSchema, document: WithId): void { // Initialize schema if it's empty @@ -74,7 +74,7 @@ export function updateSchemaWithDocument(schema: JSONSchema, document: WithId; const objKeysCount = Object.keys(objValue).length; // Update min and max property counts updateMinMaxStats(item.propertySchema, 'x-minProperties', 'x-maxProperties', objKeysCount); + // Track how many object instances contributed to this sub-schema. + // This enables uniform probability computation at every nesting level: + // probability = property.x-occurrence / parentObject.x-documentsInspected + // + // Without this, array-embedded objects have no denominator for probability. + // Example: doc1.a=[], doc2.a=[{b:1},...,{b:100}] + // b.x-occurrence = 100, root.x-documentsInspected = 2 + // Naive: 100/2 = 5000% — wrong! + // With fix: objectEntry.x-documentsInspected = 100, so 100/100 = 100% + item.propertySchema['x-documentsInspected'] = (item.propertySchema['x-documentsInspected'] ?? 0) + 1; + // Ensure 'properties' exists if (!item.propertySchema.properties) { item.propertySchema.properties = {}; @@ -158,7 +169,7 @@ export function updateSchemaWithDocument(schema: JSONSchema, document: WithId = new Map(); - // Iterate over the array elements for (const element of arrayValue) { - const elementMongoType = MongoBSONTypes.inferType(element); + const elementMongoType = BSONTypes.inferType(element); // Find or create the type entry in 'items.anyOf' let itemEntry = findTypeEntry(itemsSchema.anyOf as JSONSchema[], elementMongoType); + const isNewTypeEntry = !itemEntry; if (!itemEntry) { // Create a new type entry itemEntry = { - type: MongoBSONTypes.toJSONType(elementMongoType), + type: BSONTypes.toJSONType(elementMongoType), 'x-bsonType': elementMongoType, 'x-typeOccurrence': 0, }; @@ -249,18 +258,19 @@ export function updateSchemaWithDocument(schema: JSONSchema, document: WithId entry['x-bsonType'] === bsonType); } @@ -314,7 +324,7 @@ export function getSchemaFromDocument(document: WithId): JSONSchema { type WorkItem = { fieldName: string; - fieldMongoType: MongoBSONTypes; // the inferred BSON type + fieldMongoType: BSONTypes; // the inferred BSON type propertyTypeEntry: JSONSchema; // points to the entry within the 'anyOf' property of the schema fieldValue: unknown; pathSoFar: string; // used for debugging @@ -329,10 +339,10 @@ export function getSchemaFromDocument(document: WithId): JSONSchema { * Push all elements from the root of the document into the queue */ for (const [name, value] of Object.entries(document)) { - const mongoDatatype = MongoBSONTypes.inferType(value); + const mongoDatatype = BSONTypes.inferType(value); const typeEntry = { - type: MongoBSONTypes.toJSONType(mongoDatatype), + type: BSONTypes.toJSONType(mongoDatatype), 'x-bsonType': mongoDatatype, 'x-typeOccurrence': 1, }; @@ -362,7 +372,7 @@ export function getSchemaFromDocument(document: WithId): JSONSchema { } switch (item.fieldMongoType) { - case MongoBSONTypes.Object: { + case BSONTypes.Object: { const objKeys = Object.keys(item.fieldValue as object).length; item.propertyTypeEntry['x-maxLength'] = objKeys; item.propertyTypeEntry['x-minLength'] = objKeys; @@ -371,10 +381,10 @@ export function getSchemaFromDocument(document: WithId): JSONSchema { item.propertyTypeEntry.properties = {}; for (const [name, value] of Object.entries(item.fieldValue as object)) { - const mongoDatatype = MongoBSONTypes.inferType(value); + const mongoDatatype = BSONTypes.inferType(value); const typeEntry = { - type: MongoBSONTypes.toJSONType(mongoDatatype), + type: BSONTypes.toJSONType(mongoDatatype), 'x-bsonType': mongoDatatype, 'x-typeOccurrence': 1, }; @@ -392,7 +402,7 @@ export function getSchemaFromDocument(document: WithId): JSONSchema { } break; } - case MongoBSONTypes.Array: { + case BSONTypes.Array: { const arrayLength = (item.fieldValue as unknown[]).length; item.propertyTypeEntry['x-maxLength'] = arrayLength; item.propertyTypeEntry['x-minLength'] = arrayLength; @@ -401,17 +411,17 @@ export function getSchemaFromDocument(document: WithId): JSONSchema { item.propertyTypeEntry.items = {}; item.propertyTypeEntry.items.anyOf = []; - const encounteredMongoTypes: Map = new Map(); + const encounteredMongoTypes: Map = new Map(); // iterate over the array and infer the type of each element for (const element of item.fieldValue as unknown[]) { - const elementMongoType = MongoBSONTypes.inferType(element); + const elementMongoType = BSONTypes.inferType(element); let itemEntry: JSONSchema; if (!encounteredMongoTypes.has(elementMongoType)) { itemEntry = { - type: MongoBSONTypes.toJSONType(elementMongoType), + type: BSONTypes.toJSONType(elementMongoType), 'x-bsonType': elementMongoType, 'x-typeOccurrence': 1, // Initialize type occurrence counter }; @@ -435,7 +445,7 @@ export function getSchemaFromDocument(document: WithId): JSONSchema { // an imporant exception for arrays as we have to start adding them already now to the schema // (if we want to avoid more iterations over the data) - if (elementMongoType === MongoBSONTypes.Object || elementMongoType === MongoBSONTypes.Array) { + if (elementMongoType === BSONTypes.Object || elementMongoType === BSONTypes.Array) { fifoQueue.push({ fieldName: '[]', // Array items don't have a field name fieldMongoType: elementMongoType, @@ -464,56 +474,56 @@ export function getSchemaFromDocument(document: WithId): JSONSchema { * Helper function to compute stats for a value based on its MongoDB data type * Updates the provided propertyTypeEntry with the computed stats */ -function initializeStatsForValue(value: unknown, mongoType: MongoBSONTypes, propertyTypeEntry: JSONSchema): void { +function initializeStatsForValue(value: unknown, mongoType: BSONTypes, propertyTypeEntry: JSONSchema): void { switch (mongoType) { - case MongoBSONTypes.String: { + case BSONTypes.String: { const currentLength = (value as string).length; propertyTypeEntry['x-maxLength'] = currentLength; propertyTypeEntry['x-minLength'] = currentLength; break; } - case MongoBSONTypes.Number: - case MongoBSONTypes.Int32: - case MongoBSONTypes.Long: - case MongoBSONTypes.Double: - case MongoBSONTypes.Decimal128: { + case BSONTypes.Number: + case BSONTypes.Int32: + case BSONTypes.Long: + case BSONTypes.Double: + case BSONTypes.Decimal128: { const numericValue = Number(value); propertyTypeEntry['x-maxValue'] = numericValue; propertyTypeEntry['x-minValue'] = numericValue; break; } - case MongoBSONTypes.Boolean: { + case BSONTypes.Boolean: { const boolValue = value as boolean; propertyTypeEntry['x-trueCount'] = boolValue ? 1 : 0; propertyTypeEntry['x-falseCount'] = boolValue ? 0 : 1; break; } - case MongoBSONTypes.Date: { + case BSONTypes.Date: { const dateValue = (value as Date).getTime(); propertyTypeEntry['x-maxDate'] = dateValue; propertyTypeEntry['x-minDate'] = dateValue; break; } - case MongoBSONTypes.Binary: { + case BSONTypes.Binary: { const binaryLength = (value as Buffer).length; propertyTypeEntry['x-maxLength'] = binaryLength; propertyTypeEntry['x-minLength'] = binaryLength; break; } - case MongoBSONTypes.Null: - case MongoBSONTypes.RegExp: - case MongoBSONTypes.ObjectId: - case MongoBSONTypes.MinKey: - case MongoBSONTypes.MaxKey: - case MongoBSONTypes.Symbol: - case MongoBSONTypes.Timestamp: - case MongoBSONTypes.DBRef: - case MongoBSONTypes.Map: + case BSONTypes.Null: + case BSONTypes.RegExp: + case BSONTypes.ObjectId: + case BSONTypes.MinKey: + case BSONTypes.MaxKey: + case BSONTypes.Symbol: + case BSONTypes.Timestamp: + case BSONTypes.DBRef: + case BSONTypes.Map: // No stats computation for other types break; @@ -527,9 +537,9 @@ function initializeStatsForValue(value: unknown, mongoType: MongoBSONTypes, prop * Helper function to aggregate stats for a value based on its MongoDB data type * Used when processing multiple values (e.g., elements in arrays) */ -function aggregateStatsForValue(value: unknown, mongoType: MongoBSONTypes, propertyTypeEntry: JSONSchema): void { +function aggregateStatsForValue(value: unknown, mongoType: BSONTypes, propertyTypeEntry: JSONSchema): void { switch (mongoType) { - case MongoBSONTypes.String: { + case BSONTypes.String: { const currentLength = (value as string).length; // Update minLength @@ -544,11 +554,11 @@ function aggregateStatsForValue(value: unknown, mongoType: MongoBSONTypes, prope break; } - case MongoBSONTypes.Number: - case MongoBSONTypes.Int32: - case MongoBSONTypes.Long: - case MongoBSONTypes.Double: - case MongoBSONTypes.Decimal128: { + case BSONTypes.Number: + case BSONTypes.Int32: + case BSONTypes.Long: + case BSONTypes.Double: + case BSONTypes.Decimal128: { const numericValue = Number(value); // Update minValue @@ -563,7 +573,7 @@ function aggregateStatsForValue(value: unknown, mongoType: MongoBSONTypes, prope break; } - case MongoBSONTypes.Boolean: { + case BSONTypes.Boolean: { const boolValue = value as boolean; // Update trueCount and falseCount @@ -581,7 +591,7 @@ function aggregateStatsForValue(value: unknown, mongoType: MongoBSONTypes, prope break; } - case MongoBSONTypes.Date: { + case BSONTypes.Date: { const dateValue = (value as Date).getTime(); // Update minDate @@ -596,7 +606,7 @@ function aggregateStatsForValue(value: unknown, mongoType: MongoBSONTypes, prope break; } - case MongoBSONTypes.Binary: { + case BSONTypes.Binary: { const binaryLength = (value as Buffer).length; // Update minLength diff --git a/src/utils/json/mongo/MongoValueFormatters.ts b/src/utils/json/data-api/ValueFormatters.ts similarity index 63% rename from src/utils/json/mongo/MongoValueFormatters.ts rename to src/utils/json/data-api/ValueFormatters.ts index 243ce2631..8d770e318 100644 --- a/src/utils/json/mongo/MongoValueFormatters.ts +++ b/src/utils/json/data-api/ValueFormatters.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { type Binary, type BSONRegExp, type ObjectId } from 'mongodb'; -import { MongoBSONTypes } from './MongoBSONTypes'; +import { BSONTypes } from './BSONTypes'; /** * Converts a MongoDB value to its display string representation based on its type. @@ -24,60 +24,60 @@ import { MongoBSONTypes } from './MongoBSONTypes'; * * For unsupported or unknown types, the function defaults to JSON stringification. */ -export function valueToDisplayString(value: unknown, type: MongoBSONTypes): string { +export function valueToDisplayString(value: unknown, type: BSONTypes): string { switch (type) { - case MongoBSONTypes.String: { + case BSONTypes.String: { return value as string; } - case MongoBSONTypes.Number: - case MongoBSONTypes.Int32: - case MongoBSONTypes.Double: - case MongoBSONTypes.Decimal128: - case MongoBSONTypes.Long: { + case BSONTypes.Number: + case BSONTypes.Int32: + case BSONTypes.Double: + case BSONTypes.Decimal128: + case BSONTypes.Long: { return (value as number).toString(); } - case MongoBSONTypes.Boolean: { + case BSONTypes.Boolean: { return (value as boolean).toString(); } - case MongoBSONTypes.Date: { + case BSONTypes.Date: { return (value as Date).toISOString(); } - case MongoBSONTypes.ObjectId: { + case BSONTypes.ObjectId: { return (value as ObjectId).toHexString(); } - case MongoBSONTypes.Null: { + case BSONTypes.Null: { return 'null'; } - case MongoBSONTypes.RegExp: { + case BSONTypes.RegExp: { const v = value as BSONRegExp; return `${v.pattern} ${v.options}`; } - case MongoBSONTypes.Binary: { + case BSONTypes.Binary: { return `Binary[${(value as Binary).length()}]`; } - case MongoBSONTypes.Symbol: { + case BSONTypes.Symbol: { return (value as symbol).toString(); } - case MongoBSONTypes.Timestamp: { + case BSONTypes.Timestamp: { return (value as { toString: () => string }).toString(); } - case MongoBSONTypes.MinKey: { + case BSONTypes.MinKey: { return 'MinKey'; } - case MongoBSONTypes.MaxKey: { + case BSONTypes.MaxKey: { return 'MaxKey'; } - case MongoBSONTypes.Code: - case MongoBSONTypes.CodeWithScope: { + case BSONTypes.Code: + case BSONTypes.CodeWithScope: { return JSON.stringify(value); } - case MongoBSONTypes.Array: - case MongoBSONTypes.Object: - case MongoBSONTypes.Map: - case MongoBSONTypes.DBRef: - case MongoBSONTypes.Undefined: - case MongoBSONTypes._UNKNOWN_: + case BSONTypes.Array: + case BSONTypes.Object: + case BSONTypes.Map: + case BSONTypes.DBRef: + case BSONTypes.Undefined: + case BSONTypes._UNKNOWN_: default: { return JSON.stringify(value); } diff --git a/src/utils/json/mongo/autocomplete/basicMongoFindFilterSchema.json b/src/utils/json/data-api/autocomplete/basicMongoFindFilterSchema.json similarity index 100% rename from src/utils/json/mongo/autocomplete/basicMongoFindFilterSchema.json rename to src/utils/json/data-api/autocomplete/basicMongoFindFilterSchema.json diff --git a/src/utils/json/mongo/autocomplete/generateMongoFindJsonSchema.ts b/src/utils/json/data-api/autocomplete/generateMongoFindJsonSchema.ts similarity index 100% rename from src/utils/json/mongo/autocomplete/generateMongoFindJsonSchema.ts rename to src/utils/json/data-api/autocomplete/generateMongoFindJsonSchema.ts diff --git a/src/utils/json/mongo/autocomplete/getKnownFields.ts b/src/utils/json/data-api/autocomplete/getKnownFields.ts similarity index 100% rename from src/utils/json/mongo/autocomplete/getKnownFields.ts rename to src/utils/json/data-api/autocomplete/getKnownFields.ts diff --git a/src/utils/json/mongo/mongoTestDocuments.ts b/src/utils/json/data-api/mongoTestDocuments.ts similarity index 100% rename from src/utils/json/mongo/mongoTestDocuments.ts rename to src/utils/json/data-api/mongoTestDocuments.ts diff --git a/src/utils/json/mongo/MongoBSONTypes.ts b/src/utils/json/mongo/MongoBSONTypes.ts deleted file mode 100644 index fa97add9c..000000000 --- a/src/utils/json/mongo/MongoBSONTypes.ts +++ /dev/null @@ -1,200 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { - Binary, - BSONSymbol, - Code, - DBRef, - Decimal128, - Double, - Int32, - Long, - MaxKey, - MinKey, - ObjectId, - Timestamp, - UUID, -} from 'mongodb'; - -/** - * Represents the different data types that can be stored in a MongoDB document. - * The string representation is casesensitive and should match the MongoDB documentation. - * https://www.mongodb.com/docs/manual/reference/bson-types/ - */ -export enum MongoBSONTypes { - String = 'string', - Number = 'number', - Int32 = 'int32', - Double = 'double', - Decimal128 = 'decimal128', - Long = 'long', - Boolean = 'boolean', - Object = 'object', - Array = 'array', - Null = 'null', - Undefined = 'undefined', - Date = 'date', - RegExp = 'regexp', - Binary = 'binary', - ObjectId = 'objectid', - Symbol = 'symbol', - Timestamp = 'timestamp', - UUID = 'uuid', - UUID_LEGACY = 'uuid-legacy', // old UUID subtype, used in some legacy data - MinKey = 'minkey', - MaxKey = 'maxkey', - DBRef = 'dbref', - Code = 'code', - CodeWithScope = 'codewithscope', - Map = 'map', - // Add any deprecated types if necessary - _UNKNOWN_ = '_unknown_', // Catch-all for unknown types -} - -export namespace MongoBSONTypes { - const displayStringMap: Record = { - [MongoBSONTypes.String]: 'String', - [MongoBSONTypes.Number]: 'Number', - [MongoBSONTypes.Int32]: 'Int32', - [MongoBSONTypes.Double]: 'Double', - [MongoBSONTypes.Decimal128]: 'Decimal128', - [MongoBSONTypes.Long]: 'Long', - [MongoBSONTypes.Boolean]: 'Boolean', - [MongoBSONTypes.Object]: 'Object', - [MongoBSONTypes.Array]: 'Array', - [MongoBSONTypes.Null]: 'Null', - [MongoBSONTypes.Undefined]: 'Undefined', - [MongoBSONTypes.Date]: 'Date', - [MongoBSONTypes.RegExp]: 'RegExp', - [MongoBSONTypes.Binary]: 'Binary', - [MongoBSONTypes.ObjectId]: 'ObjectId', - [MongoBSONTypes.Symbol]: 'Symbol', - [MongoBSONTypes.Timestamp]: 'Timestamp', - [MongoBSONTypes.MinKey]: 'MinKey', - [MongoBSONTypes.MaxKey]: 'MaxKey', - [MongoBSONTypes.DBRef]: 'DBRef', - [MongoBSONTypes.Code]: 'Code', - [MongoBSONTypes.CodeWithScope]: 'CodeWithScope', - [MongoBSONTypes.Map]: 'Map', - [MongoBSONTypes._UNKNOWN_]: 'Unknown', - [MongoBSONTypes.UUID]: 'UUID', - [MongoBSONTypes.UUID_LEGACY]: 'UUID (Legacy)', - }; - - export function toDisplayString(type: MongoBSONTypes): string { - return displayStringMap[type] || 'Unknown'; - } - - export function toString(type: MongoBSONTypes): string { - return type; - } - - /** - * Converts a MongoDB data type to a case sensitive JSON data type - * @param type The MongoDB data type - * @returns A corresponding JSON data type (please note: it's case sensitive) - */ - export function toJSONType(type: MongoBSONTypes): string { - switch (type) { - case MongoBSONTypes.String: - case MongoBSONTypes.Symbol: - case MongoBSONTypes.Date: - case MongoBSONTypes.Timestamp: - case MongoBSONTypes.ObjectId: - case MongoBSONTypes.RegExp: - case MongoBSONTypes.Binary: - case MongoBSONTypes.Code: - case MongoBSONTypes.UUID: - case MongoBSONTypes.UUID_LEGACY: - return 'string'; - - case MongoBSONTypes.Boolean: - return 'boolean'; - - case MongoBSONTypes.Int32: - case MongoBSONTypes.Long: - case MongoBSONTypes.Double: - case MongoBSONTypes.Decimal128: - return 'number'; - - case MongoBSONTypes.Object: - case MongoBSONTypes.Map: - case MongoBSONTypes.DBRef: - case MongoBSONTypes.CodeWithScope: - return 'object'; - - case MongoBSONTypes.Array: - return 'array'; - - case MongoBSONTypes.Null: - case MongoBSONTypes.Undefined: - case MongoBSONTypes.MinKey: - case MongoBSONTypes.MaxKey: - return 'null'; - - default: - return 'string'; // Default to string for unknown types - } - } - - /** - * Accepts a value from a MongoDB 'Document' object and returns the inferred type. - * @param value The value of a field in a MongoDB 'Document' object - * @returns - */ - export function inferType(value: unknown): MongoBSONTypes { - if (value === null) return MongoBSONTypes.Null; - if (value === undefined) return MongoBSONTypes.Undefined; - - switch (typeof value) { - case 'string': - return MongoBSONTypes.String; - case 'number': - return MongoBSONTypes.Double; // JavaScript numbers are doubles - case 'boolean': - return MongoBSONTypes.Boolean; - case 'object': - if (Array.isArray(value)) { - return MongoBSONTypes.Array; - } - - // Check for common BSON types first - if (value instanceof ObjectId) return MongoBSONTypes.ObjectId; - if (value instanceof Int32) return MongoBSONTypes.Int32; - if (value instanceof Double) return MongoBSONTypes.Double; - if (value instanceof Date) return MongoBSONTypes.Date; - if (value instanceof Timestamp) return MongoBSONTypes.Timestamp; - - // Less common types - if (value instanceof Decimal128) return MongoBSONTypes.Decimal128; - if (value instanceof Long) return MongoBSONTypes.Long; - if (value instanceof MinKey) return MongoBSONTypes.MinKey; - if (value instanceof MaxKey) return MongoBSONTypes.MaxKey; - if (value instanceof BSONSymbol) return MongoBSONTypes.Symbol; - if (value instanceof DBRef) return MongoBSONTypes.DBRef; - if (value instanceof Map) return MongoBSONTypes.Map; - if (value instanceof UUID && value.sub_type === Binary.SUBTYPE_UUID) return MongoBSONTypes.UUID; - if (value instanceof UUID && value.sub_type === Binary.SUBTYPE_UUID_OLD) - return MongoBSONTypes.UUID_LEGACY; - if (value instanceof Buffer || value instanceof Binary) return MongoBSONTypes.Binary; - if (value instanceof RegExp) return MongoBSONTypes.RegExp; - if (value instanceof Code) { - if (value.scope) { - return MongoBSONTypes.CodeWithScope; - } else { - return MongoBSONTypes.Code; - } - } - - // Default to Object if none of the above match - return MongoBSONTypes.Object; - default: - // This should never happen, but if it does, we'll catch it here - // TODO: add telemetry somewhere to know when it happens (not here, this could get hit too often) - return MongoBSONTypes._UNKNOWN_; - } - } -} diff --git a/src/utils/slickgrid/mongo/toSlickGridTable.ts b/src/utils/slickgrid/mongo/toSlickGridTable.ts index 737fcb7c0..ece8d02e6 100644 --- a/src/utils/slickgrid/mongo/toSlickGridTable.ts +++ b/src/utils/slickgrid/mongo/toSlickGridTable.ts @@ -6,8 +6,8 @@ import { EJSON } from 'bson'; import { type Document, type WithId } from 'mongodb'; import { type TableDataEntry } from '../../../documentdb/ClusterSession'; -import { MongoBSONTypes } from '../../json/mongo/MongoBSONTypes'; -import { valueToDisplayString } from '../../json/mongo/MongoValueFormatters'; +import { BSONTypes } from '../../json/data-api/BSONTypes'; +import { valueToDisplayString } from '../../json/data-api/ValueFormatters'; /** * Extracts data from a list of MongoDB documents at a specified path. @@ -45,8 +45,8 @@ export function getDataAtPath(documents: WithId[], path: string[]): Ta // we also make sure that the '_id' field is always included in the data! if (doc._id) { row['_id'] = { - value: valueToDisplayString(doc._id, MongoBSONTypes.inferType(doc._id)), - type: MongoBSONTypes.inferType(doc._id), + value: valueToDisplayString(doc._id, BSONTypes.inferType(doc._id)), + type: BSONTypes.inferType(doc._id), }; // TODO: problem here -> what if the user has a field with this name... row['x-objectid'] = EJSON.stringify(doc._id, { relaxed: false }); // this is crucial, we need to retain the _id field for future queries from the table view @@ -72,13 +72,13 @@ export function getDataAtPath(documents: WithId[], path: string[]): Ta continue; } else { const value: unknown = subdocument[key]; - const type: MongoBSONTypes = MongoBSONTypes.inferType(value); + const type: BSONTypes = BSONTypes.inferType(value); // eslint-disable-next-line if (value instanceof Array) { row[key] = { value: `array[${value.length}]`, - type: MongoBSONTypes.Array, + type: BSONTypes.Array, }; } else { row[key] = { value: valueToDisplayString(value, type), type: type }; diff --git a/src/utils/slickgrid/mongo/toSlickGridTree.ts b/src/utils/slickgrid/mongo/toSlickGridTree.ts index 9d3742cfe..1db9e979d 100644 --- a/src/utils/slickgrid/mongo/toSlickGridTree.ts +++ b/src/utils/slickgrid/mongo/toSlickGridTree.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { type Document, type ObjectId, type WithId } from 'mongodb'; -import { MongoBSONTypes } from '../../json/mongo/MongoBSONTypes'; -import { valueToDisplayString } from '../../json/mongo/MongoValueFormatters'; +import { BSONTypes } from '../../json/data-api/BSONTypes'; +import { valueToDisplayString } from '../../json/data-api/ValueFormatters'; /** * The data structure for a single node entry in the tree data structure for SlickGrid. @@ -113,10 +113,10 @@ export function documentToSlickGridTree(document: WithId, idPrefix?: s continue; } - const dataType: MongoBSONTypes = MongoBSONTypes.inferType(stackEntry.value); + const dataType: BSONTypes = BSONTypes.inferType(stackEntry.value); switch (dataType) { - case MongoBSONTypes.Object: { + case BSONTypes.Object: { tree.push({ id: globalEntryId, field: `${stackEntry.key}`, @@ -131,7 +131,7 @@ export function documentToSlickGridTree(document: WithId, idPrefix?: s }); break; } - case MongoBSONTypes.Array: { + case BSONTypes.Array: { const value = stackEntry.value as unknown[]; tree.push({ @@ -157,7 +157,7 @@ export function documentToSlickGridTree(document: WithId, idPrefix?: s id: globalEntryId, field: `${stackEntry.key}`, value: valueToDisplayString(stackEntry.value, dataType), - type: MongoBSONTypes.toDisplayString(MongoBSONTypes.inferType(stackEntry.value)), + type: BSONTypes.toDisplayString(BSONTypes.inferType(stackEntry.value)), parentId: stackEntry.parentId, }); break; diff --git a/src/webviews/documentdb/collectionView/collectionViewRouter.ts b/src/webviews/documentdb/collectionView/collectionViewRouter.ts index fec8eb05d..c2d71e1d9 100644 --- a/src/webviews/documentdb/collectionView/collectionViewRouter.ts +++ b/src/webviews/documentdb/collectionView/collectionViewRouter.ts @@ -12,7 +12,7 @@ import { type JSONSchema } from 'vscode-json-languageservice'; import { z } from 'zod'; import { ClusterSession } from '../../../documentdb/ClusterSession'; import { getConfirmationAsInSettings } from '../../../utils/dialogs/getConfirmation'; -import { getKnownFields, type FieldEntry } from '../../../utils/json/mongo/autocomplete/getKnownFields'; +import { getKnownFields, type FieldEntry } from '../../../utils/json/data-api/autocomplete/getKnownFields'; import { publicProcedureWithTelemetry, router, type WithTelemetry } from '../../api/extension-server/trpc'; import * as l10n from '@vscode/l10n'; @@ -40,8 +40,8 @@ import { ext } from '../../../extensionVariables'; import { QueryInsightsAIService } from '../../../services/ai/QueryInsightsAIService'; import { type CollectionItem } from '../../../tree/documentdb/CollectionItem'; // eslint-disable-next-line import/no-internal-modules -import basicFindQuerySchema from '../../../utils/json/mongo/autocomplete/basicMongoFindFilterSchema.json'; -import { generateMongoFindJsonSchema } from '../../../utils/json/mongo/autocomplete/generateMongoFindJsonSchema'; +import basicFindQuerySchema from '../../../utils/json/data-api/autocomplete/basicMongoFindFilterSchema.json'; +import { generateMongoFindJsonSchema } from '../../../utils/json/data-api/autocomplete/generateMongoFindJsonSchema'; import { promptAfterActionEventually } from '../../../utils/survey'; import { UsageImpact } from '../../../utils/surveyTypes'; import { type BaseRouterContext } from '../../api/configuration/appRouter'; diff --git a/src/webviews/documentdb/collectionView/components/queryEditor/QueryEditor.tsx b/src/webviews/documentdb/collectionView/components/queryEditor/QueryEditor.tsx index 7b6d7e8ec..b03e5e9bb 100644 --- a/src/webviews/documentdb/collectionView/components/queryEditor/QueryEditor.tsx +++ b/src/webviews/documentdb/collectionView/components/queryEditor/QueryEditor.tsx @@ -11,7 +11,7 @@ import { InputWithProgress } from '../../../../components/InputWithProgress'; // eslint-disable-next-line import/no-internal-modules import type * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; // eslint-disable-next-line import/no-internal-modules -import basicFindQuerySchema from '../../../../../utils/json/mongo/autocomplete/basicMongoFindFilterSchema.json'; +import basicFindQuerySchema from '../../../../../utils/json/data-api/autocomplete/basicMongoFindFilterSchema.json'; import { useConfiguration } from '../../../../api/webview-client/useConfiguration'; import { type CollectionViewWebviewConfigurationType } from '../../collectionViewController'; From 50dabfb827e7284a35a76c4b627a87f8a144171d Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Mon, 16 Feb 2026 20:22:56 +0100 Subject: [PATCH 02/25] refactor: convert SchemaAnalyzer to class with addDocument/getSchema API Group B of SchemaAnalyzer refactor: - B1: SchemaAnalyzer class with addDocument(), getSchema(), reset(), getDocumentCount() - B2: clone() method using structuredClone for schema branching - B3: addDocuments() batch convenience method - B4: static fromDocument()/fromDocuments() factories (replaces getSchemaFromDocument) - B5: Migrate ClusterSession to use SchemaAnalyzer instance - B6-B7: Remove old free functions (updateSchemaWithDocument, getSchemaFromDocument) - Keep getPropertyNamesAtLevel, getSchemaAtPath, buildFullPaths as standalone exports --- src/documentdb/ClusterSession.ts | 14 +- .../SchemaAnalyzer.arrayStats.test.ts | 67 ++-- .../json/data-api/SchemaAnalyzer.test.ts | 45 +-- src/utils/json/data-api/SchemaAnalyzer.ts | 285 +++++------------- 4 files changed, 149 insertions(+), 262 deletions(-) diff --git a/src/documentdb/ClusterSession.ts b/src/documentdb/ClusterSession.ts index b4ac8f3b1..53ae67c7e 100644 --- a/src/documentdb/ClusterSession.ts +++ b/src/documentdb/ClusterSession.ts @@ -7,7 +7,7 @@ import * as l10n from '@vscode/l10n'; import { EJSON } from 'bson'; import { ObjectId, type Document, type Filter, type WithId } from 'mongodb'; import { type JSONSchema } from '../utils/json/JSONSchema'; -import { getPropertyNamesAtLevel, updateSchemaWithDocument } from '../utils/json/data-api/SchemaAnalyzer'; +import { SchemaAnalyzer, getPropertyNamesAtLevel } from '../utils/json/data-api/SchemaAnalyzer'; import { getDataAtPath } from '../utils/slickgrid/mongo/toSlickGridTable'; import { toSlickGridTree, type TreeData } from '../utils/slickgrid/mongo/toSlickGridTree'; import { ClustersClient, type FindQueryParams } from './ClustersClient'; @@ -78,7 +78,7 @@ export class ClusterSession { * Updates progressively as users navigate through different pages. * Reset when the query or page size changes. */ - private _accumulatedJsonSchema: JSONSchema = {}; + private _schemaAnalyzer: SchemaAnalyzer = new SchemaAnalyzer(); /** * Tracks the highest page number that has been accumulated into the schema. @@ -162,7 +162,7 @@ export class ClusterSession { } // The user's query has changed, invalidate all caches - this._accumulatedJsonSchema = {}; + this._schemaAnalyzer.reset(); this._highestPageAccumulated = 0; this._currentPageSize = null; this._currentRawDocuments = []; @@ -185,7 +185,7 @@ export class ClusterSession { private resetAccumulationIfPageSizeChanged(newPageSize: number): void { if (this._currentPageSize !== null && this._currentPageSize !== newPageSize) { // Page size changed, reset accumulation tracking - this._accumulatedJsonSchema = {}; + this._schemaAnalyzer.reset(); this._highestPageAccumulated = 0; } this._currentPageSize = newPageSize; @@ -298,7 +298,7 @@ export class ClusterSession { // Since navigation is sequential and starts at page 1, we only need to track // the highest page number accumulated if (pageNumber > this._highestPageAccumulated) { - this._currentRawDocuments.map((doc) => updateSchemaWithDocument(this._accumulatedJsonSchema, doc)); + this._schemaAnalyzer.addDocuments(this._currentRawDocuments); this._highestPageAccumulated = pageNumber; } @@ -355,7 +355,7 @@ export class ClusterSession { public getCurrentPageAsTable(path: string[]): TableData { const responsePack: TableData = { path: path, - headers: getPropertyNamesAtLevel(this._accumulatedJsonSchema, path), + headers: getPropertyNamesAtLevel(this._schemaAnalyzer.getSchema(), path), data: getDataAtPath(this._currentRawDocuments, path), }; @@ -363,7 +363,7 @@ export class ClusterSession { } public getCurrentSchema(): JSONSchema { - return this._accumulatedJsonSchema; + return this._schemaAnalyzer.getSchema(); } // ============================================================================ diff --git a/src/utils/json/data-api/SchemaAnalyzer.arrayStats.test.ts b/src/utils/json/data-api/SchemaAnalyzer.arrayStats.test.ts index 6933315da..1f0ce7a3e 100644 --- a/src/utils/json/data-api/SchemaAnalyzer.arrayStats.test.ts +++ b/src/utils/json/data-api/SchemaAnalyzer.arrayStats.test.ts @@ -5,7 +5,7 @@ import { ObjectId, type Document, type WithId } from 'mongodb'; import { type JSONSchema } from '../JSONSchema'; -import { updateSchemaWithDocument } from './SchemaAnalyzer'; +import { SchemaAnalyzer } from './SchemaAnalyzer'; /** * This test file investigates the array element occurrence/stats problem. @@ -36,7 +36,7 @@ import { updateSchemaWithDocument } from './SchemaAnalyzer'; */ describe('Array element occurrence analysis', () => { it('counts element types across multiple documents', () => { - const schema: JSONSchema = {}; + const analyzer = new SchemaAnalyzer(); const doc1: WithId = { _id: new ObjectId(), @@ -51,9 +51,10 @@ describe('Array element occurrence analysis', () => { data: ['z'], }; - updateSchemaWithDocument(schema, doc1); - updateSchemaWithDocument(schema, doc2); - updateSchemaWithDocument(schema, doc3); + analyzer.addDocument(doc1); + analyzer.addDocument(doc2); + analyzer.addDocument(doc3); + const schema = analyzer.getSchema(); // data field: array seen in 3 docs const dataField = schema.properties?.['data'] as JSONSchema; @@ -102,7 +103,7 @@ describe('Array element occurrence analysis', () => { }); it('tracks min/max array lengths across documents', () => { - const schema: JSONSchema = {}; + const analyzer = new SchemaAnalyzer(); const doc1: WithId = { _id: new ObjectId(), @@ -117,9 +118,10 @@ describe('Array element occurrence analysis', () => { tags: ['p', 'q', 'r', 's', 't'], }; - updateSchemaWithDocument(schema, doc1); - updateSchemaWithDocument(schema, doc2); - updateSchemaWithDocument(schema, doc3); + analyzer.addDocument(doc1); + analyzer.addDocument(doc2); + analyzer.addDocument(doc3); + const schema = analyzer.getSchema(); const tagsField = schema.properties?.['tags'] as JSONSchema; const arrayEntry = tagsField.anyOf?.find((e) => (e as JSONSchema)['x-bsonType'] === 'array') as JSONSchema; @@ -129,7 +131,7 @@ describe('Array element occurrence analysis', () => { }); it('accumulates nested object properties from objects inside arrays across documents', () => { - const schema: JSONSchema = {}; + const analyzer = new SchemaAnalyzer(); // doc1 has two objects with different properties in the items array const doc1: WithId = { @@ -146,8 +148,9 @@ describe('Array element occurrence analysis', () => { items: [{ name: 'Desk', weight: 50 }], }; - updateSchemaWithDocument(schema, doc1); - updateSchemaWithDocument(schema, doc2); + analyzer.addDocument(doc1); + analyzer.addDocument(doc2); + const schema = analyzer.getSchema(); const itemsField = schema.properties?.['items'] as JSONSchema; const arrayEntry = itemsField.anyOf?.find((e) => (e as JSONSchema)['x-bsonType'] === 'array') as JSONSchema; @@ -182,7 +185,7 @@ describe('Array element occurrence analysis', () => { }); it('handles arrays that ONLY contain primitives (no occurrence complexity)', () => { - const schema: JSONSchema = {}; + const analyzer = new SchemaAnalyzer(); const doc1: WithId = { _id: new ObjectId(), @@ -193,8 +196,9 @@ describe('Array element occurrence analysis', () => { scores: [100, 55], }; - updateSchemaWithDocument(schema, doc1); - updateSchemaWithDocument(schema, doc2); + analyzer.addDocument(doc1); + analyzer.addDocument(doc2); + const schema = analyzer.getSchema(); const scoresField = schema.properties?.['scores'] as JSONSchema; const arrayEntry = scoresField.anyOf?.find((e) => (e as JSONSchema)['x-bsonType'] === 'array') as JSONSchema; @@ -238,7 +242,7 @@ describe('Array element occurrence analysis', () => { // This is a REAL BUG: initializeStatsForValue is called for the first occurrence // per array, but the typeEntry already has stats from previous arrays. - const schema: JSONSchema = {}; + const analyzer = new SchemaAnalyzer(); const doc1: WithId = { _id: new ObjectId(), @@ -249,8 +253,9 @@ describe('Array element occurrence analysis', () => { scores: [5, 15], }; - updateSchemaWithDocument(schema, doc1); - updateSchemaWithDocument(schema, doc2); + analyzer.addDocument(doc1); + analyzer.addDocument(doc2); + const schema = analyzer.getSchema(); const scoresField = schema.properties?.['scores'] as JSONSchema; const arrayEntry = scoresField.anyOf?.find((e) => (e as JSONSchema)['x-bsonType'] === 'array') as JSONSchema; @@ -296,7 +301,7 @@ describe('Array probability denominator problem', () => { // formula `x-occurrence / parent.x-documentsInspected` works at every // nesting level. - const schema: JSONSchema = {}; + const analyzer = new SchemaAnalyzer(); const doc1: WithId = { _id: new ObjectId(), @@ -313,8 +318,9 @@ describe('Array probability denominator problem', () => { a: objectElements, }; - updateSchemaWithDocument(schema, doc1); - updateSchemaWithDocument(schema, doc2); + analyzer.addDocument(doc1); + analyzer.addDocument(doc2); + const schema = analyzer.getSchema(); // Root level expect(schema['x-documentsInspected']).toBe(2); @@ -351,7 +357,7 @@ describe('Array probability denominator problem', () => { // price: 1/3 = 33% // discount: 1/3 = 33% - const schema: JSONSchema = {}; + const analyzer = new SchemaAnalyzer(); const doc1: WithId = { _id: new ObjectId(), @@ -362,8 +368,9 @@ describe('Array probability denominator problem', () => { items: [{ name: 'C', discount: true }], }; - updateSchemaWithDocument(schema, doc1); - updateSchemaWithDocument(schema, doc2); + analyzer.addDocument(doc1); + analyzer.addDocument(doc2); + const schema = analyzer.getSchema(); const itemsField = schema.properties?.['items'] as JSONSchema; const arrayEntry = itemsField.anyOf?.find((e) => (e as JSONSchema)['x-bsonType'] === 'array') as JSONSchema; @@ -389,14 +396,15 @@ describe('Array probability denominator problem', () => { // At address.anyOf[object] level: x-documentsInspected = 2 // city: 2/2 = 100%, zip: 1/2 = 50% - const schema: JSONSchema = {}; + const analyzer = new SchemaAnalyzer(); const doc: WithId = { _id: new ObjectId(), items: [{ address: { city: 'NY', zip: '10001' } }, { address: { city: 'LA' } }], }; - updateSchemaWithDocument(schema, doc); + analyzer.addDocument(doc); + const schema = analyzer.getSchema(); const itemsField = schema.properties?.['items'] as JSONSchema; const arrayEntry = itemsField.anyOf?.find((e) => (e as JSONSchema)['x-bsonType'] === 'array') as JSONSchema; @@ -422,7 +430,7 @@ describe('Array probability denominator problem', () => { }); it('does NOT change x-documentsInspected at root level (root keeps document count)', () => { - const schema: JSONSchema = {}; + const analyzer = new SchemaAnalyzer(); const doc1: WithId = { _id: new ObjectId(), @@ -435,8 +443,9 @@ describe('Array probability denominator problem', () => { address: { city: 'LA', zip: '90001' }, }; - updateSchemaWithDocument(schema, doc1); - updateSchemaWithDocument(schema, doc2); + analyzer.addDocument(doc1); + analyzer.addDocument(doc2); + const schema = analyzer.getSchema(); // Root x-documentsInspected is document count, not affected by the fix expect(schema['x-documentsInspected']).toBe(2); diff --git a/src/utils/json/data-api/SchemaAnalyzer.test.ts b/src/utils/json/data-api/SchemaAnalyzer.test.ts index 731791611..f8da609b9 100644 --- a/src/utils/json/data-api/SchemaAnalyzer.test.ts +++ b/src/utils/json/data-api/SchemaAnalyzer.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { type JSONSchema, type JSONSchemaRef } from '../JSONSchema'; -import { getPropertyNamesAtLevel, updateSchemaWithDocument } from './SchemaAnalyzer'; +import { SchemaAnalyzer, getPropertyNamesAtLevel } from './SchemaAnalyzer'; import { arraysWithDifferentDataTypes, complexDocument, @@ -17,15 +17,15 @@ import { describe('DocumentDB Schema Analyzer', () => { it('prints out schema for testing', () => { - const schema: JSONSchema = {}; - updateSchemaWithDocument(schema, embeddedDocumentOnly); + const analyzer = SchemaAnalyzer.fromDocument(embeddedDocumentOnly); + const schema = analyzer.getSchema(); console.log(JSON.stringify(schema, null, 2)); expect(schema).toBeDefined(); }); it('supports many documents', () => { - const schema: JSONSchema = {}; - sparseDocumentsArray.forEach((doc) => updateSchemaWithDocument(schema, doc)); + const analyzer = SchemaAnalyzer.fromDocuments(sparseDocumentsArray); + const schema = analyzer.getSchema(); expect(schema).toBeDefined(); // Check that 'x-documentsInspected' is correct @@ -66,8 +66,8 @@ describe('DocumentDB Schema Analyzer', () => { }); it('detects all BSON types from flatDocument', () => { - const schema: JSONSchema = {}; - updateSchemaWithDocument(schema, flatDocument); + const analyzer = SchemaAnalyzer.fromDocument(flatDocument); + const schema = analyzer.getSchema(); // Check that all fields are detected const expectedFields = Object.keys(flatDocument); @@ -92,8 +92,8 @@ describe('DocumentDB Schema Analyzer', () => { }); it('detects embedded objects correctly', () => { - const schema: JSONSchema = {}; - updateSchemaWithDocument(schema, embeddedDocumentOnly); + const analyzer = SchemaAnalyzer.fromDocument(embeddedDocumentOnly); + const schema = analyzer.getSchema(); // Check that the root properties are detected expect(schema.properties).toHaveProperty('personalInfo'); @@ -118,8 +118,8 @@ describe('DocumentDB Schema Analyzer', () => { }); it('detects arrays and their element types correctly', () => { - const schema: JSONSchema = {}; - updateSchemaWithDocument(schema, arraysWithDifferentDataTypes); + const analyzer = SchemaAnalyzer.fromDocument(arraysWithDifferentDataTypes); + const schema = analyzer.getSchema(); // Check that arrays are detected expect(schema.properties).toHaveProperty('integersArray'); @@ -150,8 +150,8 @@ describe('DocumentDB Schema Analyzer', () => { }); it('handles arrays within objects and objects within arrays', () => { - const schema: JSONSchema = {}; - updateSchemaWithDocument(schema, complexDocument); + const analyzer = SchemaAnalyzer.fromDocument(complexDocument); + const schema = analyzer.getSchema(); // Access 'user.profile.hobbies' const userProfile = schema.properties && schema.properties['user'].anyOf?.[0]?.properties?.['profile']; @@ -179,8 +179,8 @@ describe('DocumentDB Schema Analyzer', () => { }); it('updates schema correctly when processing multiple documents', () => { - const schema: JSONSchema = {}; - complexDocumentsArray.forEach((doc) => updateSchemaWithDocument(schema, doc)); + const analyzer = SchemaAnalyzer.fromDocuments(complexDocumentsArray); + const schema = analyzer.getSchema(); // Check that 'x-documentsInspected' is correct expect(schema['x-documentsInspected']).toBe(complexDocumentsArray.length); @@ -207,8 +207,8 @@ describe('DocumentDB Schema Analyzer', () => { describe('traverses schema', () => { it('with valid paths', () => { - const schema: JSONSchema = {}; - updateSchemaWithDocument(schema, complexDocument); + const analyzer = SchemaAnalyzer.fromDocument(complexDocument); + const schema = analyzer.getSchema(); let propertiesAtRoot = getPropertyNamesAtLevel(schema, []); expect(propertiesAtRoot).toHaveLength(4); @@ -221,8 +221,8 @@ describe('DocumentDB Schema Analyzer', () => { }); it('with broken paths', () => { - const schema: JSONSchema = {}; - updateSchemaWithDocument(schema, complexDocument); + const analyzer = SchemaAnalyzer.fromDocument(complexDocument); + const schema = analyzer.getSchema(); const propertiesAtRoot = getPropertyNamesAtLevel(schema, []); expect(propertiesAtRoot).toHaveLength(4); @@ -233,9 +233,10 @@ describe('DocumentDB Schema Analyzer', () => { }); it('with sparse docs and mixed types', () => { - const schema: JSONSchema = {}; - updateSchemaWithDocument(schema, complexDocument); - updateSchemaWithDocument(schema, complexDocumentWithOddTypes); + const analyzer = new SchemaAnalyzer(); + analyzer.addDocument(complexDocument); + analyzer.addDocument(complexDocumentWithOddTypes); + const schema = analyzer.getSchema(); let propertiesAtRoot = getPropertyNamesAtLevel(schema, []); expect(propertiesAtRoot).toHaveLength(4); diff --git a/src/utils/json/data-api/SchemaAnalyzer.ts b/src/utils/json/data-api/SchemaAnalyzer.ts index bc97dff49..c340172a9 100644 --- a/src/utils/json/data-api/SchemaAnalyzer.ts +++ b/src/utils/json/data-api/SchemaAnalyzer.ts @@ -3,66 +3,96 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as l10n from '@vscode/l10n'; +import { assert } from 'console'; +import Denque from 'denque'; +import { type Document, type WithId } from 'mongodb'; +import { type JSONSchema } from '../JSONSchema'; +import { BSONTypes } from './BSONTypes'; + /** - * This is an example of a JSON Schema document that will be generated from MongoDB documents. - * It's optimized for the use-case of generating a schema for a table view, the monaco editor, and schema statistics. - * - * This is a 'work in progress' and will be updated as we progress with the project. - * - * Curent focus is: - * - discovery of the document structure - * - basic pre for future statistics work + * Incremental schema analyzer for documents from the MongoDB API / DocumentDB API. * - * Future tasks: - * - statistics aggregation - * - meaningful 'description' and 'markdownDescription' - * - add more properties to the schema, incl. properties like '$id', '$schema', and enable schema sharing/download + * Analyzes documents one at a time (or in batches) and builds a cumulative + * JSON Schema with statistical extensions (x-occurrence, x-bsonType, etc.). * + * The output schema follows JSON Schema draft-07 with custom x- extensions. + */ +export class SchemaAnalyzer { + private _schema: JSONSchema = {}; -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://example.com/sample.schema.json", - "title": "Sample Document Schema", - "type": "object", - "properties": { - "a-propert-root-level": { - "description": "a description as text", - "anyOf": [ // anyOf is used to indicate that the value can be of any of the types listed - { - "type": "string" - }, - { - "type": "string" - } - ] - }, - "isOpen": { - "description": "Indicates if the item is open", - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "number" + /** + * Adds a single document to the accumulated schema. + * This is the primary incremental API — call once per document. + */ + addDocument(document: WithId): void { + updateSchemaWithDocumentInternal(this._schema, document); + } + + /** + * Adds multiple documents to the accumulated schema. + * Convenience method equivalent to calling addDocument() for each. + */ + addDocuments(documents: ReadonlyArray>): void { + for (const doc of documents) { + this.addDocument(doc); } - ] } - }, - "required": ["isOpen"] -} - * - * - */ + /** + * Returns the current accumulated JSON Schema. + * The returned object is a live reference (not a copy) — do not mutate externally. + */ + getSchema(): JSONSchema { + return this._schema; + } -import * as l10n from '@vscode/l10n'; -import { assert } from 'console'; -import Denque from 'denque'; -import { type Document, type WithId } from 'mongodb'; -import { type JSONSchema } from '../JSONSchema'; -import { BSONTypes } from './BSONTypes'; + /** + * Returns the number of documents analyzed so far. + */ + getDocumentCount(): number { + return (this._schema['x-documentsInspected'] as number) ?? 0; + } + + /** + * Resets the analyzer to its initial empty state. + */ + reset(): void { + this._schema = {}; + } -export function updateSchemaWithDocument(schema: JSONSchema, document: WithId): void { + /** + * Creates a deep copy of this analyzer, including all accumulated schema data. + * Useful for aggregation stage branching where each stage needs its own schema state. + */ + clone(): SchemaAnalyzer { + const copy = new SchemaAnalyzer(); + copy._schema = structuredClone(this._schema); + return copy; + } + + /** + * Creates a SchemaAnalyzer from a single document. + * Equivalent to creating an instance and calling addDocument() once. + */ + static fromDocument(document: WithId): SchemaAnalyzer { + const analyzer = new SchemaAnalyzer(); + analyzer.addDocument(document); + return analyzer; + } + + /** + * Creates a SchemaAnalyzer from multiple documents. + * Equivalent to creating an instance and calling addDocuments(). + */ + static fromDocuments(documents: ReadonlyArray>): SchemaAnalyzer { + const analyzer = new SchemaAnalyzer(); + analyzer.addDocuments(documents); + return analyzer; + } +} + +function updateSchemaWithDocumentInternal(schema: JSONSchema, document: WithId): void { // Initialize schema if it's empty if (!schema.properties) { schema.properties = {}; @@ -317,159 +347,6 @@ function updateMinMaxStats(schema: JSONSchema, minKey: string, maxKey: string, v } } -export function getSchemaFromDocument(document: WithId): JSONSchema { - const schema: JSONSchema = {}; - schema['x-documentsInspected'] = 1; // we're inspecting one document, this will make sense when we start aggregating stats - schema.properties = {}; - - type WorkItem = { - fieldName: string; - fieldMongoType: BSONTypes; // the inferred BSON type - propertyTypeEntry: JSONSchema; // points to the entry within the 'anyOf' property of the schema - fieldValue: unknown; - pathSoFar: string; // used for debugging - }; - - // having some import/require issues with Denque atm - // prototype with an array - //const fifoQueue = new Denque(); - const fifoQueue: WorkItem[] = []; - - /** - * Push all elements from the root of the document into the queue - */ - for (const [name, value] of Object.entries(document)) { - const mongoDatatype = BSONTypes.inferType(value); - - const typeEntry = { - type: BSONTypes.toJSONType(mongoDatatype), - 'x-bsonType': mongoDatatype, - 'x-typeOccurrence': 1, - }; - - // please note (1/2): we're adding the type entry to the schema here - schema.properties[name] = { anyOf: [typeEntry], 'x-occurrence': 1 }; - - fifoQueue.push({ - fieldName: name, - fieldMongoType: mongoDatatype, - propertyTypeEntry: typeEntry, // please note (2/2): and we're keeping a reference to it here for further updates - fieldValue: value, - pathSoFar: name, - }); - } - - /** - * Work through the queue, adding elements to the schema as we go. - * This is a breadth-first search of the document, do note special - * handling on objects/arrays - */ - while (fifoQueue.length > 0) { - const item = fifoQueue.shift(); // todo, replace with a proper queue - if (item === undefined) { - // unexpected, but let's try to continue - continue; - } - - switch (item.fieldMongoType) { - case BSONTypes.Object: { - const objKeys = Object.keys(item.fieldValue as object).length; - item.propertyTypeEntry['x-maxLength'] = objKeys; - item.propertyTypeEntry['x-minLength'] = objKeys; - - // prepare an entry for the object properties - item.propertyTypeEntry.properties = {}; - - for (const [name, value] of Object.entries(item.fieldValue as object)) { - const mongoDatatype = BSONTypes.inferType(value); - - const typeEntry = { - type: BSONTypes.toJSONType(mongoDatatype), - 'x-bsonType': mongoDatatype, - 'x-typeOccurrence': 1, - }; - - // please note (1/2): we're adding the entry to the main schema here - item.propertyTypeEntry.properties[name] = { anyOf: [typeEntry], 'x-occurrence': 1 }; - - fifoQueue.push({ - fieldName: name, - fieldMongoType: mongoDatatype, - propertyTypeEntry: typeEntry, // please note (2/2): and we're keeping a reference to it here for further updates to the schema - fieldValue: value, - pathSoFar: `${item.pathSoFar}.${item.fieldName}`, - }); - } - break; - } - case BSONTypes.Array: { - const arrayLength = (item.fieldValue as unknown[]).length; - item.propertyTypeEntry['x-maxLength'] = arrayLength; - item.propertyTypeEntry['x-minLength'] = arrayLength; - - // preapare the array items entry (in two lines for ts not to compalin about the missing type later on) - item.propertyTypeEntry.items = {}; - item.propertyTypeEntry.items.anyOf = []; - - const encounteredMongoTypes: Map = new Map(); - - // iterate over the array and infer the type of each element - for (const element of item.fieldValue as unknown[]) { - const elementMongoType = BSONTypes.inferType(element); - - let itemEntry: JSONSchema; - - if (!encounteredMongoTypes.has(elementMongoType)) { - itemEntry = { - type: BSONTypes.toJSONType(elementMongoType), - 'x-bsonType': elementMongoType, - 'x-typeOccurrence': 1, // Initialize type occurrence counter - }; - item.propertyTypeEntry.items.anyOf.push(itemEntry); - encounteredMongoTypes.set(elementMongoType, itemEntry); - - initializeStatsForValue(element, elementMongoType, itemEntry); - } else { - // if we've already encountered this type, we'll just add the type to the existing entry - itemEntry = encounteredMongoTypes.get(elementMongoType) as JSONSchema; - - if (itemEntry === undefined) continue; // unexpected, but let's try to continue - - if (itemEntry['x-typeOccurrence'] !== undefined) { - itemEntry['x-typeOccurrence'] += 1; - } - - // Aggregate stats with the new value - aggregateStatsForValue(element, elementMongoType, itemEntry); - } - - // an imporant exception for arrays as we have to start adding them already now to the schema - // (if we want to avoid more iterations over the data) - if (elementMongoType === BSONTypes.Object || elementMongoType === BSONTypes.Array) { - fifoQueue.push({ - fieldName: '[]', // Array items don't have a field name - fieldMongoType: elementMongoType, - propertyTypeEntry: itemEntry, - fieldValue: element, - pathSoFar: `${item.pathSoFar}.${item.fieldName}.items`, - }); - } - } - - break; - } - - default: { - // For all other types, update stats for the value - initializeStatsForValue(item.fieldValue, item.fieldMongoType, item.propertyTypeEntry); - break; - } - } - } - - return schema; -} - /** * Helper function to compute stats for a value based on its MongoDB data type * Updates the provided propertyTypeEntry with the computed stats From bd708657f5c4686e5abf3a2e2949e06cf01707dc Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Mon, 16 Feb 2026 20:31:32 +0100 Subject: [PATCH 03/25] refactor: update JSONSchema interface with typed x- properties and fix properties type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Group C of SchemaAnalyzer refactor: - C1: Add typed x-minValue, x-maxValue, x-minLength, x-maxLength, x-minDate, x-maxDate, x-trueCount, x-falseCount, x-minItems, x-maxItems, x-minProperties, x-maxProperties to JSONSchema interface - C2: Fix properties type: properties?: JSONSchema → properties?: JSONSchemaMap - C3: Fix downstream type errors in SchemaAnalyzer.test.ts (JSONSchemaRef casts) --- src/utils/json/JSONSchema.ts | 34 ++++++--- .../json/data-api/SchemaAnalyzer.test.ts | 72 ++++++++++--------- 2 files changed, 64 insertions(+), 42 deletions(-) diff --git a/src/utils/json/JSONSchema.ts b/src/utils/json/JSONSchema.ts index 467669ed5..3127932d6 100644 --- a/src/utils/json/JSONSchema.ts +++ b/src/utils/json/JSONSchema.ts @@ -24,16 +24,14 @@ export interface JSONSchema { $id?: string; $schema?: string; type?: string | string[]; - 'x-documentsInspected'?: number; - 'x-occurrence'?: number; - 'x-typeOccurrence'?: number; - 'x-bsonType'?: string; // Explicitly declare the key with a dash using quotes title?: string; + description?: string; definitions?: { [name: string]: JSONSchema; }; - description?: string; - properties?: JSONSchema; // changed from: JSONSchemaMap; + + // Structure + properties?: JSONSchemaMap; patternProperties?: JSONSchemaMap; additionalProperties?: JSONSchemaRef; minProperties?: number; @@ -44,7 +42,6 @@ export interface JSONSchema { [prop: string]: string[]; }; items?: JSONSchemaRef | JSONSchemaRef[]; - required?: string[]; $ref?: string; anyOf?: JSONSchemaRef[]; @@ -58,14 +55,35 @@ export interface JSONSchema { propertyNames?: JSONSchemaRef; examples?: undefined[]; $comment?: string; - $defs?: { [name: string]: JSONSchema; }; + + // Monaco extensions markdownEnumDescriptions?: string[]; markdownDescription?: string; doNotSuggest?: boolean; suggestSortText?: string; + + // SchemaAnalyzer extensions — document/field level + 'x-documentsInspected'?: number; + 'x-occurrence'?: number; + + // SchemaAnalyzer extensions — type entry level (on entries in anyOf) + 'x-bsonType'?: string; + 'x-typeOccurrence'?: number; + 'x-minValue'?: number; + 'x-maxValue'?: number; + 'x-minLength'?: number; + 'x-maxLength'?: number; + 'x-minDate'?: number; + 'x-maxDate'?: number; + 'x-trueCount'?: number; + 'x-falseCount'?: number; + 'x-minItems'?: number; + 'x-maxItems'?: number; + 'x-minProperties'?: number; + 'x-maxProperties'?: number; } export interface JSONSchemaMap { [name: string]: JSONSchemaRef; diff --git a/src/utils/json/data-api/SchemaAnalyzer.test.ts b/src/utils/json/data-api/SchemaAnalyzer.test.ts index f8da609b9..33d2c5156 100644 --- a/src/utils/json/data-api/SchemaAnalyzer.test.ts +++ b/src/utils/json/data-api/SchemaAnalyzer.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { type JSONSchema, type JSONSchemaRef } from '../JSONSchema'; +import { type JSONSchema, type JSONSchemaMap, type JSONSchemaRef } from '../JSONSchema'; import { SchemaAnalyzer, getPropertyNamesAtLevel } from './SchemaAnalyzer'; import { arraysWithDifferentDataTypes, @@ -39,28 +39,28 @@ describe('DocumentDB Schema Analyzer', () => { ); // Check that the 'name' field is detected correctly - const nameField: JSONSchema = schema.properties?.['name']; + const nameField = schema.properties?.['name'] as JSONSchema; expect(nameField).toBeDefined(); expect(nameField?.['x-occurrence']).toBeGreaterThan(0); // Access 'anyOf' to get the type entries - const nameFieldTypes = nameField.anyOf?.map((typeEntry) => typeEntry['type']); + const nameFieldTypes = nameField.anyOf?.map((typeEntry) => (typeEntry as JSONSchema)['type']); expect(nameFieldTypes).toContain('string'); // Check that the 'age' field has the correct type - const ageField: JSONSchema = schema.properties?.['age']; + const ageField = schema.properties?.['age'] as JSONSchema; expect(ageField).toBeDefined(); - const ageFieldTypes = ageField.anyOf?.map((typeEntry) => typeEntry['type']); + const ageFieldTypes = ageField.anyOf?.map((typeEntry) => (typeEntry as JSONSchema)['type']); expect(ageFieldTypes).toContain('number'); // Check that the 'isActive' field is a boolean - const isActiveField: JSONSchema = schema.properties?.['isActive']; + const isActiveField = schema.properties?.['isActive'] as JSONSchema; expect(isActiveField).toBeDefined(); - const isActiveTypes = isActiveField.anyOf?.map((typeEntry) => typeEntry['type']); + const isActiveTypes = isActiveField.anyOf?.map((typeEntry) => (typeEntry as JSONSchema)['type']); expect(isActiveTypes).toContain('boolean'); // Check that the 'description' field is optional (occurs in some documents) - const descriptionField = schema.properties?.['description']; + const descriptionField = schema.properties?.['description'] as JSONSchema | undefined; expect(descriptionField).toBeDefined(); expect(descriptionField?.['x-occurrence']).toBeLessThan(sparseDocumentsArray.length); }); @@ -75,9 +75,9 @@ describe('DocumentDB Schema Analyzer', () => { // Helper function to get the 'x-bsonType' from a field function getBsonType(fieldName: string): string | undefined { - const field = schema.properties?.[fieldName]; + const field = schema.properties?.[fieldName] as JSONSchema | undefined; const anyOf = field?.anyOf; - return anyOf && anyOf[0]?.['x-bsonType']; + return anyOf && (anyOf[0] as JSONSchema | undefined)?.['x-bsonType']; } // Check that specific BSON types are correctly identified @@ -100,8 +100,8 @@ describe('DocumentDB Schema Analyzer', () => { expect(schema.properties).toHaveProperty('jobInfo'); // Access 'personalInfo' properties - const personalInfoAnyOf = schema.properties && schema.properties['personalInfo']?.anyOf; - const personalInfoProperties = personalInfoAnyOf?.[0]?.properties; + const personalInfoAnyOf = schema.properties && (schema.properties['personalInfo'] as JSONSchema | undefined)?.anyOf; + const personalInfoProperties = (personalInfoAnyOf?.[0] as JSONSchema | undefined)?.properties; expect(personalInfoProperties).toBeDefined(); expect(personalInfoProperties).toHaveProperty('name'); expect(personalInfoProperties).toHaveProperty('age'); @@ -109,8 +109,8 @@ describe('DocumentDB Schema Analyzer', () => { expect(personalInfoProperties).toHaveProperty('address'); // Access 'address' properties within 'personalInfo' - const addressAnyOf = personalInfoProperties['address'].anyOf; - const addressProperties = addressAnyOf?.[0]?.properties; + const addressAnyOf = ((personalInfoProperties as JSONSchemaMap)['address'] as JSONSchema).anyOf; + const addressProperties = (addressAnyOf?.[0] as JSONSchema | undefined)?.properties; expect(addressProperties).toBeDefined(); expect(addressProperties).toHaveProperty('street'); expect(addressProperties).toHaveProperty('city'); @@ -130,10 +130,10 @@ describe('DocumentDB Schema Analyzer', () => { // Helper function to get item types from an array field function getArrayItemTypes(fieldName: string): string[] | undefined { - const field = schema.properties?.[fieldName]; + const field = schema.properties?.[fieldName] as JSONSchema | undefined; const anyOf = field?.anyOf; - const itemsAnyOf: JSONSchemaRef[] = anyOf?.[0]?.items?.anyOf; - return itemsAnyOf?.map((typeEntry) => typeEntry['type']); + const itemsAnyOf: JSONSchemaRef[] | undefined = ((anyOf?.[0] as JSONSchema | undefined)?.items as JSONSchema | undefined)?.anyOf; + return itemsAnyOf?.map((typeEntry) => (typeEntry as JSONSchema)['type'] as string); } // Check that 'integersArray' has elements of type 'number' @@ -154,27 +154,29 @@ describe('DocumentDB Schema Analyzer', () => { const schema = analyzer.getSchema(); // Access 'user.profile.hobbies' - const userProfile = schema.properties && schema.properties['user'].anyOf?.[0]?.properties?.['profile']; - const hobbies = userProfile?.anyOf?.[0]?.properties?.['hobbies']; - // eslint-disable-next-line @typescript-eslint/no-unsafe-call - const hobbiesItemTypes = hobbies?.anyOf?.[0]?.items?.anyOf?.map((typeEntry) => typeEntry['type']); + const user = schema.properties?.['user'] as JSONSchema | undefined; + const userProfile = (user?.anyOf?.[0] as JSONSchema | undefined)?.properties?.['profile'] as JSONSchema | undefined; + const hobbies = (userProfile?.anyOf?.[0] as JSONSchema | undefined)?.properties?.['hobbies'] as JSONSchema | undefined; + const hobbiesItems = (hobbies?.anyOf?.[0] as JSONSchema | undefined)?.items as JSONSchema | undefined; + const hobbiesItemTypes = hobbiesItems?.anyOf?.map((typeEntry) => (typeEntry as JSONSchema).type); expect(hobbiesItemTypes).toContain('string'); // Access 'user.profile.addresses' - const addresses = userProfile?.anyOf?.[0]?.properties?.['addresses']; - // eslint-disable-next-line @typescript-eslint/no-unsafe-call - const addressItemTypes = addresses?.anyOf?.[0]?.items?.anyOf?.map((typeEntry) => typeEntry['type']); + const addresses = (userProfile?.anyOf?.[0] as JSONSchema | undefined)?.properties?.['addresses'] as JSONSchema | undefined; + const addressesItems = (addresses?.anyOf?.[0] as JSONSchema | undefined)?.items as JSONSchema | undefined; + const addressItemTypes = addressesItems?.anyOf?.map((typeEntry) => (typeEntry as JSONSchema).type); expect(addressItemTypes).toContain('object'); // Check that 'orders' is an array - const orders = schema.properties && schema.properties['orders']; + const orders = schema.properties?.['orders'] as JSONSchema | undefined; expect(orders).toBeDefined(); - const ordersType = orders.anyOf?.[0]?.type; + const ordersType = (orders?.anyOf?.[0] as JSONSchema | undefined)?.type; expect(ordersType).toBe('array'); // Access 'items' within 'orders' - const orderItems = orders.anyOf?.[0]?.items?.anyOf?.[0]?.properties?.['items']; - const orderItemsType = orderItems?.anyOf?.[0]?.type; + const orderItemsParent = (orders?.anyOf?.[0] as JSONSchema | undefined)?.items as JSONSchema | undefined; + const orderItems = (orderItemsParent?.anyOf?.[0] as JSONSchema | undefined)?.properties?.['items'] as JSONSchema | undefined; + const orderItemsType = (orderItems?.anyOf?.[0] as JSONSchema | undefined)?.type; expect(orderItemsType).toBe('array'); }); @@ -192,16 +194,18 @@ describe('DocumentDB Schema Analyzer', () => { expect(schema.properties).toHaveProperty('user'); // Check that 'integersArray' has correct min and max values - const integersArray = schema.properties && schema.properties['integersArray']; - const integerItemType = integersArray.anyOf?.[0]?.items?.anyOf?.[0]; + const integersArray = schema.properties?.['integersArray'] as JSONSchema | undefined; + const integerItemType = ((integersArray?.anyOf?.[0] as JSONSchema | undefined)?.items as JSONSchema | undefined)?.anyOf?.[0] as JSONSchema | undefined; expect(integerItemType?.['x-minValue']).toBe(1); expect(integerItemType?.['x-maxValue']).toBe(5); // Check that 'orders.items.price' is detected as Decimal128 - const orders = schema.properties && schema.properties['orders']; - const orderItems = orders.anyOf?.[0]?.items?.anyOf?.[0]?.properties?.['items']; - const priceField = orderItems?.anyOf?.[0]?.items?.anyOf?.[0]?.properties?.['price']; - const priceFieldType = priceField?.anyOf?.[0]; + const orders2 = schema.properties?.['orders'] as JSONSchema | undefined; + const orderItemsParent2 = (orders2?.anyOf?.[0] as JSONSchema | undefined)?.items as JSONSchema | undefined; + const orderItems = (orderItemsParent2?.anyOf?.[0] as JSONSchema | undefined)?.properties?.['items'] as JSONSchema | undefined; + const priceFieldParent = ((orderItems?.anyOf?.[0] as JSONSchema | undefined)?.items as JSONSchema | undefined)?.anyOf?.[0] as JSONSchema | undefined; + const priceField = priceFieldParent?.properties?.['price'] as JSONSchema | undefined; + const priceFieldType = priceField?.anyOf?.[0] as JSONSchema | undefined; expect(priceFieldType?.['x-bsonType']).toBe('decimal128'); }); From 99c0234d9d8958eee43a8bea1791121d7da049e0 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Mon, 16 Feb 2026 20:35:58 +0100 Subject: [PATCH 04/25] feat: enhance FieldEntry with bsonType, bsonTypes, isOptional, arrayItemBsonType Group D of SchemaAnalyzer refactor: - D1: Add bsonType to FieldEntry (dominant BSON type from x-bsonType) - D2: Add bsonTypes[] for polymorphic fields (2+ distinct types) - D3: Add isOptional flag (x-occurrence < parent x-documentsInspected) - D4: Add arrayItemBsonType for array fields (dominant element BSON type) - D5: Sort results: _id first, then alphabetical by path - D6: Verified generateMongoFindJsonSchema still works (additive changes) - G4: Add 7 getKnownFields tests covering all new fields --- .../autocomplete/getKnownFields.test.ts | 131 ++++++++++++++++++ .../data-api/autocomplete/getKnownFields.ts | 116 +++++++++++++++- 2 files changed, 241 insertions(+), 6 deletions(-) create mode 100644 src/utils/json/data-api/autocomplete/getKnownFields.test.ts diff --git a/src/utils/json/data-api/autocomplete/getKnownFields.test.ts b/src/utils/json/data-api/autocomplete/getKnownFields.test.ts new file mode 100644 index 000000000..ffa8a0976 --- /dev/null +++ b/src/utils/json/data-api/autocomplete/getKnownFields.test.ts @@ -0,0 +1,131 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ObjectId } from 'bson'; +import { SchemaAnalyzer } from '../SchemaAnalyzer'; +import { getKnownFields, type FieldEntry } from './getKnownFields'; + +describe('getKnownFields', () => { + it('returns bsonType for primitive fields', () => { + const analyzer = SchemaAnalyzer.fromDocument({ + _id: new ObjectId(), + name: 'Alice', + age: 42, + score: 3.14, + active: true, + }); + const fields = getKnownFields(analyzer.getSchema()); + + const nameField = fields.find((f: FieldEntry) => f.path === 'name'); + expect(nameField?.type).toBe('string'); + expect(nameField?.bsonType).toBe('string'); + + const ageField = fields.find((f: FieldEntry) => f.path === 'age'); + expect(ageField?.type).toBe('number'); + // bsonType could be 'double' or 'int32' depending on JS runtime + expect(['double', 'int32']).toContain(ageField?.bsonType); + + const activeField = fields.find((f: FieldEntry) => f.path === 'active'); + expect(activeField?.type).toBe('boolean'); + expect(activeField?.bsonType).toBe('boolean'); + }); + + it('returns _id first and sorts alphabetically', () => { + const analyzer = SchemaAnalyzer.fromDocument({ + _id: new ObjectId(), + zebra: 1, + apple: 2, + mango: 3, + }); + const fields = getKnownFields(analyzer.getSchema()); + const paths = fields.map((f: FieldEntry) => f.path); + + expect(paths[0]).toBe('_id'); + // Remaining should be alphabetical + expect(paths.slice(1)).toEqual(['apple', 'mango', 'zebra']); + }); + + it('detects optional fields', () => { + const analyzer = new SchemaAnalyzer(); + analyzer.addDocument({ _id: new ObjectId(), name: 'Alice', age: 30 }); + analyzer.addDocument({ _id: new ObjectId(), name: 'Bob' }); // no 'age' + + const fields = getKnownFields(analyzer.getSchema()); + + const nameField = fields.find((f: FieldEntry) => f.path === 'name'); + expect(nameField?.isOptional).toBeUndefined(); // present in all docs + + const ageField = fields.find((f: FieldEntry) => f.path === 'age'); + expect(ageField?.isOptional).toBe(true); // missing in doc2 + }); + + it('returns bsonTypes for polymorphic fields', () => { + const analyzer = new SchemaAnalyzer(); + analyzer.addDocument({ _id: new ObjectId(), value: 'hello' }); + analyzer.addDocument({ _id: new ObjectId(), value: 42 }); + + const fields = getKnownFields(analyzer.getSchema()); + const valueField = fields.find((f: FieldEntry) => f.path === 'value'); + + expect(valueField?.bsonTypes).toBeDefined(); + expect(valueField?.bsonTypes).toHaveLength(2); + expect(valueField?.bsonTypes).toContain('string'); + // Could be 'double' or 'int32' + expect(valueField?.bsonTypes?.some((t: string) => ['double', 'int32'].includes(t))).toBe( + true, + ); + }); + + it('returns arrayItemBsonType for array fields', () => { + const analyzer = SchemaAnalyzer.fromDocument({ + _id: new ObjectId(), + tags: ['a', 'b', 'c'], + scores: [10, 20, 30], + }); + const fields = getKnownFields(analyzer.getSchema()); + + const tagsField = fields.find((f: FieldEntry) => f.path === 'tags'); + expect(tagsField?.type).toBe('array'); + expect(tagsField?.bsonType).toBe('array'); + expect(tagsField?.arrayItemBsonType).toBe('string'); + + const scoresField = fields.find((f: FieldEntry) => f.path === 'scores'); + expect(scoresField?.type).toBe('array'); + expect(scoresField?.arrayItemBsonType).toBeDefined(); + }); + + it('handles nested object fields', () => { + const analyzer = SchemaAnalyzer.fromDocument({ + _id: new ObjectId(), + user: { + name: 'Alice', + profile: { + bio: 'hello', + }, + }, + }); + const fields = getKnownFields(analyzer.getSchema()); + const paths = fields.map((f: FieldEntry) => f.path); + + // Objects are expanded, not leaf nodes + expect(paths).not.toContain('user'); + expect(paths).toContain('user.name'); + expect(paths).toContain('user.profile.bio'); + }); + + it('detects optional nested fields', () => { + const analyzer = new SchemaAnalyzer(); + analyzer.addDocument({ _id: new ObjectId(), user: { name: 'Alice', age: 30 } }); + analyzer.addDocument({ _id: new ObjectId(), user: { name: 'Bob' } }); // no age in nested obj + + const fields = getKnownFields(analyzer.getSchema()); + + const nameField = fields.find((f: FieldEntry) => f.path === 'user.name'); + expect(nameField?.isOptional).toBeUndefined(); // present in both objects + + const ageField = fields.find((f: FieldEntry) => f.path === 'user.age'); + expect(ageField?.isOptional).toBe(true); // missing in doc2's user object + }); +}); diff --git a/src/utils/json/data-api/autocomplete/getKnownFields.ts b/src/utils/json/data-api/autocomplete/getKnownFields.ts index a82277a73..811e74224 100644 --- a/src/utils/json/data-api/autocomplete/getKnownFields.ts +++ b/src/utils/json/data-api/autocomplete/getKnownFields.ts @@ -7,8 +7,18 @@ import Denque from 'denque'; import { type JSONSchema } from '../../JSONSchema'; export interface FieldEntry { + /** Dot-notated path (e.g., "user.profile.name") */ path: string; + /** JSON type of the dominant type entry ("string", "number", "object", "array", etc.) */ type: string; + /** Dominant BSON type from x-bsonType on the most common type entry ("date", "objectid", "int32", etc.) */ + bsonType: string; + /** All observed BSON types for this field (for polymorphic fields) */ + bsonTypes?: string[]; + /** true if the field is optional (x-occurrence < parent x-documentsInspected) */ + isOptional?: boolean; + /** If the field is an array, the dominant element BSON type */ + arrayItemBsonType?: string; } /** @@ -30,19 +40,26 @@ export interface FieldEntry { * - Return the result array containing objects with 'path' and 'type' for each leaf property. */ export function getKnownFields(schema: JSONSchema): FieldEntry[] { - const result: Array<{ path: string; type: string }> = []; + const result: FieldEntry[] = []; + type QueueItem = { path: string; schemaNode: JSONSchema; + parentDocumentsInspected: number; }; + const rootDocumentsInspected = (schema['x-documentsInspected'] as number) ?? 0; const queue: Denque = new Denque(); // Initialize the queue with root properties if (schema.properties) { for (const propName of Object.keys(schema.properties)) { const propSchema = schema.properties[propName] as JSONSchema; - queue.push({ path: propName, schemaNode: propSchema }); + queue.push({ + path: propName, + schemaNode: propSchema, + parentDocumentsInspected: rootDocumentsInspected, + }); } } @@ -50,23 +67,66 @@ export function getKnownFields(schema: JSONSchema): FieldEntry[] { const item = queue.shift(); if (!item) continue; - const { path, schemaNode } = item; + const { path, schemaNode, parentDocumentsInspected } = item; const mostCommonTypeEntry = getMostCommonTypeEntry(schemaNode); if (mostCommonTypeEntry) { if (mostCommonTypeEntry.type === 'object' && mostCommonTypeEntry.properties) { // Not a leaf node, enqueue its properties + const objectDocumentsInspected = + (mostCommonTypeEntry['x-documentsInspected'] as number) ?? 0; for (const childName of Object.keys(mostCommonTypeEntry.properties)) { const childSchema = mostCommonTypeEntry.properties[childName] as JSONSchema; - queue.push({ path: `${path}.${childName}`, schemaNode: childSchema }); + queue.push({ + path: `${path}.${childName}`, + schemaNode: childSchema, + parentDocumentsInspected: objectDocumentsInspected, + }); } } else { - // Leaf node, add to result - result.push({ path: path, type: mostCommonTypeEntry.type as string }); + // Leaf node, build the FieldEntry + const bsonType = + (mostCommonTypeEntry['x-bsonType'] as string) ?? + (mostCommonTypeEntry.type as string); + + const entry: FieldEntry = { + path, + type: mostCommonTypeEntry.type as string, + bsonType, + }; + + // bsonTypes: collect all distinct x-bsonType values from anyOf entries + const allBsonTypes = collectBsonTypes(schemaNode); + if (allBsonTypes.length >= 2) { + entry.bsonTypes = allBsonTypes; + } + + // isOptional: compare x-occurrence against parent's x-documentsInspected + const occurrence = (schemaNode['x-occurrence'] as number) ?? 0; + if (parentDocumentsInspected > 0 && occurrence < parentDocumentsInspected) { + entry.isOptional = true; + } + + // arrayItemBsonType: for array fields, find the dominant element type + if (mostCommonTypeEntry.type === 'array') { + const itemBsonType = getDominantArrayItemBsonType(mostCommonTypeEntry); + if (itemBsonType) { + entry.arrayItemBsonType = itemBsonType; + } + } + + result.push(entry); } } } + // Sort: _id first, then alphabetical by path + result.sort((a, b) => { + if (a.path === '_id') return -1; + if (b.path === '_id') return 1; + return a.path.localeCompare(b.path); + }); + return result; } @@ -93,3 +153,47 @@ function getMostCommonTypeEntry(schemaNode: JSONSchema): JSONSchema | null { } return null; } + +/** + * Collects all distinct x-bsonType values from a schema node's anyOf entries. + * Returns them sorted alphabetically for determinism. + */ +function collectBsonTypes(schemaNode: JSONSchema): string[] { + if (!schemaNode.anyOf || schemaNode.anyOf.length === 0) { + return []; + } + + const bsonTypes = new Set(); + for (const entry of schemaNode.anyOf as JSONSchema[]) { + const bsonType = entry['x-bsonType'] as string | undefined; + if (bsonType) { + bsonTypes.add(bsonType); + } + } + + return Array.from(bsonTypes).sort(); +} + +/** + * For an array type entry, finds the dominant element BSON type by looking at + * items.anyOf and selecting the entry with the highest x-typeOccurrence. + */ +function getDominantArrayItemBsonType(arrayTypeEntry: JSONSchema): string | undefined { + const itemsSchema = arrayTypeEntry.items as JSONSchema | undefined; + if (!itemsSchema?.anyOf || itemsSchema.anyOf.length === 0) { + return undefined; + } + + let maxOccurrence = -1; + let dominantBsonType: string | undefined; + + for (const entry of itemsSchema.anyOf as JSONSchema[]) { + const occurrence = (entry['x-typeOccurrence'] as number) ?? 0; + if (occurrence > maxOccurrence) { + maxOccurrence = occurrence; + dominantBsonType = entry['x-bsonType'] as string | undefined; + } + } + + return dominantBsonType; +} From 48914635796ef13c425d53e66a144a9839616539 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Mon, 16 Feb 2026 20:47:38 +0100 Subject: [PATCH 05/25] feat: add transformers (generateDescriptions, toTypeScriptDefinition, toFieldCompletionItems) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Group E of SchemaAnalyzer refactor: - E1: generateDescriptions() — post-processor adding human-readable description strings with type info, occurrence percentage, and min/max stats - E2: toTypeScriptDefinition() — generates TypeScript interface strings from JSONSchema for shell addExtraLib() integration - E3: toFieldCompletionItems() — converts FieldEntry[] to CompletionItemProvider- ready FieldCompletionData[] with insert text escaping and $ references Also: - Rename isOptional → isSparse in FieldEntry and FieldCompletionData (all fields are implicitly optional in MongoDB API / DocumentDB API; isSparse is a statistical observation, not a constraint) - Fix lint errors (inline type specifiers) - 18 new tests for transformers + updated existing tests --- src/utils/json/data-api/BSONTypes.ts | 3 +- .../json/data-api/SchemaAnalyzer.test.ts | 35 ++- .../autocomplete/generateDescriptions.test.ts | 210 ++++++++++++++++ .../autocomplete/generateDescriptions.ts | 219 +++++++++++++++++ .../autocomplete/getKnownFields.test.ts | 12 +- .../data-api/autocomplete/getKnownFields.ts | 21 +- .../toFieldCompletionItems.test.ts | 85 +++++++ .../autocomplete/toFieldCompletionItems.ts | 52 ++++ .../toTypeScriptDefinition.test.ts | 209 ++++++++++++++++ .../autocomplete/toTypeScriptDefinition.ts | 232 ++++++++++++++++++ 10 files changed, 1050 insertions(+), 28 deletions(-) create mode 100644 src/utils/json/data-api/autocomplete/generateDescriptions.test.ts create mode 100644 src/utils/json/data-api/autocomplete/generateDescriptions.ts create mode 100644 src/utils/json/data-api/autocomplete/toFieldCompletionItems.test.ts create mode 100644 src/utils/json/data-api/autocomplete/toFieldCompletionItems.ts create mode 100644 src/utils/json/data-api/autocomplete/toTypeScriptDefinition.test.ts create mode 100644 src/utils/json/data-api/autocomplete/toTypeScriptDefinition.ts diff --git a/src/utils/json/data-api/BSONTypes.ts b/src/utils/json/data-api/BSONTypes.ts index ef52911e7..17c8ff45d 100644 --- a/src/utils/json/data-api/BSONTypes.ts +++ b/src/utils/json/data-api/BSONTypes.ts @@ -177,8 +177,7 @@ export namespace BSONTypes { if (value instanceof DBRef) return BSONTypes.DBRef; if (value instanceof Map) return BSONTypes.Map; if (value instanceof UUID && value.sub_type === Binary.SUBTYPE_UUID) return BSONTypes.UUID; - if (value instanceof UUID && value.sub_type === Binary.SUBTYPE_UUID_OLD) - return BSONTypes.UUID_LEGACY; + if (value instanceof UUID && value.sub_type === Binary.SUBTYPE_UUID_OLD) return BSONTypes.UUID_LEGACY; if (value instanceof Buffer || value instanceof Binary) return BSONTypes.Binary; if (value instanceof RegExp) return BSONTypes.RegExp; if (value instanceof Code) { diff --git a/src/utils/json/data-api/SchemaAnalyzer.test.ts b/src/utils/json/data-api/SchemaAnalyzer.test.ts index 33d2c5156..477c843db 100644 --- a/src/utils/json/data-api/SchemaAnalyzer.test.ts +++ b/src/utils/json/data-api/SchemaAnalyzer.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { type JSONSchema, type JSONSchemaMap, type JSONSchemaRef } from '../JSONSchema'; -import { SchemaAnalyzer, getPropertyNamesAtLevel } from './SchemaAnalyzer'; +import { getPropertyNamesAtLevel, SchemaAnalyzer } from './SchemaAnalyzer'; import { arraysWithDifferentDataTypes, complexDocument, @@ -100,7 +100,8 @@ describe('DocumentDB Schema Analyzer', () => { expect(schema.properties).toHaveProperty('jobInfo'); // Access 'personalInfo' properties - const personalInfoAnyOf = schema.properties && (schema.properties['personalInfo'] as JSONSchema | undefined)?.anyOf; + const personalInfoAnyOf = + schema.properties && (schema.properties['personalInfo'] as JSONSchema | undefined)?.anyOf; const personalInfoProperties = (personalInfoAnyOf?.[0] as JSONSchema | undefined)?.properties; expect(personalInfoProperties).toBeDefined(); expect(personalInfoProperties).toHaveProperty('name'); @@ -132,7 +133,9 @@ describe('DocumentDB Schema Analyzer', () => { function getArrayItemTypes(fieldName: string): string[] | undefined { const field = schema.properties?.[fieldName] as JSONSchema | undefined; const anyOf = field?.anyOf; - const itemsAnyOf: JSONSchemaRef[] | undefined = ((anyOf?.[0] as JSONSchema | undefined)?.items as JSONSchema | undefined)?.anyOf; + const itemsAnyOf: JSONSchemaRef[] | undefined = ( + (anyOf?.[0] as JSONSchema | undefined)?.items as JSONSchema | undefined + )?.anyOf; return itemsAnyOf?.map((typeEntry) => (typeEntry as JSONSchema)['type'] as string); } @@ -155,14 +158,20 @@ describe('DocumentDB Schema Analyzer', () => { // Access 'user.profile.hobbies' const user = schema.properties?.['user'] as JSONSchema | undefined; - const userProfile = (user?.anyOf?.[0] as JSONSchema | undefined)?.properties?.['profile'] as JSONSchema | undefined; - const hobbies = (userProfile?.anyOf?.[0] as JSONSchema | undefined)?.properties?.['hobbies'] as JSONSchema | undefined; + const userProfile = (user?.anyOf?.[0] as JSONSchema | undefined)?.properties?.['profile'] as + | JSONSchema + | undefined; + const hobbies = (userProfile?.anyOf?.[0] as JSONSchema | undefined)?.properties?.['hobbies'] as + | JSONSchema + | undefined; const hobbiesItems = (hobbies?.anyOf?.[0] as JSONSchema | undefined)?.items as JSONSchema | undefined; const hobbiesItemTypes = hobbiesItems?.anyOf?.map((typeEntry) => (typeEntry as JSONSchema).type); expect(hobbiesItemTypes).toContain('string'); // Access 'user.profile.addresses' - const addresses = (userProfile?.anyOf?.[0] as JSONSchema | undefined)?.properties?.['addresses'] as JSONSchema | undefined; + const addresses = (userProfile?.anyOf?.[0] as JSONSchema | undefined)?.properties?.['addresses'] as + | JSONSchema + | undefined; const addressesItems = (addresses?.anyOf?.[0] as JSONSchema | undefined)?.items as JSONSchema | undefined; const addressItemTypes = addressesItems?.anyOf?.map((typeEntry) => (typeEntry as JSONSchema).type); expect(addressItemTypes).toContain('object'); @@ -175,7 +184,9 @@ describe('DocumentDB Schema Analyzer', () => { // Access 'items' within 'orders' const orderItemsParent = (orders?.anyOf?.[0] as JSONSchema | undefined)?.items as JSONSchema | undefined; - const orderItems = (orderItemsParent?.anyOf?.[0] as JSONSchema | undefined)?.properties?.['items'] as JSONSchema | undefined; + const orderItems = (orderItemsParent?.anyOf?.[0] as JSONSchema | undefined)?.properties?.['items'] as + | JSONSchema + | undefined; const orderItemsType = (orderItems?.anyOf?.[0] as JSONSchema | undefined)?.type; expect(orderItemsType).toBe('array'); }); @@ -195,15 +206,19 @@ describe('DocumentDB Schema Analyzer', () => { // Check that 'integersArray' has correct min and max values const integersArray = schema.properties?.['integersArray'] as JSONSchema | undefined; - const integerItemType = ((integersArray?.anyOf?.[0] as JSONSchema | undefined)?.items as JSONSchema | undefined)?.anyOf?.[0] as JSONSchema | undefined; + const integerItemType = ((integersArray?.anyOf?.[0] as JSONSchema | undefined)?.items as JSONSchema | undefined) + ?.anyOf?.[0] as JSONSchema | undefined; expect(integerItemType?.['x-minValue']).toBe(1); expect(integerItemType?.['x-maxValue']).toBe(5); // Check that 'orders.items.price' is detected as Decimal128 const orders2 = schema.properties?.['orders'] as JSONSchema | undefined; const orderItemsParent2 = (orders2?.anyOf?.[0] as JSONSchema | undefined)?.items as JSONSchema | undefined; - const orderItems = (orderItemsParent2?.anyOf?.[0] as JSONSchema | undefined)?.properties?.['items'] as JSONSchema | undefined; - const priceFieldParent = ((orderItems?.anyOf?.[0] as JSONSchema | undefined)?.items as JSONSchema | undefined)?.anyOf?.[0] as JSONSchema | undefined; + const orderItems = (orderItemsParent2?.anyOf?.[0] as JSONSchema | undefined)?.properties?.['items'] as + | JSONSchema + | undefined; + const priceFieldParent = ((orderItems?.anyOf?.[0] as JSONSchema | undefined)?.items as JSONSchema | undefined) + ?.anyOf?.[0] as JSONSchema | undefined; const priceField = priceFieldParent?.properties?.['price'] as JSONSchema | undefined; const priceFieldType = priceField?.anyOf?.[0] as JSONSchema | undefined; expect(priceFieldType?.['x-bsonType']).toBe('decimal128'); diff --git a/src/utils/json/data-api/autocomplete/generateDescriptions.test.ts b/src/utils/json/data-api/autocomplete/generateDescriptions.test.ts new file mode 100644 index 000000000..7500cc95c --- /dev/null +++ b/src/utils/json/data-api/autocomplete/generateDescriptions.test.ts @@ -0,0 +1,210 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { type JSONSchema } from '../../JSONSchema'; +import { generateDescriptions } from './generateDescriptions'; + +describe('generateDescriptions', () => { + it('adds descriptions with type and percentage for simple document', () => { + const schema: JSONSchema = { + 'x-documentsInspected': 100, + properties: { + name: { + 'x-occurrence': 100, + anyOf: [ + { + type: 'string', + 'x-bsonType': 'string', + 'x-typeOccurrence': 100, + }, + ], + }, + }, + }; + + generateDescriptions(schema); + + const nameSchema = schema.properties?.name as JSONSchema; + expect(nameSchema.description).toBe('String · 100%'); + }); + + it('includes min/max stats for numeric fields', () => { + const schema: JSONSchema = { + 'x-documentsInspected': 100, + properties: { + age: { + 'x-occurrence': 95, + anyOf: [ + { + type: 'number', + 'x-bsonType': 'int32', + 'x-typeOccurrence': 95, + 'x-minValue': 18, + 'x-maxValue': 95, + }, + ], + }, + }, + }; + + generateDescriptions(schema); + + const ageSchema = schema.properties?.age as JSONSchema; + expect(ageSchema.description).toBe('Int32 · 95% · range: 18–95'); + }); + + it('includes length stats for string fields', () => { + const schema: JSONSchema = { + 'x-documentsInspected': 100, + properties: { + name: { + 'x-occurrence': 100, + anyOf: [ + { + type: 'string', + 'x-bsonType': 'string', + 'x-typeOccurrence': 100, + 'x-minLength': 3, + 'x-maxLength': 50, + }, + ], + }, + }, + }; + + generateDescriptions(schema); + + const nameSchema = schema.properties?.name as JSONSchema; + expect(nameSchema.description).toBe('String · 100% · length: 3–50'); + }); + + it('includes date range stats for date fields', () => { + const minDate = new Date('2020-01-01T00:00:00.000Z').getTime(); + const maxDate = new Date('2024-12-31T00:00:00.000Z').getTime(); + + const schema: JSONSchema = { + 'x-documentsInspected': 100, + properties: { + createdAt: { + 'x-occurrence': 80, + anyOf: [ + { + type: 'string', + 'x-bsonType': 'date', + 'x-typeOccurrence': 80, + 'x-minDate': minDate, + 'x-maxDate': maxDate, + }, + ], + }, + }, + }; + + generateDescriptions(schema); + + const createdAtSchema = schema.properties?.createdAt as JSONSchema; + expect(createdAtSchema.description).toBe('Date · 80% · range: 2020-01-01 – 2024-12-31'); + }); + + it('includes true/false counts for boolean fields', () => { + const schema: JSONSchema = { + 'x-documentsInspected': 100, + properties: { + active: { + 'x-occurrence': 100, + anyOf: [ + { + type: 'boolean', + 'x-bsonType': 'boolean', + 'x-typeOccurrence': 100, + 'x-trueCount': 80, + 'x-falseCount': 20, + }, + ], + }, + }, + }; + + generateDescriptions(schema); + + const activeSchema = schema.properties?.active as JSONSchema; + expect(activeSchema.description).toBe('Boolean · 100% · true: 80, false: 20'); + }); + + it('handles nested object fields (descriptions at nested level)', () => { + const schema: JSONSchema = { + 'x-documentsInspected': 100, + properties: { + address: { + 'x-occurrence': 100, + anyOf: [ + { + type: 'object', + 'x-bsonType': 'object', + 'x-typeOccurrence': 100, + 'x-documentsInspected': 100, + properties: { + city: { + 'x-occurrence': 100, + anyOf: [ + { + type: 'string', + 'x-bsonType': 'string', + 'x-typeOccurrence': 100, + 'x-minLength': 2, + 'x-maxLength': 30, + }, + ], + }, + }, + }, + ], + }, + }, + }; + + generateDescriptions(schema); + + // The parent (address) should also get a description + const addressSchema = schema.properties?.address as JSONSchema; + expect(addressSchema.description).toBe('Object · 100%'); + + // The nested city should get its own description + const addressTypeEntry = (addressSchema.anyOf as JSONSchema[])[0]; + const citySchema = addressTypeEntry.properties?.city as JSONSchema; + expect(citySchema.description).toBe('String · 100% · length: 2–30'); + }); + + it('handles polymorphic fields (shows multiple types)', () => { + const schema: JSONSchema = { + 'x-documentsInspected': 100, + properties: { + value: { + 'x-occurrence': 95, + anyOf: [ + { + type: 'number', + 'x-bsonType': 'int32', + 'x-typeOccurrence': 60, + 'x-minValue': 1, + 'x-maxValue': 100, + }, + { + type: 'string', + 'x-bsonType': 'string', + 'x-typeOccurrence': 35, + }, + ], + }, + }, + }; + + generateDescriptions(schema); + + const valueSchema = schema.properties?.value as JSONSchema; + // Dominant type first, then secondary + expect(valueSchema.description).toBe('Int32 | String · 95% · range: 1–100'); + }); +}); diff --git a/src/utils/json/data-api/autocomplete/generateDescriptions.ts b/src/utils/json/data-api/autocomplete/generateDescriptions.ts new file mode 100644 index 000000000..261549bc2 --- /dev/null +++ b/src/utils/json/data-api/autocomplete/generateDescriptions.ts @@ -0,0 +1,219 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import Denque from 'denque'; +import { type JSONSchema } from '../../JSONSchema'; +import { BSONTypes } from '../BSONTypes'; + +/** + * Work item for BFS traversal of the schema tree. + */ +interface WorkItem { + schemaNode: JSONSchema; + parentDocumentsInspected: number; +} + +/** + * Post-processor that mutates the schema in-place, adding human-readable + * `description` strings to each property node. Descriptions include: + * - Dominant type name(s) + * - Occurrence percentage (based on `x-occurrence / parentDocumentsInspected`) + * - Type-specific stats (length, range, true/false counts, etc.) + * + * Uses BFS to traverse all property levels. + */ +export function generateDescriptions(schema: JSONSchema): void { + const rootDocumentsInspected = (schema['x-documentsInspected'] as number) ?? 0; + + const queue = new Denque(); + + // Seed the queue with root-level properties + if (schema.properties) { + for (const propName of Object.keys(schema.properties)) { + const propSchema = schema.properties[propName] as JSONSchema; + if (typeof propSchema === 'boolean') continue; + + queue.push({ + schemaNode: propSchema, + parentDocumentsInspected: rootDocumentsInspected, + }); + } + } + + while (queue.length > 0) { + const item = queue.shift(); + if (!item) continue; + + const { schemaNode, parentDocumentsInspected } = item; + + // Collect type display names from anyOf entries + const typeNames = collectTypeDisplayNames(schemaNode); + + // Build description parts + const parts: string[] = []; + + // Part 1: Type info + if (typeNames.length > 0) { + parts.push(typeNames.join(' | ')); + } + + // Part 2: Occurrence percentage + if (parentDocumentsInspected > 0) { + const occurrence = (schemaNode['x-occurrence'] as number) ?? 0; + const percentage = ((occurrence / parentDocumentsInspected) * 100).toFixed(0); + parts.push(`${percentage}%`); + } + + // Part 3: Stats from the dominant type entry + const dominantEntry = getDominantTypeEntry(schemaNode); + if (dominantEntry) { + const statString = getStatString(dominantEntry); + if (statString) { + parts.push(statString); + } + + // If the dominant entry is an object with properties, enqueue children + if (dominantEntry.type === 'object' && dominantEntry.properties) { + const objectDocumentsInspected = (dominantEntry['x-documentsInspected'] as number) ?? 0; + for (const childName of Object.keys(dominantEntry.properties)) { + const childSchema = dominantEntry.properties[childName] as JSONSchema; + if (typeof childSchema === 'boolean') continue; + + queue.push({ + schemaNode: childSchema, + parentDocumentsInspected: objectDocumentsInspected, + }); + } + } + } + + // Set the description + if (parts.length > 0) { + schemaNode.description = parts.join(' · '); + } + } +} + +/** + * Collects display names for all types in a schema node's `anyOf` entries. + * Returns them ordered by descending `x-typeOccurrence`. + */ +function collectTypeDisplayNames(schemaNode: JSONSchema): string[] { + if (!schemaNode.anyOf || schemaNode.anyOf.length === 0) { + return []; + } + + const entries: Array<{ name: string; occurrence: number }> = []; + for (const entry of schemaNode.anyOf) { + if (typeof entry === 'boolean') continue; + const bsonType = (entry['x-bsonType'] as string) ?? ''; + const occurrence = (entry['x-typeOccurrence'] as number) ?? 0; + const name = bsonType + ? BSONTypes.toDisplayString(bsonType as BSONTypes) + : ((entry.type as string) ?? 'Unknown'); + entries.push({ name, occurrence }); + } + + // Sort by occurrence descending so dominant type comes first + entries.sort((a, b) => b.occurrence - a.occurrence); + return entries.map((e) => e.name); +} + +/** + * Returns the anyOf entry with the highest `x-typeOccurrence`. + */ +function getDominantTypeEntry(schemaNode: JSONSchema): JSONSchema | null { + if (!schemaNode.anyOf || schemaNode.anyOf.length === 0) { + return null; + } + + let maxOccurrence = -1; + let dominant: JSONSchema | null = null; + + for (const entry of schemaNode.anyOf) { + if (typeof entry === 'boolean') continue; + const occurrence = (entry['x-typeOccurrence'] as number) ?? 0; + if (occurrence > maxOccurrence) { + maxOccurrence = occurrence; + dominant = entry; + } + } + + return dominant; +} + +/** + * Returns a type-specific stats string for the given type entry, or undefined if + * no relevant stats are available. + */ +function getStatString(typeEntry: JSONSchema): string | undefined { + const bsonType = (typeEntry['x-bsonType'] as string) ?? ''; + + switch (bsonType) { + case 'string': + case 'binary': { + const minLen = typeEntry['x-minLength'] as number | undefined; + const maxLen = typeEntry['x-maxLength'] as number | undefined; + if (minLen !== undefined && maxLen !== undefined) { + return `length: ${String(minLen)}–${String(maxLen)}`; + } + return undefined; + } + + case 'int32': + case 'double': + case 'long': + case 'decimal128': + case 'number': { + const minVal = typeEntry['x-minValue'] as number | undefined; + const maxVal = typeEntry['x-maxValue'] as number | undefined; + if (minVal !== undefined && maxVal !== undefined) { + return `range: ${String(minVal)}–${String(maxVal)}`; + } + return undefined; + } + + case 'date': { + const minDate = typeEntry['x-minDate'] as number | undefined; + const maxDate = typeEntry['x-maxDate'] as number | undefined; + if (minDate !== undefined && maxDate !== undefined) { + const minISO = new Date(minDate).toISOString().split('T')[0]; + const maxISO = new Date(maxDate).toISOString().split('T')[0]; + return `range: ${minISO} – ${maxISO}`; + } + return undefined; + } + + case 'boolean': { + const trueCount = typeEntry['x-trueCount'] as number | undefined; + const falseCount = typeEntry['x-falseCount'] as number | undefined; + if (trueCount !== undefined && falseCount !== undefined) { + return `true: ${String(trueCount)}, false: ${String(falseCount)}`; + } + return undefined; + } + + case 'array': { + const minItems = typeEntry['x-minItems'] as number | undefined; + const maxItems = typeEntry['x-maxItems'] as number | undefined; + if (minItems !== undefined && maxItems !== undefined) { + return `items: ${String(minItems)}–${String(maxItems)}`; + } + return undefined; + } + + case 'object': { + const minProps = typeEntry['x-minProperties'] as number | undefined; + const maxProps = typeEntry['x-maxProperties'] as number | undefined; + if (minProps !== undefined && maxProps !== undefined) { + return `properties: ${String(minProps)}–${String(maxProps)}`; + } + return undefined; + } + + default: + return undefined; + } +} diff --git a/src/utils/json/data-api/autocomplete/getKnownFields.test.ts b/src/utils/json/data-api/autocomplete/getKnownFields.test.ts index ffa8a0976..351dab7ff 100644 --- a/src/utils/json/data-api/autocomplete/getKnownFields.test.ts +++ b/src/utils/json/data-api/autocomplete/getKnownFields.test.ts @@ -55,10 +55,10 @@ describe('getKnownFields', () => { const fields = getKnownFields(analyzer.getSchema()); const nameField = fields.find((f: FieldEntry) => f.path === 'name'); - expect(nameField?.isOptional).toBeUndefined(); // present in all docs + expect(nameField?.isSparse).toBeUndefined(); // present in all docs const ageField = fields.find((f: FieldEntry) => f.path === 'age'); - expect(ageField?.isOptional).toBe(true); // missing in doc2 + expect(ageField?.isSparse).toBe(true); // missing in doc2 }); it('returns bsonTypes for polymorphic fields', () => { @@ -73,9 +73,7 @@ describe('getKnownFields', () => { expect(valueField?.bsonTypes).toHaveLength(2); expect(valueField?.bsonTypes).toContain('string'); // Could be 'double' or 'int32' - expect(valueField?.bsonTypes?.some((t: string) => ['double', 'int32'].includes(t))).toBe( - true, - ); + expect(valueField?.bsonTypes?.some((t: string) => ['double', 'int32'].includes(t))).toBe(true); }); it('returns arrayItemBsonType for array fields', () => { @@ -123,9 +121,9 @@ describe('getKnownFields', () => { const fields = getKnownFields(analyzer.getSchema()); const nameField = fields.find((f: FieldEntry) => f.path === 'user.name'); - expect(nameField?.isOptional).toBeUndefined(); // present in both objects + expect(nameField?.isSparse).toBeUndefined(); // present in both objects const ageField = fields.find((f: FieldEntry) => f.path === 'user.age'); - expect(ageField?.isOptional).toBe(true); // missing in doc2's user object + expect(ageField?.isSparse).toBe(true); // missing in doc2's user object }); }); diff --git a/src/utils/json/data-api/autocomplete/getKnownFields.ts b/src/utils/json/data-api/autocomplete/getKnownFields.ts index 811e74224..006c57f94 100644 --- a/src/utils/json/data-api/autocomplete/getKnownFields.ts +++ b/src/utils/json/data-api/autocomplete/getKnownFields.ts @@ -15,8 +15,14 @@ export interface FieldEntry { bsonType: string; /** All observed BSON types for this field (for polymorphic fields) */ bsonTypes?: string[]; - /** true if the field is optional (x-occurrence < parent x-documentsInspected) */ - isOptional?: boolean; + /** + * True if this field was not present in every inspected document + * (x-occurrence < parent x-documentsInspected). + * + * This is a statistical observation, not a schema constraint — in the + * MongoDB API / DocumentDB API all fields are implicitly optional. + */ + isSparse?: boolean; /** If the field is an array, the dominant element BSON type */ arrayItemBsonType?: string; } @@ -73,8 +79,7 @@ export function getKnownFields(schema: JSONSchema): FieldEntry[] { if (mostCommonTypeEntry) { if (mostCommonTypeEntry.type === 'object' && mostCommonTypeEntry.properties) { // Not a leaf node, enqueue its properties - const objectDocumentsInspected = - (mostCommonTypeEntry['x-documentsInspected'] as number) ?? 0; + const objectDocumentsInspected = (mostCommonTypeEntry['x-documentsInspected'] as number) ?? 0; for (const childName of Object.keys(mostCommonTypeEntry.properties)) { const childSchema = mostCommonTypeEntry.properties[childName] as JSONSchema; queue.push({ @@ -85,9 +90,7 @@ export function getKnownFields(schema: JSONSchema): FieldEntry[] { } } else { // Leaf node, build the FieldEntry - const bsonType = - (mostCommonTypeEntry['x-bsonType'] as string) ?? - (mostCommonTypeEntry.type as string); + const bsonType = (mostCommonTypeEntry['x-bsonType'] as string) ?? (mostCommonTypeEntry.type as string); const entry: FieldEntry = { path, @@ -101,10 +104,10 @@ export function getKnownFields(schema: JSONSchema): FieldEntry[] { entry.bsonTypes = allBsonTypes; } - // isOptional: compare x-occurrence against parent's x-documentsInspected + // isSparse: field was not observed in every document const occurrence = (schemaNode['x-occurrence'] as number) ?? 0; if (parentDocumentsInspected > 0 && occurrence < parentDocumentsInspected) { - entry.isOptional = true; + entry.isSparse = true; } // arrayItemBsonType: for array fields, find the dominant element type diff --git a/src/utils/json/data-api/autocomplete/toFieldCompletionItems.test.ts b/src/utils/json/data-api/autocomplete/toFieldCompletionItems.test.ts new file mode 100644 index 000000000..307b0c9d5 --- /dev/null +++ b/src/utils/json/data-api/autocomplete/toFieldCompletionItems.test.ts @@ -0,0 +1,85 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { type FieldEntry } from './getKnownFields'; +import { toFieldCompletionItems } from './toFieldCompletionItems'; + +describe('toFieldCompletionItems', () => { + it('converts simple fields', () => { + const fields: FieldEntry[] = [ + { path: 'name', type: 'string', bsonType: 'string' }, + { path: 'age', type: 'number', bsonType: 'int32' }, + ]; + + const result = toFieldCompletionItems(fields); + + expect(result).toHaveLength(2); + expect(result[0].fieldName).toBe('name'); + expect(result[0].displayType).toBe('String'); + expect(result[0].bsonType).toBe('string'); + expect(result[0].insertText).toBe('name'); + + expect(result[1].fieldName).toBe('age'); + expect(result[1].displayType).toBe('Int32'); + expect(result[1].bsonType).toBe('int32'); + expect(result[1].insertText).toBe('age'); + }); + + it('escapes dotted paths in insertText', () => { + const fields: FieldEntry[] = [ + { path: 'address.city', type: 'string', bsonType: 'string' }, + { path: 'user.profile.bio', type: 'string', bsonType: 'string' }, + ]; + + const result = toFieldCompletionItems(fields); + + expect(result[0].insertText).toBe('"address.city"'); + expect(result[1].insertText).toBe('"user.profile.bio"'); + }); + + it('adds $ prefix to referenceText', () => { + const fields: FieldEntry[] = [ + { path: 'age', type: 'number', bsonType: 'int32' }, + { path: 'address.city', type: 'string', bsonType: 'string' }, + ]; + + const result = toFieldCompletionItems(fields); + + expect(result[0].referenceText).toBe('$age'); + expect(result[1].referenceText).toBe('$address.city'); + }); + + it('preserves isSparse', () => { + const fields: FieldEntry[] = [ + { path: 'name', type: 'string', bsonType: 'string', isSparse: false }, + { path: 'nickname', type: 'string', bsonType: 'string', isSparse: true }, + { path: 'email', type: 'string', bsonType: 'string' }, // undefined → false + ]; + + const result = toFieldCompletionItems(fields); + + expect(result[0].isSparse).toBe(false); + expect(result[1].isSparse).toBe(true); + expect(result[2].isSparse).toBe(false); + }); + + it('uses correct displayType', () => { + const fields: FieldEntry[] = [ + { path: '_id', type: 'string', bsonType: 'objectid' }, + { path: 'createdAt', type: 'string', bsonType: 'date' }, + { path: 'active', type: 'boolean', bsonType: 'boolean' }, + { path: 'score', type: 'number', bsonType: 'double' }, + { path: 'tags', type: 'array', bsonType: 'array' }, + ]; + + const result = toFieldCompletionItems(fields); + + expect(result[0].displayType).toBe('ObjectId'); + expect(result[1].displayType).toBe('Date'); + expect(result[2].displayType).toBe('Boolean'); + expect(result[3].displayType).toBe('Double'); + expect(result[4].displayType).toBe('Array'); + }); +}); diff --git a/src/utils/json/data-api/autocomplete/toFieldCompletionItems.ts b/src/utils/json/data-api/autocomplete/toFieldCompletionItems.ts new file mode 100644 index 000000000..af68908a2 --- /dev/null +++ b/src/utils/json/data-api/autocomplete/toFieldCompletionItems.ts @@ -0,0 +1,52 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { BSONTypes } from '../BSONTypes'; +import { type FieldEntry } from './getKnownFields'; + +/** + * Completion-ready data for a single field entry. + */ +export interface FieldCompletionData { + /** The full dot-notated field name, e.g., "address.city" */ + fieldName: string; + /** Human-readable type display, e.g., "String", "Date", "ObjectId" */ + displayType: string; + /** Raw BSON type from FieldEntry */ + bsonType: string; + /** Whether the field was not present in every inspected document (statistical observation, not a constraint) */ + isSparse: boolean; + /** Text to insert — escaped if field name contains dots or special chars */ + insertText: string; + /** Field reference for aggregation expressions, e.g., "$age", "$address.city" */ + referenceText: string; +} + +/** + * Characters that require quoting in a field name for insert text. + */ +const SPECIAL_CHARS_PATTERN = /[.$\s]/; + +/** + * Converts an array of FieldEntry objects into completion-ready FieldCompletionData items. + * + * @param fields - Array of FieldEntry objects from getKnownFields + * @returns Array of FieldCompletionData ready for use in editor completions + */ +export function toFieldCompletionItems(fields: FieldEntry[]): FieldCompletionData[] { + return fields.map((entry) => { + const displayType = BSONTypes.toDisplayString(entry.bsonType as BSONTypes); + const needsQuoting = SPECIAL_CHARS_PATTERN.test(entry.path); + + return { + fieldName: entry.path, + displayType, + bsonType: entry.bsonType, + isSparse: entry.isSparse ?? false, + insertText: needsQuoting ? `"${entry.path}"` : entry.path, + referenceText: `$${entry.path}`, + }; + }); +} diff --git a/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.test.ts b/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.test.ts new file mode 100644 index 000000000..00a112536 --- /dev/null +++ b/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.test.ts @@ -0,0 +1,209 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { type JSONSchema } from '../../JSONSchema'; +import { toTypeScriptDefinition } from './toTypeScriptDefinition'; + +describe('toTypeScriptDefinition', () => { + it('generates basic interface with primitive types', () => { + const schema: JSONSchema = { + 'x-documentsInspected': 100, + properties: { + _id: { + 'x-occurrence': 100, + anyOf: [ + { + type: 'string', + 'x-bsonType': 'objectid', + 'x-typeOccurrence': 100, + }, + ], + }, + name: { + 'x-occurrence': 100, + anyOf: [ + { + type: 'string', + 'x-bsonType': 'string', + 'x-typeOccurrence': 100, + }, + ], + }, + age: { + 'x-occurrence': 100, + anyOf: [ + { + type: 'number', + 'x-bsonType': 'int32', + 'x-typeOccurrence': 100, + }, + ], + }, + }, + }; + + const result = toTypeScriptDefinition(schema, 'users'); + + expect(result).toContain('interface UsersDocument {'); + expect(result).toContain(' _id: ObjectId;'); + expect(result).toContain(' name: string;'); + expect(result).toContain(' age: number;'); + expect(result).toContain('}'); + }); + + it('marks optional fields with ?', () => { + const schema: JSONSchema = { + 'x-documentsInspected': 100, + properties: { + name: { + 'x-occurrence': 100, + anyOf: [ + { + type: 'string', + 'x-bsonType': 'string', + 'x-typeOccurrence': 100, + }, + ], + }, + nickname: { + 'x-occurrence': 50, + anyOf: [ + { + type: 'string', + 'x-bsonType': 'string', + 'x-typeOccurrence': 50, + }, + ], + }, + }, + }; + + const result = toTypeScriptDefinition(schema, 'users'); + + expect(result).toContain(' name: string;'); + expect(result).toContain(' nickname?: string;'); + }); + + it('handles nested objects as inline blocks', () => { + const schema: JSONSchema = { + 'x-documentsInspected': 100, + properties: { + address: { + 'x-occurrence': 100, + anyOf: [ + { + type: 'object', + 'x-bsonType': 'object', + 'x-typeOccurrence': 100, + 'x-documentsInspected': 100, + properties: { + city: { + 'x-occurrence': 100, + anyOf: [ + { + type: 'string', + 'x-bsonType': 'string', + 'x-typeOccurrence': 100, + }, + ], + }, + zip: { + 'x-occurrence': 100, + anyOf: [ + { + type: 'string', + 'x-bsonType': 'string', + 'x-typeOccurrence': 100, + }, + ], + }, + }, + }, + ], + }, + }, + }; + + const result = toTypeScriptDefinition(schema, 'users'); + + expect(result).toContain(' address: {'); + expect(result).toContain(' city: string;'); + expect(result).toContain(' zip: string;'); + expect(result).toContain(' };'); + }); + + it('handles arrays with element types', () => { + const schema: JSONSchema = { + 'x-documentsInspected': 100, + properties: { + tags: { + 'x-occurrence': 100, + anyOf: [ + { + type: 'array', + 'x-bsonType': 'array', + 'x-typeOccurrence': 100, + items: { + anyOf: [ + { + type: 'string', + 'x-bsonType': 'string', + 'x-typeOccurrence': 100, + }, + ], + }, + }, + ], + }, + }, + }; + + const result = toTypeScriptDefinition(schema, 'posts'); + + expect(result).toContain(' tags: string[];'); + }); + + it('handles polymorphic fields as unions', () => { + const schema: JSONSchema = { + 'x-documentsInspected': 100, + properties: { + metadata: { + 'x-occurrence': 80, + anyOf: [ + { + type: 'string', + 'x-bsonType': 'string', + 'x-typeOccurrence': 50, + }, + { + type: 'number', + 'x-bsonType': 'int32', + 'x-typeOccurrence': 20, + }, + { + type: 'null', + 'x-bsonType': 'null', + 'x-typeOccurrence': 10, + }, + ], + }, + }, + }; + + const result = toTypeScriptDefinition(schema, 'items'); + + expect(result).toContain(' metadata?: string | number | null;'); + }); + + it('PascalCase conversion for collection name', () => { + expect(toTypeScriptDefinition({ 'x-documentsInspected': 0 }, 'users')).toContain('interface UsersDocument'); + expect(toTypeScriptDefinition({ 'x-documentsInspected': 0 }, 'order_items')).toContain( + 'interface OrderItemsDocument', + ); + expect(toTypeScriptDefinition({ 'x-documentsInspected': 0 }, 'my-awesome-collection')).toContain( + 'interface MyAwesomeCollectionDocument', + ); + }); +}); diff --git a/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.ts b/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.ts new file mode 100644 index 000000000..92bef8a9c --- /dev/null +++ b/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.ts @@ -0,0 +1,232 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { type JSONSchema } from '../../JSONSchema'; +import { BSONTypes } from '../BSONTypes'; + +/** + * Maps a BSON type string to the corresponding TypeScript type representation. + */ +const bsonToTypeScriptMap: Record = { + [BSONTypes.String]: 'string', + [BSONTypes.Int32]: 'number', + [BSONTypes.Double]: 'number', + [BSONTypes.Long]: 'number', + [BSONTypes.Decimal128]: 'number', + [BSONTypes.Number]: 'number', + [BSONTypes.Boolean]: 'boolean', + [BSONTypes.Date]: 'Date', + [BSONTypes.ObjectId]: 'ObjectId', + [BSONTypes.Null]: 'null', + [BSONTypes.Undefined]: 'undefined', + [BSONTypes.Binary]: 'Binary', + [BSONTypes.RegExp]: 'RegExp', + [BSONTypes.UUID]: 'UUID', + [BSONTypes.UUID_LEGACY]: 'UUID', + [BSONTypes.Timestamp]: 'Timestamp', + [BSONTypes.MinKey]: 'MinKey', + [BSONTypes.MaxKey]: 'MaxKey', + [BSONTypes.Code]: 'Code', + [BSONTypes.CodeWithScope]: 'Code', + [BSONTypes.DBRef]: 'DBRef', + [BSONTypes.Map]: 'Map', + [BSONTypes.Symbol]: 'symbol', +}; + +/** + * Converts a BSON type string to a TypeScript type string. + */ +function bsonTypeToTS(bsonType: string): string { + return bsonToTypeScriptMap[bsonType] ?? 'unknown'; +} + +/** + * Converts a collection name to PascalCase and appends "Document". + * Examples: + * - "users" → "UsersDocument" + * - "order_items" → "OrderItemsDocument" + */ +function toInterfaceName(collectionName: string): string { + const pascal = collectionName + .split(/[_\-\s]+/) + .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)) + .join(''); + return `${pascal}Document`; +} + +/** + * Generates a TypeScript interface definition string from a JSONSchema + * produced by the SchemaAnalyzer. + * + * @param schema - The JSON Schema with x- extensions from SchemaAnalyzer + * @param collectionName - The MongoDB collection name, used to derive the interface name + * @returns A formatted TypeScript interface definition string + */ +export function toTypeScriptDefinition(schema: JSONSchema, collectionName: string): string { + const interfaceName = toInterfaceName(collectionName); + const rootDocumentsInspected = (schema['x-documentsInspected'] as number) ?? 0; + + const lines: string[] = []; + lines.push(`interface ${interfaceName} {`); + + if (schema.properties) { + renderProperties(schema.properties, rootDocumentsInspected, 1, lines); + } + + lines.push('}'); + return lines.join('\n'); +} + +/** + * Renders property lines for a set of JSON Schema properties at a given indent level. + */ +function renderProperties( + properties: Record, + parentDocumentsInspected: number, + indentLevel: number, + lines: string[], +): void { + const indent = ' '.repeat(indentLevel); + + for (const [propName, propSchema] of Object.entries(properties)) { + if (typeof propSchema === 'boolean') continue; + + const isOptional = isFieldOptional(propSchema, parentDocumentsInspected); + const optionalMarker = isOptional ? '?' : ''; + const tsType = resolveTypeString(propSchema, indentLevel); + + lines.push(`${indent}${propName}${optionalMarker}: ${tsType};`); + } +} + +/** + * Returns true if the field's occurrence is less than the parent's document count. + */ +function isFieldOptional(schemaNode: JSONSchema, parentDocumentsInspected: number): boolean { + const occurrence = (schemaNode['x-occurrence'] as number) ?? 0; + return parentDocumentsInspected > 0 && occurrence < parentDocumentsInspected; +} + +/** + * Resolves a full TypeScript type string for a schema node by examining its + * `anyOf` entries. Handles primitives, objects (inline blocks), and arrays. + */ +function resolveTypeString(schemaNode: JSONSchema, indentLevel: number): string { + if (!schemaNode.anyOf || schemaNode.anyOf.length === 0) { + return 'unknown'; + } + + const typeStrings: string[] = []; + + for (const entry of schemaNode.anyOf) { + if (typeof entry === 'boolean') continue; + const ts = singleEntryToTS(entry, indentLevel); + if (ts && !typeStrings.includes(ts)) { + typeStrings.push(ts); + } + } + + if (typeStrings.length === 0) { + return 'unknown'; + } + + return typeStrings.join(' | '); +} + +/** + * Converts a single `anyOf` type entry to a TypeScript type string. + */ +function singleEntryToTS(entry: JSONSchema, indentLevel: number): string { + const bsonType = (entry['x-bsonType'] as string) ?? ''; + + // Object with nested properties → inline block + if (entry.type === 'object' && entry.properties) { + return renderInlineObject(entry, indentLevel); + } + + // Array → determine element types + if (entry.type === 'array' || bsonType === (BSONTypes.Array as string)) { + return renderArrayType(entry, indentLevel); + } + + // Primitive or mapped type + if (bsonType) { + return bsonTypeToTS(bsonType); + } + + // Fallback to JSON type + const jsonType = entry.type as string | undefined; + if (jsonType) { + return jsonType; + } + + return 'unknown'; +} + +/** + * Renders an inline object type `{ field: type; ... }`. + */ +function renderInlineObject(entry: JSONSchema, indentLevel: number): string { + const lines: string[] = []; + const objectDocumentsInspected = (entry['x-documentsInspected'] as number) ?? 0; + + lines.push('{'); + + if (entry.properties) { + renderProperties(entry.properties, objectDocumentsInspected, indentLevel + 1, lines); + } + + const closingIndent = ' '.repeat(indentLevel); + lines.push(`${closingIndent}}`); + + return lines.join('\n'); +} + +/** + * Renders an array type, e.g., `string[]` or `(string | number)[]`. + */ +function renderArrayType(entry: JSONSchema, indentLevel: number): string { + const itemsSchema = entry.items; + + if (!itemsSchema || typeof itemsSchema === 'boolean') { + return 'unknown[]'; + } + + // Items specified as a single schema (not an array of schemas) + if (!Array.isArray(itemsSchema)) { + const itemSchema = itemsSchema as JSONSchema; + + if (itemSchema.anyOf && itemSchema.anyOf.length > 0) { + const elementTypes: string[] = []; + for (const itemEntry of itemSchema.anyOf) { + if (typeof itemEntry === 'boolean') continue; + const ts = singleEntryToTS(itemEntry, indentLevel); + if (ts && !elementTypes.includes(ts)) { + elementTypes.push(ts); + } + } + + if (elementTypes.length === 0) { + return 'unknown[]'; + } + + if (elementTypes.length === 1) { + return `${elementTypes[0]}[]`; + } + + return `(${elementTypes.join(' | ')})[]`; + } + + // Single item type without anyOf + const bsonType = (itemSchema['x-bsonType'] as string) ?? ''; + if (bsonType) { + return `${bsonTypeToTS(bsonType)}[]`; + } + + return 'unknown[]'; + } + + return 'unknown[]'; +} From 7a54b8c7cc378010999a8c2028e8ce8f8a9a4c58 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Mon, 16 Feb 2026 21:00:40 +0100 Subject: [PATCH 06/25] test: add SchemaAnalyzer class method tests + update plan checklist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add 5 tests for clone(), reset(), fromDocument(), fromDocuments(), addDocuments() - Mark all checklist items A-G as complete, F1-F2 as deferred - Add Manual Test Plan section (§14) with 5 end-to-end test scenarios - Document clone() limitation with BSON Binary types (structuredClone) --- .../json/data-api/SchemaAnalyzer.test.ts | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/src/utils/json/data-api/SchemaAnalyzer.test.ts b/src/utils/json/data-api/SchemaAnalyzer.test.ts index 477c843db..fe4d83629 100644 --- a/src/utils/json/data-api/SchemaAnalyzer.test.ts +++ b/src/utils/json/data-api/SchemaAnalyzer.test.ts @@ -272,4 +272,79 @@ describe('DocumentDB Schema Analyzer', () => { expect(propertiesAtRoot).toHaveLength(6); }); }); + + describe('SchemaAnalyzer class methods', () => { + it('clone() creates an independent deep copy', () => { + // Use embeddedDocumentOnly (plain JS types) to avoid structuredClone issues with BSON types + const original = SchemaAnalyzer.fromDocument(embeddedDocumentOnly); + const cloned = original.clone(); + + // Clone has the same document count + expect(cloned.getDocumentCount()).toBe(1); + + // Clone has the same properties + const originalProps = Object.keys(original.getSchema().properties || {}); + const clonedProps = Object.keys(cloned.getSchema().properties || {}); + expect(clonedProps).toEqual(originalProps); + + // Add another document to the original only + original.addDocument(arraysWithDifferentDataTypes); + expect(original.getDocumentCount()).toBe(2); + expect(cloned.getDocumentCount()).toBe(1); + + // Clone's schema was NOT affected by the mutation + const originalPropsAfter = Object.keys(original.getSchema().properties || {}); + const clonedPropsAfter = Object.keys(cloned.getSchema().properties || {}); + expect(originalPropsAfter).toContain('integersArray'); + expect(originalPropsAfter).toContain('stringsArray'); + expect(clonedPropsAfter).not.toContain('integersArray'); + expect(clonedPropsAfter).not.toContain('stringsArray'); + }); + + it('reset() clears all accumulated state', () => { + const analyzer = SchemaAnalyzer.fromDocument(flatDocument); + expect(analyzer.getDocumentCount()).toBeGreaterThan(0); + expect(Object.keys(analyzer.getSchema().properties || {})).not.toHaveLength(0); + + analyzer.reset(); + + expect(analyzer.getDocumentCount()).toBe(0); + const schema = analyzer.getSchema(); + expect(schema.properties).toBeUndefined(); + expect(schema['x-documentsInspected']).toBeUndefined(); + }); + + it('fromDocument() creates analyzer with single document', () => { + const analyzer = SchemaAnalyzer.fromDocument(flatDocument); + expect(analyzer.getDocumentCount()).toBe(1); + + const schema = analyzer.getSchema(); + const expectedFields = Object.keys(flatDocument); + expect(Object.keys(schema.properties || {})).toEqual(expect.arrayContaining(expectedFields)); + }); + + it('fromDocuments() creates analyzer with multiple documents', () => { + const analyzer = SchemaAnalyzer.fromDocuments(sparseDocumentsArray); + expect(analyzer.getDocumentCount()).toBe(sparseDocumentsArray.length); + + // Compare with manually-built analyzer + const manual = new SchemaAnalyzer(); + manual.addDocuments(sparseDocumentsArray); + + expect(JSON.stringify(analyzer.getSchema())).toBe(JSON.stringify(manual.getSchema())); + }); + + it('addDocuments() is equivalent to multiple addDocument() calls', () => { + const batch = new SchemaAnalyzer(); + batch.addDocuments(complexDocumentsArray); + + const sequential = new SchemaAnalyzer(); + for (const doc of complexDocumentsArray) { + sequential.addDocument(doc); + } + + expect(batch.getDocumentCount()).toBe(sequential.getDocumentCount()); + expect(JSON.stringify(batch.getSchema())).toBe(JSON.stringify(sequential.getSchema())); + }); + }); }); From d4380f448dd5997672a30a2a6280ef613077aa2e Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Mon, 16 Feb 2026 21:19:11 +0100 Subject: [PATCH 07/25] feat: add version-based caching to SchemaAnalyzer + trace logging - Add monotonic version counter to SchemaAnalyzer (incremented on mutations) - Cache getKnownFields() with version-based staleness check - Add ClusterSession.getKnownFields() accessor (delegates to cached analyzer) - Wire collectionViewRouter to use session.getKnownFields() instead of standalone function - Add ext.outputChannel.trace for schema accumulation and reset events --- src/documentdb/ClusterSession.ts | 16 ++++++++++ src/utils/json/data-api/SchemaAnalyzer.ts | 32 ++++++++++++++++++- .../collectionView/collectionViewRouter.ts | 5 ++- 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/documentdb/ClusterSession.ts b/src/documentdb/ClusterSession.ts index 53ae67c7e..cfa065196 100644 --- a/src/documentdb/ClusterSession.ts +++ b/src/documentdb/ClusterSession.ts @@ -6,8 +6,10 @@ import * as l10n from '@vscode/l10n'; import { EJSON } from 'bson'; import { ObjectId, type Document, type Filter, type WithId } from 'mongodb'; +import { ext } from '../extensionVariables'; import { type JSONSchema } from '../utils/json/JSONSchema'; import { SchemaAnalyzer, getPropertyNamesAtLevel } from '../utils/json/data-api/SchemaAnalyzer'; +import { type FieldEntry } from '../utils/json/data-api/autocomplete/getKnownFields'; import { getDataAtPath } from '../utils/slickgrid/mongo/toSlickGridTable'; import { toSlickGridTree, type TreeData } from '../utils/slickgrid/mongo/toSlickGridTree'; import { ClustersClient, type FindQueryParams } from './ClustersClient'; @@ -163,6 +165,7 @@ export class ClusterSession { // The user's query has changed, invalidate all caches this._schemaAnalyzer.reset(); + ext.outputChannel.trace('[SchemaAnalyzer] Reset — query changed'); this._highestPageAccumulated = 0; this._currentPageSize = null; this._currentRawDocuments = []; @@ -186,6 +189,7 @@ export class ClusterSession { if (this._currentPageSize !== null && this._currentPageSize !== newPageSize) { // Page size changed, reset accumulation tracking this._schemaAnalyzer.reset(); + ext.outputChannel.trace('[SchemaAnalyzer] Reset — page size changed'); this._highestPageAccumulated = 0; } this._currentPageSize = newPageSize; @@ -300,6 +304,10 @@ export class ClusterSession { if (pageNumber > this._highestPageAccumulated) { this._schemaAnalyzer.addDocuments(this._currentRawDocuments); this._highestPageAccumulated = pageNumber; + + ext.outputChannel.trace( + `[SchemaAnalyzer] Analyzed ${String(this._schemaAnalyzer.getDocumentCount())} documents, ${String(this._schemaAnalyzer.getKnownFields().length)} known fields`, + ); } return documents.length; @@ -366,6 +374,14 @@ export class ClusterSession { return this._schemaAnalyzer.getSchema(); } + /** + * Returns the cached list of known fields from the accumulated schema. + * Uses SchemaAnalyzer's version-based caching — only recomputed when the schema changes. + */ + public getKnownFields(): FieldEntry[] { + return this._schemaAnalyzer.getKnownFields(); + } + // ============================================================================ // Query Insights Methods // ============================================================================ diff --git a/src/utils/json/data-api/SchemaAnalyzer.ts b/src/utils/json/data-api/SchemaAnalyzer.ts index c340172a9..93b73a36a 100644 --- a/src/utils/json/data-api/SchemaAnalyzer.ts +++ b/src/utils/json/data-api/SchemaAnalyzer.ts @@ -9,6 +9,7 @@ import Denque from 'denque'; import { type Document, type WithId } from 'mongodb'; import { type JSONSchema } from '../JSONSchema'; import { BSONTypes } from './BSONTypes'; +import { type FieldEntry, getKnownFields as getKnownFieldsFromSchema } from './autocomplete/getKnownFields'; /** * Incremental schema analyzer for documents from the MongoDB API / DocumentDB API. @@ -20,6 +21,18 @@ import { BSONTypes } from './BSONTypes'; */ export class SchemaAnalyzer { private _schema: JSONSchema = {}; + private _version: number = 0; + private _knownFieldsCache: FieldEntry[] | null = null; + private _knownFieldsCacheVersion: number = -1; + + /** + * A monotonically increasing version counter. Incremented on every mutation + * (addDocument, addDocuments, reset). Adapters can store this value alongside + * their cached derived data and recompute only when it changes. + */ + get version(): number { + return this._version; + } /** * Adds a single document to the accumulated schema. @@ -27,16 +40,19 @@ export class SchemaAnalyzer { */ addDocument(document: WithId): void { updateSchemaWithDocumentInternal(this._schema, document); + this._version++; } /** * Adds multiple documents to the accumulated schema. * Convenience method equivalent to calling addDocument() for each. + * Increments version once for the entire batch — not per document. */ addDocuments(documents: ReadonlyArray>): void { for (const doc of documents) { - this.addDocument(doc); + updateSchemaWithDocumentInternal(this._schema, doc); } + this._version++; } /** @@ -59,11 +75,13 @@ export class SchemaAnalyzer { */ reset(): void { this._schema = {}; + this._version++; } /** * Creates a deep copy of this analyzer, including all accumulated schema data. * Useful for aggregation stage branching where each stage needs its own schema state. + * The clone starts with version 0, independent from the original. */ clone(): SchemaAnalyzer { const copy = new SchemaAnalyzer(); @@ -71,6 +89,18 @@ export class SchemaAnalyzer { return copy; } + /** + * Returns the cached list of known fields (all nesting levels, sorted). + * Recomputed only when the schema version has changed since the last call. + */ + getKnownFields(): FieldEntry[] { + if (this._knownFieldsCacheVersion !== this._version || this._knownFieldsCache === null) { + this._knownFieldsCache = getKnownFieldsFromSchema(this._schema); + this._knownFieldsCacheVersion = this._version; + } + return this._knownFieldsCache; + } + /** * Creates a SchemaAnalyzer from a single document. * Equivalent to creating an instance and calling addDocument() once. diff --git a/src/webviews/documentdb/collectionView/collectionViewRouter.ts b/src/webviews/documentdb/collectionView/collectionViewRouter.ts index c2d71e1d9..0e4b6a81b 100644 --- a/src/webviews/documentdb/collectionView/collectionViewRouter.ts +++ b/src/webviews/documentdb/collectionView/collectionViewRouter.ts @@ -12,7 +12,7 @@ import { type JSONSchema } from 'vscode-json-languageservice'; import { z } from 'zod'; import { ClusterSession } from '../../../documentdb/ClusterSession'; import { getConfirmationAsInSettings } from '../../../utils/dialogs/getConfirmation'; -import { getKnownFields, type FieldEntry } from '../../../utils/json/data-api/autocomplete/getKnownFields'; +import { type FieldEntry } from '../../../utils/json/data-api/autocomplete/getKnownFields'; import { publicProcedureWithTelemetry, router, type WithTelemetry } from '../../api/extension-server/trpc'; import * as l10n from '@vscode/l10n'; @@ -241,8 +241,7 @@ export const collectionsViewRouter = router({ const session: ClusterSession = ClusterSession.getSession(myCtx.sessionId); - const _currentJsonSchema = session.getCurrentSchema(); - const autoCompletionData: FieldEntry[] = getKnownFields(_currentJsonSchema); + const autoCompletionData: FieldEntry[] = session.getKnownFields(); let querySchema: JSONSchema; From 633f0b4370e0551b5c9c6c1e893209c0f2e2deed Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Feb 2026 07:39:35 +0000 Subject: [PATCH 08/25] Initial plan From 74eeeac88cd4b4118c1839487922b46c42b11b89 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Feb 2026 07:42:40 +0000 Subject: [PATCH 09/25] refactor: remove debug console.log statements from tests Co-authored-by: tnaum-ms <171359267+tnaum-ms@users.noreply.github.com> --- src/utils/json/data-api/SchemaAnalyzer.arrayStats.test.ts | 4 ---- src/utils/json/data-api/SchemaAnalyzer.test.ts | 1 - 2 files changed, 5 deletions(-) diff --git a/src/utils/json/data-api/SchemaAnalyzer.arrayStats.test.ts b/src/utils/json/data-api/SchemaAnalyzer.arrayStats.test.ts index 1f0ce7a3e..00f53d98f 100644 --- a/src/utils/json/data-api/SchemaAnalyzer.arrayStats.test.ts +++ b/src/utils/json/data-api/SchemaAnalyzer.arrayStats.test.ts @@ -274,10 +274,6 @@ describe('Array element occurrence analysis', () => { // doc2 second element (15): aggregateStatsForValue → max becomes 15 // final: min=5, max=15 ← WRONG (lost 30 from doc1) - // Let's check what actually happens: - console.log('numEntry x-minValue:', numEntry['x-minValue']); - console.log('numEntry x-maxValue:', numEntry['x-maxValue']); - // This test documents the actual behavior (might be buggy): expect(numEntry['x-minValue']).toBe(5); // If the bug exists, this will be 15 instead of 30: diff --git a/src/utils/json/data-api/SchemaAnalyzer.test.ts b/src/utils/json/data-api/SchemaAnalyzer.test.ts index fe4d83629..cb2b362c7 100644 --- a/src/utils/json/data-api/SchemaAnalyzer.test.ts +++ b/src/utils/json/data-api/SchemaAnalyzer.test.ts @@ -19,7 +19,6 @@ describe('DocumentDB Schema Analyzer', () => { it('prints out schema for testing', () => { const analyzer = SchemaAnalyzer.fromDocument(embeddedDocumentOnly); const schema = analyzer.getSchema(); - console.log(JSON.stringify(schema, null, 2)); expect(schema).toBeDefined(); }); From d8d07095af3a26f39d29472dbad3041cf853efaf Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Tue, 17 Feb 2026 09:58:59 +0100 Subject: [PATCH 10/25] test: add comprehensive tests for SchemaAnalyzer versioning and caching behavior --- .../SchemaAnalyzer.versioning.test.ts | 663 ++++++++++++++++++ 1 file changed, 663 insertions(+) create mode 100644 src/utils/json/data-api/SchemaAnalyzer.versioning.test.ts diff --git a/src/utils/json/data-api/SchemaAnalyzer.versioning.test.ts b/src/utils/json/data-api/SchemaAnalyzer.versioning.test.ts new file mode 100644 index 000000000..91a6fbddc --- /dev/null +++ b/src/utils/json/data-api/SchemaAnalyzer.versioning.test.ts @@ -0,0 +1,663 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ObjectId, type Document, type WithId } from 'mongodb'; +import { type JSONSchema } from '../JSONSchema'; +import { SchemaAnalyzer } from './SchemaAnalyzer'; + +// ------------------------------------------------------------------ +// Test fixtures +// ------------------------------------------------------------------ + +function makeDoc(fields: Record = {}): WithId { + return { _id: new ObjectId(), ...fields }; +} + +// ------------------------------------------------------------------ +// Version counter +// ------------------------------------------------------------------ +describe('SchemaAnalyzer version counter', () => { + it('starts at 0 for a new analyzer', () => { + const analyzer = new SchemaAnalyzer(); + expect(analyzer.version).toBe(0); + }); + + it('increments on addDocument()', () => { + const analyzer = new SchemaAnalyzer(); + analyzer.addDocument(makeDoc({ a: 1 })); + expect(analyzer.version).toBe(1); + + analyzer.addDocument(makeDoc({ b: 2 })); + expect(analyzer.version).toBe(2); + }); + + it('increments only once for addDocuments() (batch)', () => { + const analyzer = new SchemaAnalyzer(); + const docs = [makeDoc({ a: 1 }), makeDoc({ b: 2 }), makeDoc({ c: 3 })]; + + analyzer.addDocuments(docs); + expect(analyzer.version).toBe(1); + }); + + it('increments on reset()', () => { + const analyzer = SchemaAnalyzer.fromDocument(makeDoc({ x: 1 })); + expect(analyzer.version).toBe(1); + + analyzer.reset(); + expect(analyzer.version).toBe(2); + }); + + it('cloned analyzer starts with version 0 (independent from original)', () => { + const original = new SchemaAnalyzer(); + original.addDocument(makeDoc({ a: 1 })); + original.addDocument(makeDoc({ b: 2 })); + expect(original.version).toBe(2); + + const cloned = original.clone(); + expect(cloned.version).toBe(0); + + // Mutating the clone does not affect the original's version + cloned.addDocument(makeDoc({ c: 3 })); + expect(cloned.version).toBe(1); + expect(original.version).toBe(2); + }); + + it('accumulates across mixed operations', () => { + const analyzer = new SchemaAnalyzer(); + // addDocument +1 + analyzer.addDocument(makeDoc()); + expect(analyzer.version).toBe(1); + + // addDocuments +1 (batch) + analyzer.addDocuments([makeDoc(), makeDoc()]); + expect(analyzer.version).toBe(2); + + // reset +1 + analyzer.reset(); + expect(analyzer.version).toBe(3); + + // addDocument after reset +1 + analyzer.addDocument(makeDoc()); + expect(analyzer.version).toBe(4); + }); + + it('fromDocument() factory yields version 1', () => { + const analyzer = SchemaAnalyzer.fromDocument(makeDoc({ a: 1 })); + expect(analyzer.version).toBe(1); + }); + + it('fromDocuments() factory yields version 1', () => { + const analyzer = SchemaAnalyzer.fromDocuments([makeDoc(), makeDoc(), makeDoc()]); + expect(analyzer.version).toBe(1); + }); +}); + +// ------------------------------------------------------------------ +// Version-based caching (getKnownFields cache) +// ------------------------------------------------------------------ +describe('SchemaAnalyzer getKnownFields cache', () => { + it('is populated on first call to getKnownFields()', () => { + const analyzer = SchemaAnalyzer.fromDocument(makeDoc({ name: 'Alice', age: 30 })); + const fields = analyzer.getKnownFields(); + + expect(fields.length).toBeGreaterThan(0); + // Should contain _id, age, name + const paths = fields.map((f) => f.path); + expect(paths).toContain('_id'); + expect(paths).toContain('name'); + expect(paths).toContain('age'); + }); + + it('is reused when version has not changed (same reference)', () => { + const analyzer = SchemaAnalyzer.fromDocument(makeDoc({ name: 'Alice' })); + const first = analyzer.getKnownFields(); + const second = analyzer.getKnownFields(); + + // Same array reference — cache was reused, not recomputed + expect(second).toBe(first); + }); + + it('is invalidated when addDocument() is called', () => { + const analyzer = SchemaAnalyzer.fromDocument(makeDoc({ name: 'Alice' })); + const before = analyzer.getKnownFields(); + + analyzer.addDocument(makeDoc({ name: 'Bob', email: 'bob@test.com' })); + const after = analyzer.getKnownFields(); + + // Different reference — cache was recomputed + expect(after).not.toBe(before); + // New field should be present + expect(after.map((f) => f.path)).toContain('email'); + }); + + it('is invalidated when addDocuments() is called', () => { + const analyzer = SchemaAnalyzer.fromDocument(makeDoc({ name: 'Alice' })); + const before = analyzer.getKnownFields(); + + analyzer.addDocuments([makeDoc({ score: 42 }), makeDoc({ level: 7 })]); + const after = analyzer.getKnownFields(); + + expect(after).not.toBe(before); + const paths = after.map((f) => f.path); + expect(paths).toContain('score'); + expect(paths).toContain('level'); + }); + + it('is invalidated when reset() is called', () => { + const analyzer = SchemaAnalyzer.fromDocument(makeDoc({ name: 'Alice' })); + const before = analyzer.getKnownFields(); + expect(before.length).toBeGreaterThan(0); + + analyzer.reset(); + const after = analyzer.getKnownFields(); + + expect(after).not.toBe(before); + // After reset the schema is empty so no fields + expect(after).toHaveLength(0); + }); + + it('returns updated results after cache invalidation', () => { + const analyzer = new SchemaAnalyzer(); + // Empty analyzer → no known fields + expect(analyzer.getKnownFields()).toHaveLength(0); + + // Add first doc + analyzer.addDocument(makeDoc({ x: 1 })); + const fields1 = analyzer.getKnownFields(); + expect(fields1.map((f) => f.path)).toEqual(expect.arrayContaining(['_id', 'x'])); + + // Add second doc with new field + analyzer.addDocument(makeDoc({ x: 2, y: 'hello' })); + const fields2 = analyzer.getKnownFields(); + expect(fields2).not.toBe(fields1); + expect(fields2.map((f) => f.path)).toContain('y'); + }); + + it('clone gets its own independent cache', () => { + const original = SchemaAnalyzer.fromDocument(makeDoc({ name: 'Alice' })); + const originalFields = original.getKnownFields(); + + const cloned = original.clone(); + const clonedFields = cloned.getKnownFields(); + + // Both should have the same content but be independent objects + expect(clonedFields).not.toBe(originalFields); + expect(clonedFields.map((f) => f.path)).toEqual(originalFields.map((f) => f.path)); + + // Mutating the clone should not affect the original cache + cloned.addDocument(makeDoc({ extra: true })); + const clonedFieldsAfter = cloned.getKnownFields(); + expect(clonedFieldsAfter.map((f) => f.path)).toContain('extra'); + expect(original.getKnownFields().map((f) => f.path)).not.toContain('extra'); + }); +}); + +// ------------------------------------------------------------------ +// Instances and types counting +// ------------------------------------------------------------------ +describe('SchemaAnalyzer instances and types counting', () => { + describe('x-occurrence (field instance counting)', () => { + it('counts 1 for a field present in a single document', () => { + const analyzer = SchemaAnalyzer.fromDocument(makeDoc({ name: 'Alice' })); + const schema = analyzer.getSchema(); + const nameField = schema.properties?.['name'] as JSONSchema; + expect(nameField['x-occurrence']).toBe(1); + }); + + it('counts correctly across multiple documents', () => { + const analyzer = new SchemaAnalyzer(); + analyzer.addDocument(makeDoc({ name: 'Alice', age: 30 })); + analyzer.addDocument(makeDoc({ name: 'Bob', age: 25 })); + analyzer.addDocument(makeDoc({ name: 'Carol' })); // no age + + const schema = analyzer.getSchema(); + expect((schema.properties?.['name'] as JSONSchema)['x-occurrence']).toBe(3); + expect((schema.properties?.['age'] as JSONSchema)['x-occurrence']).toBe(2); + }); + + it('counts sparse fields correctly (field missing in some documents)', () => { + const analyzer = new SchemaAnalyzer(); + analyzer.addDocument(makeDoc({ a: 1, b: 2, c: 3 })); + analyzer.addDocument(makeDoc({ a: 10 })); // only 'a' + analyzer.addDocument(makeDoc({ a: 100, c: 300 })); // 'a' and 'c' + + const schema = analyzer.getSchema(); + expect((schema.properties?.['a'] as JSONSchema)['x-occurrence']).toBe(3); + expect((schema.properties?.['b'] as JSONSchema)['x-occurrence']).toBe(1); + expect((schema.properties?.['c'] as JSONSchema)['x-occurrence']).toBe(2); + }); + + it('counts occurrences for nested object properties', () => { + const analyzer = new SchemaAnalyzer(); + analyzer.addDocument(makeDoc({ user: { name: 'Alice', age: 30 } })); + analyzer.addDocument(makeDoc({ user: { name: 'Bob' } })); // no age + + const schema = analyzer.getSchema(); + const userField = schema.properties?.['user'] as JSONSchema; + const objectEntry = userField.anyOf?.find((e) => (e as JSONSchema).type === 'object') as JSONSchema; + + expect((objectEntry.properties?.['name'] as JSONSchema)['x-occurrence']).toBe(2); + expect((objectEntry.properties?.['age'] as JSONSchema)['x-occurrence']).toBe(1); + }); + }); + + describe('x-typeOccurrence (type counting)', () => { + it('counts type occurrences for a single-type field', () => { + const analyzer = new SchemaAnalyzer(); + analyzer.addDocument(makeDoc({ name: 'Alice' })); + analyzer.addDocument(makeDoc({ name: 'Bob' })); + analyzer.addDocument(makeDoc({ name: 'Carol' })); + + const schema = analyzer.getSchema(); + const nameField = schema.properties?.['name'] as JSONSchema; + const stringEntry = nameField.anyOf?.find( + (e) => (e as JSONSchema)['x-bsonType'] === 'string', + ) as JSONSchema; + + expect(stringEntry['x-typeOccurrence']).toBe(3); + }); + + it('counts type occurrences for polymorphic fields', () => { + const analyzer = new SchemaAnalyzer(); + analyzer.addDocument(makeDoc({ value: 'hello' })); + analyzer.addDocument(makeDoc({ value: 42 })); + analyzer.addDocument(makeDoc({ value: 'world' })); + analyzer.addDocument(makeDoc({ value: true })); + + const schema = analyzer.getSchema(); + const valueField = schema.properties?.['value'] as JSONSchema; + + const stringEntry = valueField.anyOf?.find( + (e) => (e as JSONSchema)['x-bsonType'] === 'string', + ) as JSONSchema; + const booleanEntry = valueField.anyOf?.find( + (e) => (e as JSONSchema)['x-bsonType'] === 'boolean', + ) as JSONSchema; + + // 2 strings, 1 number, 1 boolean + expect(stringEntry['x-typeOccurrence']).toBe(2); + expect(booleanEntry['x-typeOccurrence']).toBe(1); + + // total x-occurrence should equal sum of x-typeOccurrence values + const totalTypeOccurrence = (valueField.anyOf as JSONSchema[]).reduce( + (sum, entry) => sum + ((entry['x-typeOccurrence'] as number) ?? 0), + 0, + ); + expect(valueField['x-occurrence']).toBe(totalTypeOccurrence); + }); + + it('counts array element types across documents', () => { + const analyzer = new SchemaAnalyzer(); + analyzer.addDocument(makeDoc({ tags: ['a', 'b'] })); // 2 strings + analyzer.addDocument(makeDoc({ tags: ['c', 42] })); // 1 string + 1 number + analyzer.addDocument(makeDoc({ tags: [true] })); // 1 boolean + + const schema = analyzer.getSchema(); + const tagsField = schema.properties?.['tags'] as JSONSchema; + const arrayEntry = tagsField.anyOf?.find((e) => (e as JSONSchema)['x-bsonType'] === 'array') as JSONSchema; + const itemsSchema = arrayEntry.items as JSONSchema; + + const stringEntry = itemsSchema.anyOf?.find( + (e) => (e as JSONSchema)['x-bsonType'] === 'string', + ) as JSONSchema; + const booleanEntry = itemsSchema.anyOf?.find( + (e) => (e as JSONSchema)['x-bsonType'] === 'boolean', + ) as JSONSchema; + + // 3 string elements total: "a", "b", "c" + expect(stringEntry['x-typeOccurrence']).toBe(3); + + // 1 boolean element + expect(booleanEntry['x-typeOccurrence']).toBe(1); + }); + + it('type occurrence count equals field occurrence for a single-type field', () => { + const analyzer = new SchemaAnalyzer(); + for (let i = 0; i < 5; i++) { + analyzer.addDocument(makeDoc({ score: i * 10 })); + } + + const schema = analyzer.getSchema(); + const scoreField = schema.properties?.['score'] as JSONSchema; + const typeEntries = scoreField.anyOf as JSONSchema[]; + + // Only one type, so its typeOccurrence should equal the field occurrence + expect(typeEntries).toHaveLength(1); + expect(typeEntries[0]['x-typeOccurrence']).toBe(scoreField['x-occurrence']); + }); + }); + + describe('x-documentsInspected counting', () => { + it('tracks document count at root level', () => { + const analyzer = new SchemaAnalyzer(); + analyzer.addDocument(makeDoc({ a: 1 })); + analyzer.addDocument(makeDoc({ b: 2 })); + analyzer.addDocument(makeDoc({ c: 3 })); + + expect(analyzer.getSchema()['x-documentsInspected']).toBe(3); + expect(analyzer.getDocumentCount()).toBe(3); + }); + + it('tracks object instances for nested objects', () => { + const analyzer = new SchemaAnalyzer(); + analyzer.addDocument(makeDoc({ info: { x: 1 } })); + analyzer.addDocument(makeDoc({ info: { x: 2, y: 3 } })); + + const schema = analyzer.getSchema(); + const infoField = schema.properties?.['info'] as JSONSchema; + const objectEntry = infoField.anyOf?.find( + (e) => (e as JSONSchema)['x-bsonType'] === 'object', + ) as JSONSchema; + + expect(objectEntry['x-documentsInspected']).toBe(2); + }); + + it('tracks object instances inside arrays accurately', () => { + const analyzer = new SchemaAnalyzer(); + // doc1: array with 2 objects + analyzer.addDocument(makeDoc({ items: [{ a: 1 }, { a: 2 }] })); + // doc2: array with 1 object + analyzer.addDocument(makeDoc({ items: [{ a: 3, b: 4 }] })); + + const schema = analyzer.getSchema(); + const itemsField = schema.properties?.['items'] as JSONSchema; + const arrayEntry = itemsField.anyOf?.find((e) => (e as JSONSchema)['x-bsonType'] === 'array') as JSONSchema; + const objectEntry = (arrayEntry.items as JSONSchema).anyOf?.find( + (e) => (e as JSONSchema)['x-bsonType'] === 'object', + ) as JSONSchema; + + // 3 objects total (2 from doc1, 1 from doc2) + expect(objectEntry['x-documentsInspected']).toBe(3); + // "a" appears in all 3 objects + expect((objectEntry.properties?.['a'] as JSONSchema)['x-occurrence']).toBe(3); + // "b" appears in 1 of 3 objects + expect((objectEntry.properties?.['b'] as JSONSchema)['x-occurrence']).toBe(1); + }); + + it('resets to 0 after reset()', () => { + const analyzer = SchemaAnalyzer.fromDocuments([makeDoc({ a: 1 }), makeDoc({ b: 2 })]); + expect(analyzer.getDocumentCount()).toBe(2); + + analyzer.reset(); + expect(analyzer.getDocumentCount()).toBe(0); + }); + }); + + describe('probability correctness (occurrence / documentsInspected)', () => { + it('yields 100% for fields present in every document', () => { + const analyzer = new SchemaAnalyzer(); + for (let i = 0; i < 10; i++) { + analyzer.addDocument(makeDoc({ name: `user-${i}` })); + } + + const schema = analyzer.getSchema(); + const occurrence = (schema.properties?.['name'] as JSONSchema)['x-occurrence'] as number; + const total = schema['x-documentsInspected'] as number; + expect(occurrence / total).toBe(1); + }); + + it('yields correct fraction for sparse fields', () => { + const analyzer = new SchemaAnalyzer(); + // 3 docs with 'a', 1 doc with 'b' + analyzer.addDocument(makeDoc({ a: 1, b: 10 })); + analyzer.addDocument(makeDoc({ a: 2 })); + analyzer.addDocument(makeDoc({ a: 3 })); + + const schema = analyzer.getSchema(); + const total = schema['x-documentsInspected'] as number; + const aOccurrence = (schema.properties?.['a'] as JSONSchema)['x-occurrence'] as number; + const bOccurrence = (schema.properties?.['b'] as JSONSchema)['x-occurrence'] as number; + + expect(aOccurrence / total).toBe(1); // 3/3 + expect(bOccurrence / total).toBeCloseTo(1 / 3); // 1/3 + }); + + it('yields correct fraction for nested objects inside arrays', () => { + const analyzer = new SchemaAnalyzer(); + analyzer.addDocument( + makeDoc({ + items: [ + { name: 'A', price: 10 }, + { name: 'B' }, // no price + ], + }), + ); + analyzer.addDocument(makeDoc({ items: [{ name: 'C', price: 20 }] })); + + const schema = analyzer.getSchema(); + const itemsField = schema.properties?.['items'] as JSONSchema; + const arrayEntry = itemsField.anyOf?.find((e) => (e as JSONSchema)['x-bsonType'] === 'array') as JSONSchema; + const objectEntry = (arrayEntry.items as JSONSchema).anyOf?.find( + (e) => (e as JSONSchema)['x-bsonType'] === 'object', + ) as JSONSchema; + + const denominator = objectEntry['x-documentsInspected'] as number; + const nameOccurrence = (objectEntry.properties?.['name'] as JSONSchema)['x-occurrence'] as number; + const priceOccurrence = (objectEntry.properties?.['price'] as JSONSchema)['x-occurrence'] as number; + + expect(denominator).toBe(3); // 3 objects total + expect(nameOccurrence / denominator).toBe(1); // 3/3 + expect(priceOccurrence / denominator).toBeCloseTo(2 / 3); // 2/3 + }); + }); + + describe('array and nested array counting', () => { + it('counts x-typeOccurrence for the array type entry across documents', () => { + const analyzer = new SchemaAnalyzer(); + analyzer.addDocument(makeDoc({ tags: ['a'] })); + analyzer.addDocument(makeDoc({ tags: ['b', 'c'] })); + analyzer.addDocument(makeDoc({ tags: 42 })); // not an array + + const schema = analyzer.getSchema(); + const tagsField = schema.properties?.['tags'] as JSONSchema; + + // Field seen 3 times total + expect(tagsField['x-occurrence']).toBe(3); + + const arrayEntry = tagsField.anyOf?.find((e) => (e as JSONSchema)['x-bsonType'] === 'array') as JSONSchema; + + // Array type seen 2 out of 3 times + expect(arrayEntry['x-typeOccurrence']).toBe(2); + + // x-minItems / x-maxItems tracked across array instances + expect(arrayEntry['x-minItems']).toBe(1); + expect(arrayEntry['x-maxItems']).toBe(2); + }); + + it('counts x-minItems / x-maxItems for arrays across documents', () => { + const analyzer = new SchemaAnalyzer(); + analyzer.addDocument(makeDoc({ nums: [1, 2, 3] })); // length 3 + analyzer.addDocument(makeDoc({ nums: [10] })); // length 1 + analyzer.addDocument(makeDoc({ nums: [4, 5, 6, 7, 8] })); // length 5 + + const schema = analyzer.getSchema(); + const numsField = schema.properties?.['nums'] as JSONSchema; + const arrayEntry = numsField.anyOf?.find((e) => (e as JSONSchema)['x-bsonType'] === 'array') as JSONSchema; + + expect(arrayEntry['x-minItems']).toBe(1); + expect(arrayEntry['x-maxItems']).toBe(5); + expect(arrayEntry['x-typeOccurrence']).toBe(3); + }); + + it('counts nested arrays (arrays within arrays)', () => { + const analyzer = new SchemaAnalyzer(); + // matrix is an array of arrays of numbers + analyzer.addDocument( + makeDoc({ + matrix: [ + [1, 2], + [3, 4, 5], + ], + }), + ); + analyzer.addDocument(makeDoc({ matrix: [[10]] })); + + const schema = analyzer.getSchema(); + const matrixField = schema.properties?.['matrix'] as JSONSchema; + const outerArrayEntry = matrixField.anyOf?.find( + (e) => (e as JSONSchema)['x-bsonType'] === 'array', + ) as JSONSchema; + + // Outer array seen in 2 documents + expect(outerArrayEntry['x-typeOccurrence']).toBe(2); + // doc1 has 2 inner arrays, doc2 has 1 + expect(outerArrayEntry['x-minItems']).toBe(1); + expect(outerArrayEntry['x-maxItems']).toBe(2); + + // Inner arrays: items type should be 'array' + const innerArrayEntry = (outerArrayEntry.items as JSONSchema).anyOf?.find( + (e) => (e as JSONSchema)['x-bsonType'] === 'array', + ) as JSONSchema; + expect(innerArrayEntry).toBeDefined(); + // 3 inner arrays total: [1,2], [3,4,5], [10] + expect(innerArrayEntry['x-typeOccurrence']).toBe(3); + // inner array lengths: 2, 3, 1 + expect(innerArrayEntry['x-minItems']).toBe(1); + expect(innerArrayEntry['x-maxItems']).toBe(3); + + // Elements inside inner arrays are numbers + const numberEntry = (innerArrayEntry.items as JSONSchema).anyOf?.find( + (e) => (e as JSONSchema).type === 'number', + ) as JSONSchema; + expect(numberEntry).toBeDefined(); + // 6 numbers total: 1,2,3,4,5,10 + expect(numberEntry['x-typeOccurrence']).toBe(6); + }); + + it('counts objects within arrays within objects (deep nesting)', () => { + const analyzer = new SchemaAnalyzer(); + analyzer.addDocument( + makeDoc({ + company: { + departments: [ + { name: 'Eng', employees: [{ role: 'Dev' }, { role: 'QA', level: 3 }] }, + { name: 'Sales' }, + ], + }, + }), + ); + analyzer.addDocument( + makeDoc({ + company: { + departments: [{ name: 'HR', employees: [{ role: 'Recruiter' }] }], + }, + }), + ); + + const schema = analyzer.getSchema(); + + // company is an object + const companyField = schema.properties?.['company'] as JSONSchema; + const companyObj = companyField.anyOf?.find( + (e) => (e as JSONSchema)['x-bsonType'] === 'object', + ) as JSONSchema; + expect(companyObj['x-documentsInspected']).toBe(2); + + // departments is an array inside company + const deptField = companyObj.properties?.['departments'] as JSONSchema; + const deptArrayEntry = deptField.anyOf?.find( + (e) => (e as JSONSchema)['x-bsonType'] === 'array', + ) as JSONSchema; + expect(deptArrayEntry['x-typeOccurrence']).toBe(2); + + // department objects: 2 from doc1 + 1 from doc2 = 3 + const deptObjEntry = (deptArrayEntry.items as JSONSchema).anyOf?.find( + (e) => (e as JSONSchema)['x-bsonType'] === 'object', + ) as JSONSchema; + expect(deptObjEntry['x-documentsInspected']).toBe(3); + expect(deptObjEntry['x-typeOccurrence']).toBe(3); + + // "name" in all 3 department objects, "employees" in 2 of 3 + expect((deptObjEntry.properties?.['name'] as JSONSchema)['x-occurrence']).toBe(3); + expect((deptObjEntry.properties?.['employees'] as JSONSchema)['x-occurrence']).toBe(2); + + // employees is an array inside department objects + const empField = deptObjEntry.properties?.['employees'] as JSONSchema; + const empArrayEntry = empField.anyOf?.find( + (e) => (e as JSONSchema)['x-bsonType'] === 'array', + ) as JSONSchema; + expect(empArrayEntry['x-typeOccurrence']).toBe(2); + + // employee objects: 2 from first dept + 1 from HR = 3 + const empObjEntry = (empArrayEntry.items as JSONSchema).anyOf?.find( + (e) => (e as JSONSchema)['x-bsonType'] === 'object', + ) as JSONSchema; + expect(empObjEntry['x-documentsInspected']).toBe(3); + + // "role" in all 3 employee objects, "level" in 1 + expect((empObjEntry.properties?.['role'] as JSONSchema)['x-occurrence']).toBe(3); + expect((empObjEntry.properties?.['level'] as JSONSchema)['x-occurrence']).toBe(1); + }); + + it('tracks mixed types inside arrays (objects + primitives)', () => { + const analyzer = new SchemaAnalyzer(); + analyzer.addDocument(makeDoc({ data: ['hello', { key: 'val' }, 42] })); + analyzer.addDocument(makeDoc({ data: [{ key: 'v2', extra: true }] })); + + const schema = analyzer.getSchema(); + const dataField = schema.properties?.['data'] as JSONSchema; + const arrayEntry = dataField.anyOf?.find((e) => (e as JSONSchema)['x-bsonType'] === 'array') as JSONSchema; + const itemsSchema = arrayEntry.items as JSONSchema; + + // string: 1, object: 2, number: 1 + const stringEntry = itemsSchema.anyOf?.find( + (e) => (e as JSONSchema)['x-bsonType'] === 'string', + ) as JSONSchema; + const objectEntry = itemsSchema.anyOf?.find( + (e) => (e as JSONSchema)['x-bsonType'] === 'object', + ) as JSONSchema; + + expect(stringEntry['x-typeOccurrence']).toBe(1); + expect(objectEntry['x-typeOccurrence']).toBe(2); + expect(objectEntry['x-documentsInspected']).toBe(2); + + // "key" in both objects, "extra" in 1 + expect((objectEntry.properties?.['key'] as JSONSchema)['x-occurrence']).toBe(2); + expect((objectEntry.properties?.['extra'] as JSONSchema)['x-occurrence']).toBe(1); + }); + }); + + describe('addDocuments vs sequential addDocument equivalence', () => { + it('produces identical occurrence counts', () => { + const docs = [makeDoc({ a: 1, b: 'x' }), makeDoc({ a: 2 }), makeDoc({ a: 3, c: true })]; + + const batch = new SchemaAnalyzer(); + batch.addDocuments(docs); + + const sequential = new SchemaAnalyzer(); + for (const doc of docs) { + sequential.addDocument(doc); + } + + const batchSchema = batch.getSchema(); + const seqSchema = sequential.getSchema(); + + // Root counts match + expect(batchSchema['x-documentsInspected']).toBe(seqSchema['x-documentsInspected']); + + // Field-level occurrence counts match + for (const key of Object.keys(batchSchema.properties ?? {})) { + const batchField = batchSchema.properties?.[key] as JSONSchema; + const seqField = seqSchema.properties?.[key] as JSONSchema; + expect(batchField['x-occurrence']).toBe(seqField['x-occurrence']); + } + }); + + it('produces identical type occurrence counts', () => { + const docs = [makeDoc({ value: 'hello' }), makeDoc({ value: 42 }), makeDoc({ value: 'world' })]; + + const batch = new SchemaAnalyzer(); + batch.addDocuments(docs); + + const sequential = new SchemaAnalyzer(); + for (const doc of docs) { + sequential.addDocument(doc); + } + + // Stringify the schemas to compare their full type entry structures + expect(JSON.stringify(batch.getSchema())).toBe(JSON.stringify(sequential.getSchema())); + }); + }); +}); From ebdde30042241bfe0b9b6019e566face77e4054e Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Tue, 17 Feb 2026 10:03:18 +0100 Subject: [PATCH 11/25] refactor: remove console.log statements from test files for cleaner output --- src/utils/slickgrid/mongo/toSlickGridTable.test.ts | 1 - test/mongoGetCommand.test.ts | 4 ---- 2 files changed, 5 deletions(-) diff --git a/src/utils/slickgrid/mongo/toSlickGridTable.test.ts b/src/utils/slickgrid/mongo/toSlickGridTable.test.ts index 69156bf1b..4b1d0af3f 100644 --- a/src/utils/slickgrid/mongo/toSlickGridTable.test.ts +++ b/src/utils/slickgrid/mongo/toSlickGridTable.test.ts @@ -76,7 +76,6 @@ describe('toSlickGridTable', () => { it('at a nested level', () => { const tableData = getDataAtPath(mongoDocuments, ['nestedDocument']); - console.log(tableData); expect(tableData).toHaveLength(5); expect(tableData[0]['key']).toBeDefined(); diff --git a/test/mongoGetCommand.test.ts b/test/mongoGetCommand.test.ts index 7b4ce3f4d..bf34fa867 100644 --- a/test/mongoGetCommand.test.ts +++ b/test/mongoGetCommand.test.ts @@ -797,7 +797,6 @@ suite('scrapbook parsing Tests', () => { const commands: MongoCommand[] = getAllCommandsFromText(text); const command: MongoCommand = findCommandAtPosition(commands, new Position(0, 0)); const generatedRegExp = (nonNullProp(command, 'argumentObjects')[0]).sku; - console.log('generatedRegExp', generatedRegExp); assert.deepEqual(generatedRegExp.options, 'i'); assert.deepEqual(generatedRegExp.pattern, '789$'); }); @@ -838,11 +837,8 @@ suite('scrapbook parsing Tests', () => { // The regex parsing tests following this test should help zero-in on which case isn't handled properly. test('test regular expression parsing - with many special cases', () => { const text = `db.test1.beep.find({ sku: /^(hello?= world).*[^0-9]+|(world\\b\\*){0,2}$/ })`; - console.log(text); const commands: MongoCommand[] = getAllCommandsFromText(text); - console.log('commands', commands); const command: MongoCommand = findCommandAtPosition(commands, new Position(0, 0)); - console.log('command', command); const generatedRegExp = (nonNullProp(command, 'argumentObjects')[0]).sku; assert.deepEqual(generatedRegExp.options, ''); assert.deepEqual(generatedRegExp.pattern, '^(hello?= world).*[^0-9]+|(world\\b\\*){0,2}$'); From c23b604f64add8fba6a50ae8fa89e9ee485da0c3 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Tue, 17 Feb 2026 10:42:45 +0100 Subject: [PATCH 12/25] refactor: enhance handling of special characters in field names for TypeScript definitions and completion items --- .../json/data-api/autocomplete/future-work.md | 140 ++++++++++++++++++ .../data-api/autocomplete/getKnownFields.ts | 10 ++ .../autocomplete/toFieldCompletionItems.ts | 30 +++- .../toTypeScriptDefinition.test.ts | 91 ++++++++++++ .../autocomplete/toTypeScriptDefinition.ts | 30 +++- 5 files changed, 297 insertions(+), 4 deletions(-) create mode 100644 src/utils/json/data-api/autocomplete/future-work.md diff --git a/src/utils/json/data-api/autocomplete/future-work.md b/src/utils/json/data-api/autocomplete/future-work.md new file mode 100644 index 000000000..6e77d2913 --- /dev/null +++ b/src/utils/json/data-api/autocomplete/future-work.md @@ -0,0 +1,140 @@ +# Autocomplete — Future Work + +Outstanding TODOs flagged in code during the schema transformer implementation (PR #506). +These must be resolved before the completion providers ship to users. + +--- + +## 1. `SPECIAL_CHARS_PATTERN` is incomplete + `insertText` quoting doesn't escape + +**Severity:** Medium — will produce broken query expressions for real-world field names +**File:** `toFieldCompletionItems.ts` — `SPECIAL_CHARS_PATTERN` and `insertText` construction +**When to fix:** Before the `CompletionItemProvider` is wired up + +### Problem + +`SPECIAL_CHARS_PATTERN` (`/[.$\s]/`) only catches dots, `$`, and whitespace. MongoDB field names can also contain: + +| Character | Example field name | Current behavior | +| ---------------- | ------------------ | ----------------------------------------------- | +| Dash `-` | `order-items` | Inserted unquoted → breaks JSON key context | +| Brackets `[]` | `items[0]` | Inserted unquoted | +| Double quote `"` | `say"hi"` | Wrapped as `"say"hi""` → broken string | +| Single quote `'` | `it's` | Inserted unquoted (may break some contexts) | +| Backslash `\` | `back\slash` | Wrapped as `"back\slash"` → unescaped backslash | + +Additionally, when quoting _is_ triggered, the current logic (`"${entry.path}"`) does not escape embedded `"` or `\` inside the value. + +### Proposed fix + +1. Replace `SPECIAL_CHARS_PATTERN` with an identifier check (same as `safePropertyName` in `toTypeScriptDefinition.ts`): + ```typescript + const JS_IDENTIFIER = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/; + const needsQuoting = !JS_IDENTIFIER.test(entry.path); + ``` +2. When quoting, escape the content: + ```typescript + const escaped = entry.path.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); + insertText: needsQuoting ? `"${escaped}"` : entry.path, + ``` + +### Note on display vs insert + +The `fieldName` property intentionally stays unescaped (human-readable) for the completion list label. Only `insertText` gets escaped — this is by design, so users see clean names in the dropdown and the escaped form is inserted on selection. + +--- + +## 2. `referenceText` is invalid MQL for special field names + +**Severity:** Medium — will generate broken aggregation expressions +**File:** `toFieldCompletionItems.ts` — `referenceText` construction +**When to fix:** Before the aggregation completion provider is wired up + +### Problem + +`referenceText` is always `$${entry.path}` (e.g., `$address.city`). In MQL, the `$field.path` syntax only works when every segment is a valid identifier without dots, spaces, or `$`. For field names like `order-items`, `a.b`, or `my field`, the `$` prefix syntax produces invalid references. + +### Examples + +| Field name | Current `referenceText` | Valid? | Correct MQL | +| ------------------- | ----------------------- | -------------- | ------------------------------------ | +| `age` | `$age` | ✅ | `$age` | +| `address.city` | `$address.city` | ✅ (nested) | `$address.city` | +| `order-items` | `$order-items` | ❌ | `{ $getField: "order-items" }` | +| `a.b` (literal dot) | `$a.b` | ❌ (ambiguous) | `{ $getField: { $literal: "a.b" } }` | +| `my field` | `$my field` | ❌ | `{ $getField: "my field" }` | + +### Proposed approaches + +**Option A — Make `referenceText` optional:** Return `undefined` for fields that can't use `$`-prefix syntax. The completion provider would omit the reference suggestion for those fields. + +**Option B — Use `$getField` for special names:** + +```typescript +referenceText: needsQuoting + ? `{ $getField: "${escaped}" }` + : `$${entry.path}`, +``` + +**Option C — Provide both forms:** Add a `referenceTextRaw` (always `$path`) and `referenceTextSafe` (uses `$getField` when needed). Let the completion provider choose based on context. + +**Recommendation:** Option B is pragmatic. Option C is more flexible if we later need to support both forms in different contexts (e.g., `$match` vs `$project`). + +--- + +## 3. `FieldEntry.path` dot-concatenation is ambiguous for literal dots + +**Severity:** Low (rare in practice) — fields with literal dots were prohibited before MongoDB 3.6 +**File:** `getKnownFields.ts` — path concatenation at `path: \`${path}.${childName}\``**When to fix:** When we encounter real-world schemas with literal dots, or during the next`FieldEntry` interface revision + +### Problem + +Paths are built by concatenating segments with `.` as separator. A root-level field named `"a.b"` produces `path: "a.b"`, which is indistinguishable from a nested field `{ a: { b: ... } }`. + +This ambiguity flows downstream to all consumers: `toTypeScriptDefinition`, `toFieldCompletionItems`, `generateDescriptions`, and any future completion provider. + +### Examples + +| Document shape | Resulting `path` | Ambiguous? | +| --------------------- | ---------------- | ----------------------------- | +| `{ a: { b: 1 } }` | `"a.b"` | — | +| `{ "a.b": 1 }` | `"a.b"` | ✅ Same as above | +| `{ x: { "y.z": 1 } }` | `"x.y.z"` | ✅ Looks like 3-level nesting | + +### Proposed fix + +Change `FieldEntry.path` from `string` to `string[]` (segment array): + +```typescript +// Before +interface FieldEntry { + path: string; // "address.city" + ... +} + +// After +interface FieldEntry { + path: string[]; // ["address", "city"] + ... +} +``` + +Each consumer then formats the path for its own context: + +- **TypeScript definitions:** Already use schema `properties` keys directly (no change needed there) +- **Completion items:** `entry.path.join('.')` for display, bracket notation for special segments +- **Aggregation references:** `$` + segments joined with `.`, or `$getField` chains for special segments + +### Impact + +This is a **breaking change** to the `FieldEntry` interface. Affected consumers: + +- `toFieldCompletionItems.ts` +- `toTypeScriptDefinition.ts` (indirect — uses schema, not FieldEntry paths) +- `generateDescriptions.ts` (uses schema, not FieldEntry paths) +- `collectionViewRouter.ts` (imports `FieldEntry` type) +- `ClusterSession.ts` (imports `FieldEntry` type) +- `generateMongoFindJsonSchema.ts` (imports `FieldEntry` type) +- `SchemaAnalyzer.ts` (returns `FieldEntry[]` via `getKnownFields`) + +**Recommendation:** Defer until the completion provider is built. The ambiguity only matters for fields with literal dots, which are uncommon. When fixing, do it as a single atomic change across all consumers. diff --git a/src/utils/json/data-api/autocomplete/getKnownFields.ts b/src/utils/json/data-api/autocomplete/getKnownFields.ts index 006c57f94..63a53fc99 100644 --- a/src/utils/json/data-api/autocomplete/getKnownFields.ts +++ b/src/utils/json/data-api/autocomplete/getKnownFields.ts @@ -82,6 +82,16 @@ export function getKnownFields(schema: JSONSchema): FieldEntry[] { const objectDocumentsInspected = (mostCommonTypeEntry['x-documentsInspected'] as number) ?? 0; for (const childName of Object.keys(mostCommonTypeEntry.properties)) { const childSchema = mostCommonTypeEntry.properties[childName] as JSONSchema; + // TODO: Dot-delimited path concatenation is ambiguous when a field name + // itself contains a literal dot. For example, a root-level field named + // "a.b" produces path "a.b", indistinguishable from a nested field + // { a: { b: ... } }. Fields with literal dots in their names were + // prohibited before MongoDB 3.6 and remain rare in practice. + // + // Future improvement: change `path` from `string` to `string[]` + // (segment array) to preserve the distinction between nesting and + // literal dots, pushing escaping/formatting decisions to consumers + // (TS definitions, completion items, aggregation references, etc.). queue.push({ path: `${path}.${childName}`, schemaNode: childSchema, diff --git a/src/utils/json/data-api/autocomplete/toFieldCompletionItems.ts b/src/utils/json/data-api/autocomplete/toFieldCompletionItems.ts index af68908a2..03d1a9a89 100644 --- a/src/utils/json/data-api/autocomplete/toFieldCompletionItems.ts +++ b/src/utils/json/data-api/autocomplete/toFieldCompletionItems.ts @@ -8,9 +8,17 @@ import { type FieldEntry } from './getKnownFields'; /** * Completion-ready data for a single field entry. + * + * Design intent: + * - `fieldName` is the human-readable, unescaped field path shown in the completion list. + * Users see clean names like "address.city" or "order-items" without quotes or escaping. + * - `insertText` is the escaped/quoted form that gets inserted when the user selects a + * completion item. For simple identifiers it matches `fieldName`; for names containing + * special characters (dots, spaces, `$`, etc.) it is wrapped in double quotes. + * - `referenceText` is the `$`-prefixed aggregation field reference (e.g., "$age"). */ export interface FieldCompletionData { - /** The full dot-notated field name, e.g., "address.city" */ + /** The full dot-notated field name, e.g., "address.city" — kept unescaped for display */ fieldName: string; /** Human-readable type display, e.g., "String", "Date", "ObjectId" */ displayType: string; @@ -18,14 +26,30 @@ export interface FieldCompletionData { bsonType: string; /** Whether the field was not present in every inspected document (statistical observation, not a constraint) */ isSparse: boolean; - /** Text to insert — escaped if field name contains dots or special chars */ + /** Text to insert when the user selects this completion — quoted/escaped if the field name contains special chars */ insertText: string; - /** Field reference for aggregation expressions, e.g., "$age", "$address.city" */ + /** + * Field reference for aggregation expressions, e.g., "$age", "$address.city". + * + * TODO: The simple `$field.path` syntax is invalid MQL for field names containing dots, + * spaces, or `$` characters. For such fields, the correct MQL syntax is + * `{ $getField: "fieldName" }`. This should be addressed when the aggregation + * completion provider is wired up — either by using `$getField` for special names + * or by making `referenceText` optional for fields that cannot use the `$` prefix syntax. + */ referenceText: string; } /** * Characters that require quoting in a field name for insert text. + * + * TODO: This pattern currently only catches dots, `$`, and whitespace. It misses other + * characters that are valid in MongoDB field names but problematic in query expressions: + * dashes (`-`), brackets (`[`, `]`), quotes (`"`, `'`), and backslashes (`\`). + * Additionally, the quoting logic (`"${path}"`) does not escape embedded double quotes + * or backslashes inside the field name. Both gaps should be addressed when the + * CompletionItemProvider is wired up — the fix is to (1) widen this to a "is valid + * unquoted identifier" check and (2) escape `"` → `\"` and `\` → `\\` in insertText. */ const SPECIAL_CHARS_PATTERN = /[.$\s]/; diff --git a/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.test.ts b/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.test.ts index 00a112536..256d59a27 100644 --- a/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.test.ts +++ b/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.test.ts @@ -206,4 +206,95 @@ describe('toTypeScriptDefinition', () => { 'interface MyAwesomeCollectionDocument', ); }); + + describe('special character field names', () => { + function makeSchemaWithField(fieldName: string): JSONSchema { + return { + 'x-documentsInspected': 100, + properties: { + [fieldName]: { + 'x-occurrence': 100, + anyOf: [ + { + type: 'string', + 'x-bsonType': 'string', + 'x-typeOccurrence': 100, + }, + ], + }, + }, + }; + } + + it('leaves valid identifiers unquoted', () => { + const result = toTypeScriptDefinition(makeSchemaWithField('age'), 'test'); + expect(result).toContain(' age: string;'); + }); + + it('leaves underscore-prefixed identifiers unquoted', () => { + const result = toTypeScriptDefinition(makeSchemaWithField('_id'), 'test'); + expect(result).toContain(' _id: string;'); + }); + + it('leaves dollar-prefixed identifiers unquoted', () => { + const result = toTypeScriptDefinition(makeSchemaWithField('$type'), 'test'); + expect(result).toContain(' $type: string;'); + }); + + it('quotes field names with dashes', () => { + const result = toTypeScriptDefinition(makeSchemaWithField('order-items'), 'test'); + expect(result).toContain(' "order-items": string;'); + }); + + it('quotes field names with dots', () => { + const result = toTypeScriptDefinition(makeSchemaWithField('a.b'), 'test'); + expect(result).toContain(' "a.b": string;'); + }); + + it('quotes field names with spaces', () => { + const result = toTypeScriptDefinition(makeSchemaWithField('my field'), 'test'); + expect(result).toContain(' "my field": string;'); + }); + + it('quotes field names with brackets', () => { + const result = toTypeScriptDefinition(makeSchemaWithField('items[0]'), 'test'); + expect(result).toContain(' "items[0]": string;'); + }); + + it('escapes embedded double quotes in field names', () => { + const result = toTypeScriptDefinition(makeSchemaWithField('say"hi"'), 'test'); + expect(result).toContain(' "say\\"hi\\"": string;'); + }); + + it('escapes backslashes in field names', () => { + const result = toTypeScriptDefinition(makeSchemaWithField('back\\slash'), 'test'); + expect(result).toContain(' "back\\\\slash": string;'); + }); + + it('quotes field names that start with a digit', () => { + const result = toTypeScriptDefinition(makeSchemaWithField('123abc'), 'test'); + expect(result).toContain(' "123abc": string;'); + }); + + it('preserves optionality with quoted field names', () => { + const schema: JSONSchema = { + 'x-documentsInspected': 100, + properties: { + 'order-items': { + 'x-occurrence': 50, + anyOf: [ + { + type: 'string', + 'x-bsonType': 'string', + 'x-typeOccurrence': 50, + }, + ], + }, + }, + }; + + const result = toTypeScriptDefinition(schema, 'test'); + expect(result).toContain(' "order-items"?: string;'); + }); + }); }); diff --git a/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.ts b/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.ts index 92bef8a9c..588eb6987 100644 --- a/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.ts +++ b/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.ts @@ -42,6 +42,33 @@ function bsonTypeToTS(bsonType: string): string { return bsonToTypeScriptMap[bsonType] ?? 'unknown'; } +/** + * Matches valid JavaScript/TypeScript identifiers. + * A valid identifier starts with a letter, underscore, or dollar sign, + * followed by zero or more letters, digits, underscores, or dollar signs. + */ +const JS_IDENTIFIER_PATTERN = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/; + +/** + * Returns a safe TypeScript property name for use in interface definitions. + * If the name is a valid JS identifier, it is returned as-is. + * Otherwise, it is wrapped in double quotes with internal quotes and backslashes escaped. + * + * Examples: + * - "age" → "age" (valid identifier, unchanged) + * - "order-items" → '"order-items"' (dash) + * - "a.b" → '"a.b"' (dot) + * - "my field" → '"my field"' (space) + * - 'say"hi"' → '"say\\"hi\\""' (embedded quotes escaped) + */ +function safePropertyName(name: string): string { + if (JS_IDENTIFIER_PATTERN.test(name)) { + return name; + } + const escaped = name.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); + return `"${escaped}"`; +} + /** * Converts a collection name to PascalCase and appends "Document". * Examples: @@ -96,8 +123,9 @@ function renderProperties( const isOptional = isFieldOptional(propSchema, parentDocumentsInspected); const optionalMarker = isOptional ? '?' : ''; const tsType = resolveTypeString(propSchema, indentLevel); + const safeName = safePropertyName(propName); - lines.push(`${indent}${propName}${optionalMarker}: ${tsType};`); + lines.push(`${indent}${safeName}${optionalMarker}: ${tsType};`); } } From 3ca695103ca17a451de95719d4de6d331c5f952b Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Tue, 17 Feb 2026 11:35:57 +0100 Subject: [PATCH 13/25] refactor: extract schema-analyzer into standalone npm workspace package Move SchemaAnalyzer, JSONSchema types, BSONTypes, ValueFormatters, and getKnownFields into packages/schema-analyzer as @vscode-documentdb/schema-analyzer. - Set up npm workspaces (packages/*) and TS project references - Update all extension-side imports to use the new package - Configure Jest multi-project for both extension and package tests - Remove @vscode/l10n dependency from core (replaced with plain Error) - Fix strict-mode type issues (localeCompare bug, index signatures) - Update .gitignore to include root packages/ directory - Add packages/ to prettier glob --- .gitignore | 3 ++ jest.config.js | 16 +++++++--- l10n/bundle.l10n.json | 1 - package-lock.json | 19 ++++++++++++ package.json | 6 +++- packages/schema-analyzer/jest.config.js | 8 +++++ packages/schema-analyzer/package.json | 27 ++++++++++++++++ .../schema-analyzer/src}/BSONTypes.ts | 0 .../schema-analyzer/src}/JSONSchema.ts | 0 .../schema-analyzer/src}/SchemaAnalyzer.ts | 31 +++++++++---------- .../schema-analyzer/src}/ValueFormatters.ts | 0 .../schema-analyzer/src}/getKnownFields.ts | 2 +- packages/schema-analyzer/src/index.ts | 10 ++++++ .../test}/SchemaAnalyzer.arrayStats.test.ts | 14 ++++----- .../test}/SchemaAnalyzer.test.ts | 4 +-- .../test}/SchemaAnalyzer.versioning.test.ts | 4 +-- .../test}/mongoTestDocuments.ts | 0 packages/schema-analyzer/tsconfig.json | 20 ++++++++++++ packages/schema-analyzer/tsconfig.tsbuildinfo | 1 + src/documentdb/ClusterSession.ts | 9 ++++-- .../autocomplete/generateDescriptions.test.ts | 2 +- .../autocomplete/generateDescriptions.ts | 3 +- .../generateMongoFindJsonSchema.ts | 2 +- .../autocomplete/getKnownFields.test.ts | 3 +- .../toFieldCompletionItems.test.ts | 2 +- .../autocomplete/toFieldCompletionItems.ts | 3 +- .../toTypeScriptDefinition.test.ts | 2 +- .../autocomplete/toTypeScriptDefinition.ts | 3 +- src/utils/slickgrid/mongo/toSlickGridTable.ts | 3 +- src/utils/slickgrid/mongo/toSlickGridTree.ts | 3 +- .../collectionView/collectionViewRouter.ts | 2 +- tsconfig.json | 3 +- 32 files changed, 149 insertions(+), 57 deletions(-) create mode 100644 packages/schema-analyzer/jest.config.js create mode 100644 packages/schema-analyzer/package.json rename {src/utils/json/data-api => packages/schema-analyzer/src}/BSONTypes.ts (100%) rename {src/utils/json => packages/schema-analyzer/src}/JSONSchema.ts (100%) rename {src/utils/json/data-api => packages/schema-analyzer/src}/SchemaAnalyzer.ts (96%) rename {src/utils/json/data-api => packages/schema-analyzer/src}/ValueFormatters.ts (100%) rename {src/utils/json/data-api/autocomplete => packages/schema-analyzer/src}/getKnownFields.ts (99%) create mode 100644 packages/schema-analyzer/src/index.ts rename {src/utils/json/data-api => packages/schema-analyzer/test}/SchemaAnalyzer.arrayStats.test.ts (97%) rename {src/utils/json/data-api => packages/schema-analyzer/test}/SchemaAnalyzer.test.ts (99%) rename {src/utils/json/data-api => packages/schema-analyzer/test}/SchemaAnalyzer.versioning.test.ts (99%) rename {src/utils/json/data-api => packages/schema-analyzer/test}/mongoTestDocuments.ts (100%) create mode 100644 packages/schema-analyzer/tsconfig.json create mode 100644 packages/schema-analyzer/tsconfig.tsbuildinfo diff --git a/.gitignore b/.gitignore index ea5798951..a234ba0aa 100644 --- a/.gitignore +++ b/.gitignore @@ -160,6 +160,9 @@ PublishScripts/ **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ +# Include our monorepo packages at the root +!/packages/ +!/packages/** # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # NuGet v3's project.json files produces more ignoreable files diff --git a/jest.config.js b/jest.config.js index 7ad26361a..6db24cd52 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,11 +1,17 @@ /** @type {import('ts-jest').JestConfigWithTsJest} **/ module.exports = { - testEnvironment: 'node', - testMatch: ['/src/**/*.test.ts'], - transform: { - '^.+.tsx?$': ['ts-jest', {}], - }, // Limit workers to avoid OOM kills on machines with many cores. // Each ts-jest worker loads the TypeScript compiler and consumes ~500MB+. maxWorkers: '50%', + projects: [ + { + displayName: 'extension', + testEnvironment: 'node', + testMatch: ['/src/**/*.test.ts'], + transform: { + '^.+\\.tsx?$': ['ts-jest', {}], + }, + }, + '/packages/schema-analyzer', + ], }; diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index bd7aca008..ef1cd63d9 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -721,7 +721,6 @@ "No matching resources found.": "No matching resources found.", "No node selected.": "No node selected.", "No parent folder selected.": "No parent folder selected.", - "No properties found in the schema at path \"{0}\"": "No properties found in the schema at path \"{0}\"", "No public connectivity": "No public connectivity", "No result returned from the MongoDB shell.": "No result returned from the MongoDB shell.", "No results found": "No results found", diff --git a/package-lock.json b/package-lock.json index 68ba135c7..2a0a6f451 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,9 @@ "name": "vscode-documentdb", "version": "0.7.0", "license": "SEE LICENSE IN LICENSE.md", + "workspaces": [ + "packages/*" + ], "dependencies": { "@azure/arm-compute": "^22.4.0", "@azure/arm-cosmosdb": "~16.4.0", @@ -26,6 +29,7 @@ "@mongodb-js/explain-plan-helper": "1.4.24", "@trpc/client": "~11.10.0", "@trpc/server": "~11.10.0", + "@vscode-documentdb/schema-analyzer": "*", "@vscode/l10n": "~0.0.18", "antlr4ts": "^0.5.0-alpha.4", "bson": "~7.0.0", @@ -7307,6 +7311,10 @@ "win32" ] }, + "node_modules/@vscode-documentdb/schema-analyzer": { + "resolved": "packages/schema-analyzer", + "link": true + }, "node_modules/@vscode/extension-telemetry": { "version": "0.9.9", "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.9.9.tgz", @@ -22209,6 +22217,17 @@ "type": "github", "url": "https://github.com/sponsors/wooorm" } + }, + "packages/schema-analyzer": { + "name": "@vscode-documentdb/schema-analyzer", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "denque": "~2.1.0" + }, + "peerDependencies": { + "mongodb": ">=6.0.0" + } } } } diff --git a/package.json b/package.json index 9a87a3fe5..887ba0ecd 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,9 @@ "type": "git", "url": "https://github.com/microsoft/vscode-documentdb" }, + "workspaces": [ + "packages/*" + ], "main": "./main", "l10n": "./l10n", "activationEvents": [ @@ -67,7 +70,7 @@ "lint": "eslint --quiet .", "lint-fix": "eslint . --fix", "prettier": "prettier -c \"(src|test|l10n|grammar|docs)/**/*.@(js|ts|jsx|tsx|json)\" \"./*.@(js|ts|jsx|tsx|json)\"", - "prettier-fix": "prettier -w \"(src|test|l10n|grammar|docs)/**/*.@(js|ts|jsx|tsx|json)\" \"./*.@(js|ts|jsx|tsx|json)\"", + "prettier-fix": "prettier -w \"(src|test|l10n|grammar|docs|packages)/**/*.@(js|ts|jsx|tsx|json)\" \"./*.@(js|ts|jsx|tsx|json)\"", "pretest": "npm run build", "test": "vscode-test", "jesttest": "jest", @@ -165,6 +168,7 @@ "@trpc/client": "~11.10.0", "@trpc/server": "~11.10.0", "@vscode/l10n": "~0.0.18", + "@vscode-documentdb/schema-analyzer": "*", "antlr4ts": "^0.5.0-alpha.4", "bson": "~7.0.0", "denque": "~2.1.0", diff --git a/packages/schema-analyzer/jest.config.js b/packages/schema-analyzer/jest.config.js new file mode 100644 index 000000000..388d1e1d1 --- /dev/null +++ b/packages/schema-analyzer/jest.config.js @@ -0,0 +1,8 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} **/ +module.exports = { + testEnvironment: 'node', + testMatch: ['/test/**/*.test.ts'], + transform: { + '^.+\\.tsx?$': ['ts-jest', {}], + }, +}; diff --git a/packages/schema-analyzer/package.json b/packages/schema-analyzer/package.json new file mode 100644 index 000000000..f5b10840b --- /dev/null +++ b/packages/schema-analyzer/package.json @@ -0,0 +1,27 @@ +{ + "name": "@vscode-documentdb/schema-analyzer", + "version": "0.1.0", + "description": "Incremental JSON Schema analyzer for MongoDB/DocumentDB documents with statistical extensions", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "tsc -p .", + "clean": "rimraf dist", + "test": "jest --config jest.config.js" + }, + "repository": { + "type": "git", + "url": "https://github.com/microsoft/vscode-documentdb", + "directory": "packages/schema-analyzer" + }, + "license": "MIT", + "peerDependencies": { + "mongodb": ">=6.0.0" + }, + "dependencies": { + "denque": "~2.1.0" + } +} diff --git a/src/utils/json/data-api/BSONTypes.ts b/packages/schema-analyzer/src/BSONTypes.ts similarity index 100% rename from src/utils/json/data-api/BSONTypes.ts rename to packages/schema-analyzer/src/BSONTypes.ts diff --git a/src/utils/json/JSONSchema.ts b/packages/schema-analyzer/src/JSONSchema.ts similarity index 100% rename from src/utils/json/JSONSchema.ts rename to packages/schema-analyzer/src/JSONSchema.ts diff --git a/src/utils/json/data-api/SchemaAnalyzer.ts b/packages/schema-analyzer/src/SchemaAnalyzer.ts similarity index 96% rename from src/utils/json/data-api/SchemaAnalyzer.ts rename to packages/schema-analyzer/src/SchemaAnalyzer.ts index 93b73a36a..b26f6fa76 100644 --- a/src/utils/json/data-api/SchemaAnalyzer.ts +++ b/packages/schema-analyzer/src/SchemaAnalyzer.ts @@ -3,13 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as l10n from '@vscode/l10n'; import { assert } from 'console'; import Denque from 'denque'; import { type Document, type WithId } from 'mongodb'; -import { type JSONSchema } from '../JSONSchema'; import { BSONTypes } from './BSONTypes'; -import { type FieldEntry, getKnownFields as getKnownFieldsFromSchema } from './autocomplete/getKnownFields'; +import { type JSONSchema, type JSONSchemaRef } from './JSONSchema'; +import { type FieldEntry, getKnownFields as getKnownFieldsFromSchema } from './getKnownFields'; /** * Incremental schema analyzer for documents from the MongoDB API / DocumentDB API. @@ -369,11 +368,12 @@ function findTypeEntry(anyOfArray: JSONSchema[], bsonType: BSONTypes): JSONSchem * Helper function to update min and max stats */ function updateMinMaxStats(schema: JSONSchema, minKey: string, maxKey: string, value: number): void { - if (schema[minKey] === undefined || value < schema[minKey]) { - schema[minKey] = value; + const record = schema as Record; + if (record[minKey] === undefined || value < (record[minKey] as number)) { + record[minKey] = value; } - if (schema[maxKey] === undefined || value > schema[maxKey]) { - schema[maxKey] = value; + if (record[maxKey] === undefined || value > (record[maxKey] as number)) { + record[maxKey] = value; } } @@ -534,17 +534,12 @@ function aggregateStatsForValue(value: unknown, mongoType: BSONTypes, propertyTy } } -function getSchemaAtPath(schema: JSONSchema, path: string[]): JSONSchema { - let currentNode = schema; +function getSchemaAtPath(schema: JSONSchema, path: string[]): JSONSchema | undefined { + let currentNode: JSONSchema | undefined = schema; for (let i = 0; i < path.length; i++) { const key = path[i]; - // If the current node is an array, we should move to its `items` - // if (currentNode.type === 'array' && currentNode.items) { - // currentNode = currentNode.items; - // } - // Move to the next property in the schema if (currentNode && currentNode.properties && currentNode.properties[key]) { const nextNode: JSONSchema = currentNode.properties[key] as JSONSchema; @@ -553,13 +548,15 @@ function getSchemaAtPath(schema: JSONSchema, path: string[]): JSONSchema { * We're looking at the "Object"-one, because these have the properties we're interested in. */ if (nextNode.anyOf && nextNode.anyOf.length > 0) { - currentNode = nextNode.anyOf.find((entry: JSONSchema) => entry.type === 'object') as JSONSchema; + currentNode = nextNode.anyOf.find( + (entry: JSONSchemaRef): entry is JSONSchema => typeof entry === 'object' && entry.type === 'object', + ); } else { // we can't continue, as we're missing the next node, we abort at the last node we managed to extract return currentNode; } } else { - throw new Error(l10n.t('No properties found in the schema at path "{0}"', path.slice(0, i + 1).join('/'))); + throw new Error(`No properties found in the schema at path "${path.slice(0, i + 1).join('/')}"`); } } @@ -570,7 +567,7 @@ export function getPropertyNamesAtLevel(jsonSchema: JSONSchema, path: string[]): const headers = new Set(); // Explore the schema and apply the callback to collect headers at the specified path - const selectedSchema: JSONSchema = getSchemaAtPath(jsonSchema, path); + const selectedSchema = getSchemaAtPath(jsonSchema, path); if (selectedSchema && selectedSchema.properties) { Object.keys(selectedSchema.properties).forEach((key) => { diff --git a/src/utils/json/data-api/ValueFormatters.ts b/packages/schema-analyzer/src/ValueFormatters.ts similarity index 100% rename from src/utils/json/data-api/ValueFormatters.ts rename to packages/schema-analyzer/src/ValueFormatters.ts diff --git a/src/utils/json/data-api/autocomplete/getKnownFields.ts b/packages/schema-analyzer/src/getKnownFields.ts similarity index 99% rename from src/utils/json/data-api/autocomplete/getKnownFields.ts rename to packages/schema-analyzer/src/getKnownFields.ts index 63a53fc99..d3f12ee89 100644 --- a/src/utils/json/data-api/autocomplete/getKnownFields.ts +++ b/packages/schema-analyzer/src/getKnownFields.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import Denque from 'denque'; -import { type JSONSchema } from '../../JSONSchema'; +import { type JSONSchema } from './JSONSchema'; export interface FieldEntry { /** Dot-notated path (e.g., "user.profile.name") */ diff --git a/packages/schema-analyzer/src/index.ts b/packages/schema-analyzer/src/index.ts new file mode 100644 index 000000000..871fd61f8 --- /dev/null +++ b/packages/schema-analyzer/src/index.ts @@ -0,0 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export { BSONTypes } from './BSONTypes'; +export { getKnownFields, type FieldEntry } from './getKnownFields'; +export { type JSONSchema, type JSONSchemaMap, type JSONSchemaRef } from './JSONSchema'; +export { SchemaAnalyzer, buildFullPaths, getPropertyNamesAtLevel } from './SchemaAnalyzer'; +export { valueToDisplayString } from './ValueFormatters'; diff --git a/src/utils/json/data-api/SchemaAnalyzer.arrayStats.test.ts b/packages/schema-analyzer/test/SchemaAnalyzer.arrayStats.test.ts similarity index 97% rename from src/utils/json/data-api/SchemaAnalyzer.arrayStats.test.ts rename to packages/schema-analyzer/test/SchemaAnalyzer.arrayStats.test.ts index 00f53d98f..2669d5214 100644 --- a/src/utils/json/data-api/SchemaAnalyzer.arrayStats.test.ts +++ b/packages/schema-analyzer/test/SchemaAnalyzer.arrayStats.test.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { ObjectId, type Document, type WithId } from 'mongodb'; -import { type JSONSchema } from '../JSONSchema'; -import { SchemaAnalyzer } from './SchemaAnalyzer'; +import { type JSONSchema } from '../src/JSONSchema'; +import { SchemaAnalyzer } from '../src/SchemaAnalyzer'; /** * This test file investigates the array element occurrence/stats problem. @@ -158,19 +158,19 @@ describe('Array element occurrence analysis', () => { (e) => (e as JSONSchema)['x-bsonType'] === 'object', ) as JSONSchema; - const props = objEntry.properties as JSONSchema; + const props = objEntry.properties as Record; // "name" appeared in all 3 object elements - expect((props['name'] as JSONSchema)['x-occurrence']).toBe(3); + expect(props['name']['x-occurrence']).toBe(3); // "price" appeared in 2 of 3 object elements - expect((props['price'] as JSONSchema)['x-occurrence']).toBe(2); + expect(props['price']['x-occurrence']).toBe(2); // "discount" appeared in 1 of 3 object elements - expect((props['discount'] as JSONSchema)['x-occurrence']).toBe(1); + expect(props['discount']['x-occurrence']).toBe(1); // "weight" appeared in 1 of 3 object elements - expect((props['weight'] as JSONSchema)['x-occurrence']).toBe(1); + expect(props['weight']['x-occurrence']).toBe(1); // Total object elements = 3 (2 from doc1 + 1 from doc2) expect(objEntry['x-typeOccurrence']).toBe(3); diff --git a/src/utils/json/data-api/SchemaAnalyzer.test.ts b/packages/schema-analyzer/test/SchemaAnalyzer.test.ts similarity index 99% rename from src/utils/json/data-api/SchemaAnalyzer.test.ts rename to packages/schema-analyzer/test/SchemaAnalyzer.test.ts index cb2b362c7..f23a97bdf 100644 --- a/src/utils/json/data-api/SchemaAnalyzer.test.ts +++ b/packages/schema-analyzer/test/SchemaAnalyzer.test.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { type JSONSchema, type JSONSchemaMap, type JSONSchemaRef } from '../JSONSchema'; -import { getPropertyNamesAtLevel, SchemaAnalyzer } from './SchemaAnalyzer'; +import { type JSONSchema, type JSONSchemaMap, type JSONSchemaRef } from '../src/JSONSchema'; +import { getPropertyNamesAtLevel, SchemaAnalyzer } from '../src/SchemaAnalyzer'; import { arraysWithDifferentDataTypes, complexDocument, diff --git a/src/utils/json/data-api/SchemaAnalyzer.versioning.test.ts b/packages/schema-analyzer/test/SchemaAnalyzer.versioning.test.ts similarity index 99% rename from src/utils/json/data-api/SchemaAnalyzer.versioning.test.ts rename to packages/schema-analyzer/test/SchemaAnalyzer.versioning.test.ts index 91a6fbddc..38ef144a6 100644 --- a/src/utils/json/data-api/SchemaAnalyzer.versioning.test.ts +++ b/packages/schema-analyzer/test/SchemaAnalyzer.versioning.test.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { ObjectId, type Document, type WithId } from 'mongodb'; -import { type JSONSchema } from '../JSONSchema'; -import { SchemaAnalyzer } from './SchemaAnalyzer'; +import { type JSONSchema } from '../src/JSONSchema'; +import { SchemaAnalyzer } from '../src/SchemaAnalyzer'; // ------------------------------------------------------------------ // Test fixtures diff --git a/src/utils/json/data-api/mongoTestDocuments.ts b/packages/schema-analyzer/test/mongoTestDocuments.ts similarity index 100% rename from src/utils/json/data-api/mongoTestDocuments.ts rename to packages/schema-analyzer/test/mongoTestDocuments.ts diff --git a/packages/schema-analyzer/tsconfig.json b/packages/schema-analyzer/tsconfig.json new file mode 100644 index 000000000..8688f97ff --- /dev/null +++ b/packages/schema-analyzer/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "composite": true, + "declaration": true, + "declarationMap": true, + "module": "commonjs", + "target": "ES2023", + "lib": ["ES2023"], + "rootDir": "./src", + "outDir": "./dist", + "strict": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "sourceMap": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/schema-analyzer/tsconfig.tsbuildinfo b/packages/schema-analyzer/tsconfig.tsbuildinfo new file mode 100644 index 000000000..bfaffb0b6 --- /dev/null +++ b/packages/schema-analyzer/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"fileNames":["../../node_modules/typescript/lib/lib.es5.d.ts","../../node_modules/typescript/lib/lib.es2015.d.ts","../../node_modules/typescript/lib/lib.es2016.d.ts","../../node_modules/typescript/lib/lib.es2017.d.ts","../../node_modules/typescript/lib/lib.es2018.d.ts","../../node_modules/typescript/lib/lib.es2019.d.ts","../../node_modules/typescript/lib/lib.es2020.d.ts","../../node_modules/typescript/lib/lib.es2021.d.ts","../../node_modules/typescript/lib/lib.es2022.d.ts","../../node_modules/typescript/lib/lib.es2023.d.ts","../../node_modules/typescript/lib/lib.es2015.core.d.ts","../../node_modules/typescript/lib/lib.es2015.collection.d.ts","../../node_modules/typescript/lib/lib.es2015.generator.d.ts","../../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.promise.d.ts","../../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../node_modules/typescript/lib/lib.es2016.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../node_modules/typescript/lib/lib.es2017.date.d.ts","../../node_modules/typescript/lib/lib.es2017.object.d.ts","../../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2017.string.d.ts","../../node_modules/typescript/lib/lib.es2017.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../node_modules/typescript/lib/lib.es2018.intl.d.ts","../../node_modules/typescript/lib/lib.es2018.promise.d.ts","../../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../node_modules/typescript/lib/lib.es2019.array.d.ts","../../node_modules/typescript/lib/lib.es2019.object.d.ts","../../node_modules/typescript/lib/lib.es2019.string.d.ts","../../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../node_modules/typescript/lib/lib.es2019.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../node_modules/typescript/lib/lib.es2020.date.d.ts","../../node_modules/typescript/lib/lib.es2020.promise.d.ts","../../node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2020.string.d.ts","../../node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2020.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.number.d.ts","../../node_modules/typescript/lib/lib.es2021.promise.d.ts","../../node_modules/typescript/lib/lib.es2021.string.d.ts","../../node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../node_modules/typescript/lib/lib.es2021.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.array.d.ts","../../node_modules/typescript/lib/lib.es2022.error.d.ts","../../node_modules/typescript/lib/lib.es2022.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.object.d.ts","../../node_modules/typescript/lib/lib.es2022.string.d.ts","../../node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../node_modules/typescript/lib/lib.es2023.array.d.ts","../../node_modules/typescript/lib/lib.es2023.collection.d.ts","../../node_modules/typescript/lib/lib.es2023.intl.d.ts","../../node_modules/typescript/lib/lib.esnext.disposable.d.ts","../../node_modules/typescript/lib/lib.decorators.d.ts","../../node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../node_modules/bson/bson.d.ts","../../node_modules/mongodb/mongodb.d.ts","./src/BSONTypes.ts","./src/JSONSchema.ts","../../node_modules/denque/index.d.ts","./src/getKnownFields.ts","./src/SchemaAnalyzer.ts","./src/ValueFormatters.ts","./src/index.ts","../../node_modules/@babel/types/lib/index.d.ts","../../node_modules/@types/babel__generator/index.d.ts","../../node_modules/@babel/parser/typings/babel-parser.d.ts","../../node_modules/@types/babel__template/index.d.ts","../../node_modules/@types/babel__traverse/index.d.ts","../../node_modules/@types/babel__core/index.d.ts","../../node_modules/@types/node/compatibility/disposable.d.ts","../../node_modules/@types/node/compatibility/indexable.d.ts","../../node_modules/@types/node/compatibility/iterators.d.ts","../../node_modules/@types/node/compatibility/index.d.ts","../../node_modules/@types/node/globals.typedarray.d.ts","../../node_modules/@types/node/buffer.buffer.d.ts","../../node_modules/@types/node/globals.d.ts","../../node_modules/@types/node/web-globals/abortcontroller.d.ts","../../node_modules/@types/node/web-globals/domexception.d.ts","../../node_modules/@types/node/web-globals/events.d.ts","../../node_modules/buffer/index.d.ts","../../node_modules/undici-types/header.d.ts","../../node_modules/undici-types/readable.d.ts","../../node_modules/undici-types/file.d.ts","../../node_modules/undici-types/fetch.d.ts","../../node_modules/undici-types/formdata.d.ts","../../node_modules/undici-types/connector.d.ts","../../node_modules/undici-types/client.d.ts","../../node_modules/undici-types/errors.d.ts","../../node_modules/undici-types/dispatcher.d.ts","../../node_modules/undici-types/global-dispatcher.d.ts","../../node_modules/undici-types/global-origin.d.ts","../../node_modules/undici-types/pool-stats.d.ts","../../node_modules/undici-types/pool.d.ts","../../node_modules/undici-types/handlers.d.ts","../../node_modules/undici-types/balanced-pool.d.ts","../../node_modules/undici-types/agent.d.ts","../../node_modules/undici-types/mock-interceptor.d.ts","../../node_modules/undici-types/mock-agent.d.ts","../../node_modules/undici-types/mock-client.d.ts","../../node_modules/undici-types/mock-pool.d.ts","../../node_modules/undici-types/mock-errors.d.ts","../../node_modules/undici-types/proxy-agent.d.ts","../../node_modules/undici-types/env-http-proxy-agent.d.ts","../../node_modules/undici-types/retry-handler.d.ts","../../node_modules/undici-types/retry-agent.d.ts","../../node_modules/undici-types/api.d.ts","../../node_modules/undici-types/interceptors.d.ts","../../node_modules/undici-types/util.d.ts","../../node_modules/undici-types/cookies.d.ts","../../node_modules/undici-types/patch.d.ts","../../node_modules/undici-types/websocket.d.ts","../../node_modules/undici-types/eventsource.d.ts","../../node_modules/undici-types/filereader.d.ts","../../node_modules/undici-types/diagnostics-channel.d.ts","../../node_modules/undici-types/content-type.d.ts","../../node_modules/undici-types/cache.d.ts","../../node_modules/undici-types/index.d.ts","../../node_modules/@types/node/web-globals/fetch.d.ts","../../node_modules/@types/node/web-globals/navigator.d.ts","../../node_modules/@types/node/web-globals/storage.d.ts","../../node_modules/@types/node/assert.d.ts","../../node_modules/@types/node/assert/strict.d.ts","../../node_modules/@types/node/async_hooks.d.ts","../../node_modules/@types/node/buffer.d.ts","../../node_modules/@types/node/child_process.d.ts","../../node_modules/@types/node/cluster.d.ts","../../node_modules/@types/node/console.d.ts","../../node_modules/@types/node/constants.d.ts","../../node_modules/@types/node/crypto.d.ts","../../node_modules/@types/node/dgram.d.ts","../../node_modules/@types/node/diagnostics_channel.d.ts","../../node_modules/@types/node/dns.d.ts","../../node_modules/@types/node/dns/promises.d.ts","../../node_modules/@types/node/domain.d.ts","../../node_modules/@types/node/events.d.ts","../../node_modules/@types/node/fs.d.ts","../../node_modules/@types/node/fs/promises.d.ts","../../node_modules/@types/node/http.d.ts","../../node_modules/@types/node/http2.d.ts","../../node_modules/@types/node/https.d.ts","../../node_modules/@types/node/inspector.generated.d.ts","../../node_modules/@types/node/module.d.ts","../../node_modules/@types/node/net.d.ts","../../node_modules/@types/node/os.d.ts","../../node_modules/@types/node/path.d.ts","../../node_modules/@types/node/perf_hooks.d.ts","../../node_modules/@types/node/process.d.ts","../../node_modules/@types/node/punycode.d.ts","../../node_modules/@types/node/querystring.d.ts","../../node_modules/@types/node/readline.d.ts","../../node_modules/@types/node/readline/promises.d.ts","../../node_modules/@types/node/repl.d.ts","../../node_modules/@types/node/sea.d.ts","../../node_modules/@types/node/sqlite.d.ts","../../node_modules/@types/node/stream.d.ts","../../node_modules/@types/node/stream/promises.d.ts","../../node_modules/@types/node/stream/consumers.d.ts","../../node_modules/@types/node/stream/web.d.ts","../../node_modules/@types/node/string_decoder.d.ts","../../node_modules/@types/node/test.d.ts","../../node_modules/@types/node/timers.d.ts","../../node_modules/@types/node/timers/promises.d.ts","../../node_modules/@types/node/tls.d.ts","../../node_modules/@types/node/trace_events.d.ts","../../node_modules/@types/node/tty.d.ts","../../node_modules/@types/node/url.d.ts","../../node_modules/@types/node/util.d.ts","../../node_modules/@types/node/v8.d.ts","../../node_modules/@types/node/vm.d.ts","../../node_modules/@types/node/wasi.d.ts","../../node_modules/@types/node/worker_threads.d.ts","../../node_modules/@types/node/zlib.d.ts","../../node_modules/@types/node/index.d.ts","../../node_modules/@types/connect/index.d.ts","../../node_modules/@types/body-parser/index.d.ts","../../node_modules/@types/bonjour/index.d.ts","../../node_modules/@types/send/index.d.ts","../../node_modules/@types/qs/index.d.ts","../../node_modules/@types/range-parser/index.d.ts","../../node_modules/@types/express-serve-static-core/index.d.ts","../../node_modules/@types/connect-history-api-fallback/index.d.ts","../../node_modules/@types/ms/index.d.ts","../../node_modules/@types/debug/index.d.ts","../../node_modules/@types/documentdb/index.d.ts","../../node_modules/@types/estree/index.d.ts","../../node_modules/@types/json-schema/index.d.ts","../../node_modules/@types/eslint/use-at-your-own-risk.d.ts","../../node_modules/@types/eslint/index.d.ts","../../node_modules/@eslint/core/dist/esm/types.d.ts","../../node_modules/eslint/lib/types/use-at-your-own-risk.d.ts","../../node_modules/eslint/lib/types/index.d.ts","../../node_modules/@types/eslint-scope/index.d.ts","../../node_modules/@types/estree-jsx/index.d.ts","../../node_modules/@types/http-errors/index.d.ts","../../node_modules/@types/mime/index.d.ts","../../node_modules/@types/serve-static/node_modules/@types/send/index.d.ts","../../node_modules/@types/serve-static/index.d.ts","../../node_modules/@types/express/index.d.ts","../../node_modules/@types/unist/index.d.ts","../../node_modules/@types/hast/index.d.ts","../../node_modules/@types/http-cache-semantics/index.d.ts","../../node_modules/@types/http-proxy/index.d.ts","../../node_modules/@types/istanbul-lib-coverage/index.d.ts","../../node_modules/@types/istanbul-lib-report/index.d.ts","../../node_modules/@types/istanbul-reports/index.d.ts","../../node_modules/@jest/expect-utils/build/index.d.ts","../../node_modules/chalk/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/symbols/symbols.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/symbols/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/any/any.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/any/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/mapped/mapped-key.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/mapped/mapped-result.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/async-iterator/async-iterator.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/async-iterator/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/readonly/readonly.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/readonly/readonly-from-mapped-result.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/readonly/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/readonly-optional/readonly-optional.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/readonly-optional/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/constructor/constructor.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/constructor/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/literal/literal.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/literal/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/enum/enum.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/enum/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/function/function.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/function/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/computed/computed.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/computed/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/never/never.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/never/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/intersect/intersect-type.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/intersect/intersect-evaluated.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/intersect/intersect.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/intersect/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/union/union-type.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/union/union-evaluated.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/union/union.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/union/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/recursive/recursive.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/recursive/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/unsafe/unsafe.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/unsafe/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/ref/ref.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/ref/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/tuple/tuple.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/tuple/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/error/error.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/error/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/string/string.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/string/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/boolean/boolean.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/boolean/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/number/number.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/number/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/integer/integer.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/integer/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/bigint/bigint.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/bigint/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/template-literal/parse.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/template-literal/finite.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/template-literal/generate.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/template-literal/syntax.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/template-literal/pattern.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/template-literal/template-literal.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/template-literal/union.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/template-literal/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/indexed/indexed-property-keys.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/indexed/indexed-from-mapped-result.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/indexed/indexed.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/indexed/indexed-from-mapped-key.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/indexed/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/iterator/iterator.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/iterator/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/promise/promise.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/promise/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/sets/set.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/sets/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/mapped/mapped.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/mapped/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/optional/optional.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/optional/optional-from-mapped-result.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/optional/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/awaited/awaited.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/awaited/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/keyof/keyof-property-keys.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/keyof/keyof.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/keyof/keyof-from-mapped-result.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/keyof/keyof-property-entries.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/keyof/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/omit/omit-from-mapped-result.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/omit/omit.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/omit/omit-from-mapped-key.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/omit/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/pick/pick-from-mapped-result.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/pick/pick.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/pick/pick-from-mapped-key.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/pick/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/null/null.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/null/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/symbol/symbol.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/symbol/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/undefined/undefined.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/undefined/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/partial/partial.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/partial/partial-from-mapped-result.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/partial/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/regexp/regexp.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/regexp/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/record/record.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/record/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/required/required.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/required/required-from-mapped-result.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/required/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/transform/transform.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/transform/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/module/compute.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/module/infer.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/module/module.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/module/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/not/not.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/not/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/static/static.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/static/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/object/object.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/object/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/helpers/helpers.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/helpers/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/array/array.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/array/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/date/date.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/date/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/uint8array/uint8array.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/uint8array/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/unknown/unknown.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/unknown/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/void/void.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/void/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/schema/schema.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/schema/anyschema.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/schema/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/clone/type.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/clone/value.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/clone/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/create/type.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/create/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/argument/argument.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/argument/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/guard/kind.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/guard/type.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/guard/value.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/guard/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/patterns/patterns.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/patterns/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/registry/format.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/registry/type.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/registry/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/composite/composite.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/composite/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/const/const.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/const/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/constructor-parameters/constructor-parameters.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/constructor-parameters/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/exclude/exclude-from-template-literal.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/exclude/exclude.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/exclude/exclude-from-mapped-result.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/exclude/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/extends/extends-check.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/extends/extends-from-mapped-result.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/extends/extends.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/extends/extends-from-mapped-key.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/extends/extends-undefined.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/extends/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/extract/extract-from-template-literal.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/extract/extract.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/extract/extract-from-mapped-result.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/extract/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/instance-type/instance-type.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/instance-type/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/instantiate/instantiate.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/instantiate/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/intrinsic/intrinsic-from-mapped-key.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/intrinsic/intrinsic.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/intrinsic/capitalize.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/intrinsic/lowercase.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/intrinsic/uncapitalize.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/intrinsic/uppercase.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/intrinsic/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/parameters/parameters.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/parameters/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/rest/rest.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/rest/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/return-type/return-type.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/return-type/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/type/json.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/type/javascript.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/type/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/index.d.ts","../../node_modules/@jest/schemas/build/index.d.ts","../../node_modules/pretty-format/build/index.d.ts","../../node_modules/jest-diff/build/index.d.ts","../../node_modules/jest-matcher-utils/build/index.d.ts","../../node_modules/jest-mock/build/index.d.ts","../../node_modules/expect/build/index.d.ts","../../node_modules/@types/jest/index.d.ts","../../node_modules/@types/json5/index.d.ts","../../node_modules/@types/mdast/index.d.ts","../../node_modules/@types/mocha/index.d.ts","../../node_modules/@types/node-forge/index.d.ts","../../node_modules/@types/normalize-package-data/index.d.ts","../../node_modules/@types/react/global.d.ts","../../node_modules/csstype/index.d.ts","../../node_modules/@types/react/index.d.ts","../../node_modules/@types/react-dom/index.d.ts","../../node_modules/@types/retry/index.d.ts","../../node_modules/@types/sarif/index.d.ts","../../node_modules/@types/semver/functions/inc.d.ts","../../node_modules/@types/semver/classes/semver.d.ts","../../node_modules/@types/semver/functions/parse.d.ts","../../node_modules/@types/semver/functions/valid.d.ts","../../node_modules/@types/semver/functions/clean.d.ts","../../node_modules/@types/semver/functions/diff.d.ts","../../node_modules/@types/semver/functions/major.d.ts","../../node_modules/@types/semver/functions/minor.d.ts","../../node_modules/@types/semver/functions/patch.d.ts","../../node_modules/@types/semver/functions/prerelease.d.ts","../../node_modules/@types/semver/functions/compare.d.ts","../../node_modules/@types/semver/functions/rcompare.d.ts","../../node_modules/@types/semver/functions/compare-loose.d.ts","../../node_modules/@types/semver/functions/compare-build.d.ts","../../node_modules/@types/semver/functions/sort.d.ts","../../node_modules/@types/semver/functions/rsort.d.ts","../../node_modules/@types/semver/functions/gt.d.ts","../../node_modules/@types/semver/functions/lt.d.ts","../../node_modules/@types/semver/functions/eq.d.ts","../../node_modules/@types/semver/functions/neq.d.ts","../../node_modules/@types/semver/functions/gte.d.ts","../../node_modules/@types/semver/functions/lte.d.ts","../../node_modules/@types/semver/functions/cmp.d.ts","../../node_modules/@types/semver/functions/coerce.d.ts","../../node_modules/@types/semver/classes/comparator.d.ts","../../node_modules/@types/semver/classes/range.d.ts","../../node_modules/@types/semver/functions/satisfies.d.ts","../../node_modules/@types/semver/ranges/max-satisfying.d.ts","../../node_modules/@types/semver/ranges/min-satisfying.d.ts","../../node_modules/@types/semver/ranges/to-comparators.d.ts","../../node_modules/@types/semver/ranges/min-version.d.ts","../../node_modules/@types/semver/ranges/valid.d.ts","../../node_modules/@types/semver/ranges/outside.d.ts","../../node_modules/@types/semver/ranges/gtr.d.ts","../../node_modules/@types/semver/ranges/ltr.d.ts","../../node_modules/@types/semver/ranges/intersects.d.ts","../../node_modules/@types/semver/ranges/simplify.d.ts","../../node_modules/@types/semver/ranges/subset.d.ts","../../node_modules/@types/semver/internals/identifiers.d.ts","../../node_modules/@types/semver/index.d.ts","../../node_modules/@types/serve-index/index.d.ts","../../node_modules/@types/sockjs/index.d.ts","../../node_modules/@types/sortablejs/plugins.d.ts","../../node_modules/@types/sortablejs/index.d.ts","../../node_modules/@types/stack-utils/index.d.ts","../../node_modules/@types/trusted-types/lib/index.d.ts","../../node_modules/@types/trusted-types/index.d.ts","../../node_modules/@types/uuid/index.d.ts","../../node_modules/@types/vscode/index.d.ts","../../node_modules/@types/vscode-webview/index.d.ts","../../node_modules/@types/webidl-conversions/index.d.ts","../../node_modules/@types/whatwg-url/index.d.ts","../../node_modules/@types/ws/index.d.ts","../../node_modules/@types/yargs-parser/index.d.ts","../../node_modules/@types/yargs/index.d.ts"],"fileIdsList":[[72,83,132],[83,132],[83,132,194],[83,132,407],[83,132,217,219,223,226,228,230,232,234,236,240,244,248,250,252,254,256,258,260,262,264,266,268,276,281,283,285,287,289,292,294,299,303,307,309,311,313,316,318,320,323,325,329,331,333,335,337,339,341,343,345,347,350,353,355,357,361,363,366,368,370,372,376,382,386,388,390,397,399,401,403,406],[83,132,217,350],[83,132,218],[83,132,356],[83,132,217,333,337,350],[83,132,338],[83,132,217,333,350],[83,132,222],[83,132,238,244,248,254,285,337,350],[83,132,293],[83,132,267],[83,132,261],[83,132,351,352],[83,132,350],[83,132,240,244,281,287,299,335,337,350],[83,132,367],[83,132,216,350],[83,132,237],[83,132,219,226,232,236,240,256,268,309,311,313,335,337,341,343,345,350],[83,132,369],[83,132,230,240,256,350],[83,132,371],[83,132,217,226,228,292,333,337,350],[83,132,229],[83,132,354],[83,132,348],[83,132,340],[83,132,217,232,350],[83,132,233],[83,132,257],[83,132,289,335,350,374],[83,132,276,350,374],[83,132,240,248,276,289,333,337,350,373,375],[83,132,373,374,375],[83,132,258,350],[83,132,232,289,335,337,350,379],[83,132,289,335,350,379],[83,132,248,289,333,337,350,378,380],[83,132,377,378,379,380,381],[83,132,289,335,350,384],[83,132,276,350,384],[83,132,240,248,276,289,333,337,350,383,385],[83,132,383,384,385],[83,132,235],[83,132,358,359,360],[83,132,217,219,223,226,230,232,236,238,240,244,248,250,252,254,256,260,262,264,266,268,276,283,285,289,292,309,311,313,318,320,325,329,331,335,339,341,343,345,347,350,357],[83,132,217,219,223,226,230,232,236,238,240,244,248,250,252,254,256,258,260,262,264,266,268,276,283,285,289,292,309,311,313,318,320,325,329,331,335,339,341,343,345,347,350,357],[83,132,240,335,350],[83,132,336],[83,132,277,278,279,280],[83,132,279,289,335,337,350],[83,132,277,281,289,335,350],[83,132,232,248,264,266,276,350],[83,132,238,240,244,248,250,254,256,277,278,280,289,335,337,339,350],[83,132,387],[83,132,230,240,350],[83,132,389],[83,132,223,226,228,230,236,244,248,256,283,285,292,320,335,339,345,350,357],[83,132,265],[83,132,241,242,243],[83,132,226,240,241,292,350],[83,132,240,241,350],[83,132,350,392],[83,132,391,392,393,394,395,396],[83,132,232,289,335,337,350,392],[83,132,232,248,276,289,350,391],[83,132,282],[83,132,295,296,297,298],[83,132,289,296,335,337,350],[83,132,244,248,250,256,287,335,337,339,350],[83,132,232,238,248,254,264,289,295,297,337,350],[83,132,231],[83,132,220,221,288],[83,132,217,335,350],[83,132,220,221,223,226,230,232,234,236,244,248,256,281,283,285,287,292,335,337,339,350],[83,132,223,226,230,234,236,238,240,244,248,254,256,281,283,292,294,299,303,307,316,320,323,325,335,337,339,350],[83,132,328],[83,132,223,226,230,234,236,244,248,250,254,256,283,292,320,333,335,337,339,350],[83,132,217,326,327,333,335,350],[83,132,239],[83,132,330],[83,132,308],[83,132,263],[83,132,334],[83,132,217,226,292,333,337,350],[83,132,300,301,302],[83,132,289,301,335,350],[83,132,289,301,335,337,350],[83,132,232,238,244,248,250,254,281,289,300,302,335,337,350],[83,132,290,291],[83,132,289,290,335],[83,132,217,289,291,337,350],[83,132,398],[83,132,236,240,256,350],[83,132,314,315],[83,132,289,314,335,337,350],[83,132,226,228,232,238,244,248,250,254,260,262,264,266,268,289,292,309,311,313,315,335,337,350],[83,132,362],[83,132,304,305,306],[83,132,289,305,335,350],[83,132,289,305,335,337,350],[83,132,232,238,244,248,250,254,281,289,304,306,335,337,350],[83,132,284],[83,132,227],[83,132,226,292,350],[83,132,224,225],[83,132,224,289,335],[83,132,217,225,289,337,350],[83,132,319],[83,132,217,219,232,234,240,248,260,262,264,266,276,318,333,335,337,350],[83,132,249],[83,132,253],[83,132,217,252,333,350],[83,132,317],[83,132,364,365],[83,132,321,322],[83,132,289,321,335,337,350],[83,132,226,228,232,238,244,248,250,254,260,262,264,266,268,289,292,309,311,313,322,335,337,350],[83,132,400],[83,132,244,248,256,350],[83,132,402],[83,132,236,240,350],[83,132,219,223,230,232,234,236,244,248,250,254,256,260,262,264,266,268,276,283,285,309,311,313,318,320,331,335,339,341,343,345,347,348],[83,132,348,349],[83,132,217],[83,132,286],[83,132,332],[83,132,223,226,230,234,236,240,244,248,250,252,254,256,283,285,292,320,325,329,331,335,337,339,350],[83,132,259],[83,132,310],[83,132,216],[83,132,232,248,258,260,262,264,266,268,269,276],[83,132,232,248,258,262,269,270,276,337],[83,132,269,270,271,272,273,274,275],[83,132,258],[83,132,258,276],[83,132,232,248,260,262,264,268,276,337],[83,132,217,232,240,248,260,262,264,266,268,272,333,337,350],[83,132,232,248,274,333,337],[83,132,324],[83,132,255],[83,132,404,405],[83,132,223,230,236,268,283,285,294,311,313,318,341,343,347,350,357,372,388,390,399,403,404],[83,132,219,226,228,232,234,240,244,248,250,252,254,256,260,262,264,266,276,281,289,292,299,303,307,309,316,320,323,325,329,331,335,339,345,350,368,370,376,382,386,397,401],[83,132,342],[83,132,312],[83,132,245,246,247],[83,132,226,240,245,292,350],[83,132,240,245,350],[83,132,344],[83,132,251],[83,132,346],[72,73,74,75,76,83,132],[72,74,83,132],[83,132,146,181,182],[83,132,138,181],[83,132,174,181,188],[83,132,146,181],[83,132,190],[83,132,181],[83,132,193,199,201],[83,132,193,194,195,201],[83,132,196],[83,132,193,201],[83,132,143,146,181,185,186,187],[83,132,183,186,188,205],[83,132,207],[83,132,143,146,148,151,163,174,181],[83,132,211],[83,132,212],[83,132,409,413],[83,129,132],[83,131,132],[132],[83,132,137,166],[83,132,133,138,143,151,163,174],[83,132,133,134,143,151],[78,79,80,83,132],[83,132,135,175],[83,132,136,137,144,152],[83,132,137,163,171],[83,132,138,140,143,151],[83,131,132,139],[83,132,140,141],[83,132,142,143],[83,131,132,143],[83,132,143,144,145,163,174],[83,132,143,144,145,158,163,166],[83,125,132,140,143,146,151,163,174],[83,132,143,144,146,147,151,163,171,174],[83,132,146,148,163,171,174],[81,82,83,84,85,86,87,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180],[83,132,143,149],[83,132,150,174],[83,132,140,143,151,163],[83,132,152],[83,132,153],[83,131,132,154],[83,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180],[83,132,156],[83,132,157],[83,132,143,158,159],[83,132,158,160,175,177],[83,132,144],[83,132,143,163,164,166],[83,132,165,166],[83,132,163,164],[83,132,166],[83,132,167],[83,129,132,163,168],[83,132,143,169,170],[83,132,169,170],[83,132,137,151,163,171],[83,132,172],[83,132,151,173],[83,132,146,157,174],[83,132,137,175],[83,132,163,176],[83,132,150,177],[83,132,178],[83,125,132],[83,125,132,143,145,154,163,166,174,176,177,179],[83,132,163,180],[83,132,422],[83,132,420,421],[83,132,427,465],[83,132,427,450,465],[83,132,426,465],[83,132,465],[83,132,427],[83,132,427,451,465],[83,132,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464],[83,132,451,465],[83,132,144,163,181],[83,132,144,206],[83,132,146,181,202,204],[83,132,144,163,181,203],[83,132,468],[83,132,469],[83,132,471],[83,132,143,146,148,151,163,171,174,180,181],[83,132,479],[83,132,193,197,198,201],[83,132,199],[83,132,214,411,412],[83,132,409],[83,132,215,410],[63,83,132,140,143,151,163,171],[83,132,408],[83,97,101,132,174],[83,97,132,163,174],[83,92,132],[83,94,97,132,171,174],[83,132,151,171],[83,92,132,181],[83,94,97,132,151,174],[83,89,90,93,96,132,143,163,174],[83,97,104,132],[83,89,95,132],[83,97,118,119,132],[83,93,97,132,166,174,181],[83,118,132,181],[83,91,92,132,181],[83,97,132],[83,91,92,93,94,95,96,97,98,99,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,119,120,121,122,123,124,132],[83,97,112,132],[83,97,104,105,132],[83,95,97,105,106,132],[83,96,132],[83,89,92,97,132],[83,97,101,105,106,132],[83,101,132],[83,95,97,100,132,174],[83,89,94,97,104,132],[83,132,163],[83,92,97,118,132,179,181],[64,83,132],[64,65,66,67,68,83,132,135],[64,65,83,132],[66,67,83,132],[65,66,68,69,70,83,132]],"fileInfos":[{"version":"c430d44666289dae81f30fa7b2edebf186ecc91a2d4c71266ea6ae76388792e1","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","impliedFormat":1},{"version":"ee7bad0c15b58988daa84371e0b89d313b762ab83cb5b31b8a2d1162e8eb41c2","impliedFormat":1},{"version":"27bdc30a0e32783366a5abeda841bc22757c1797de8681bbe81fbc735eeb1c10","impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fb0f136d372979348d59b3f5020b4cdb81b5504192b1cacff5d1fbba29378aa1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"a680117f487a4d2f30ea46f1b4b7f58bef1480456e18ba53ee85c2746eeca012","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"d6d7ae4d1f1f3772e2a3cde568ed08991a8ae34a080ff1151af28b7f798e22ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true,"impliedFormat":1},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true,"impliedFormat":1},{"version":"959d36cddf5e7d572a65045b876f2956c973a586da58e5d26cde519184fd9b8a","affectsGlobalScope":true,"impliedFormat":1},{"version":"965f36eae237dd74e6cca203a43e9ca801ce38824ead814728a2807b1910117d","affectsGlobalScope":true,"impliedFormat":1},{"version":"3925a6c820dcb1a06506c90b1577db1fdbf7705d65b62b99dce4be75c637e26b","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a3d63ef2b853447ec4f749d3f368ce642264246e02911fcb1590d8c161b8005","affectsGlobalScope":true,"impliedFormat":1},{"version":"8cdf8847677ac7d20486e54dd3fcf09eda95812ac8ace44b4418da1bbbab6eb8","affectsGlobalScope":true,"impliedFormat":1},{"version":"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a","affectsGlobalScope":true,"impliedFormat":1},{"version":"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004","affectsGlobalScope":true,"impliedFormat":1},{"version":"b4b67b1a91182421f5df999988c690f14d813b9850b40acd06ed44691f6727ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"df83c2a6c73228b625b0beb6669c7ee2a09c914637e2d35170723ad49c0f5cd4","affectsGlobalScope":true,"impliedFormat":1},{"version":"436aaf437562f276ec2ddbee2f2cdedac7664c1e4c1d2c36839ddd582eeb3d0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e3c06ea092138bf9fa5e874a1fdbc9d54805d074bee1de31b99a11e2fec239d","affectsGlobalScope":true,"impliedFormat":1},{"version":"51ad4c928303041605b4d7ae32e0c1ee387d43a24cd6f1ebf4a2699e1076d4fa","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"26db0a6b70d251b4b95101d8f58dbb8a5fe0643bf010fb69f1deed06a0cc33d2","impliedFormat":1},{"version":"683ab7efcc7cf6e47280404f2bf848a542e306a0ba42cabcd254b22e26de8366","impliedFormat":1},{"version":"c20b8ce38122db4689718a20fdf6d0b4cf15093dbe6d78d37d4e356a873957ef","signature":"044267f133d8dbc6c2dc45938fc8e461ef1ffad97b3c6d578350e519e4c03317"},{"version":"6d5904e3c33d51ca70c178233b3423e975d44fb4ad59e0441a4e84cae477b21f","signature":"923380dfd6d15329e7825751cd55100d7bf79053444d24bc3b3572703de44dc3"},{"version":"ed849d616865076f44a41c87f27698f7cdf230290c44bafc71d7c2bc6919b202","impliedFormat":1},{"version":"0c848b07fd0572ab734788358318459d068d60146b97359931abf6fe9fd17aab","signature":"6530b505e10569b7eed5b4d593175563872adbe29d1897eec015c8292871ddd5"},{"version":"3ca4d95a29ae030bb7ce79baa791bf788ba666a10db998abb776a7eb2cb5d156","signature":"183f4c08a7ba0ebd372749702c05e0c3454deb66b437a8448ac53747c13dbd84"},{"version":"bb9f1c2e030d9a92e814fc030e3c640eee615690ba04e8b8a0c6f09cfb0443de","signature":"ab2bbb6e2856bc23e0d289e364b8472e694c92552b9913d552dace15ceee922d"},{"version":"a3f1ad37c61f8dc9b59d8352259fbaf26ee2e4101347c886216206f1069a969f","signature":"5a03ca98612bedcc00eeae60c16279d58c0f40051a1197ddc56dcec1e645428c"},{"version":"c2c2a861a338244d7dd700d0c52a78916b4bb75b98fc8ca5e7c501899fc03796","impliedFormat":1},{"version":"b6d03c9cfe2cf0ba4c673c209fcd7c46c815b2619fd2aad59fc4229aaef2ed43","impliedFormat":1},{"version":"adb467429462e3891de5bb4a82a4189b92005d61c7f9367c089baf03997c104e","impliedFormat":1},{"version":"670a76db379b27c8ff42f1ba927828a22862e2ab0b0908e38b671f0e912cc5ed","impliedFormat":1},{"version":"13b77ab19ef7aadd86a1e54f2f08ea23a6d74e102909e3c00d31f231ed040f62","impliedFormat":1},{"version":"069bebfee29864e3955378107e243508b163e77ab10de6a5ee03ae06939f0bb9","impliedFormat":1},{"version":"6c7176368037af28cb72f2392010fa1cef295d6d6744bca8cfb54985f3a18c3e","affectsGlobalScope":true,"impliedFormat":1},{"version":"ab41ef1f2cdafb8df48be20cd969d875602483859dc194e9c97c8a576892c052","affectsGlobalScope":true,"impliedFormat":1},{"version":"437e20f2ba32abaeb7985e0afe0002de1917bc74e949ba585e49feba65da6ca1","affectsGlobalScope":true,"impliedFormat":1},{"version":"21d819c173c0cf7cc3ce57c3276e77fd9a8a01d35a06ad87158781515c9a438a","impliedFormat":1},{"version":"98cffbf06d6bab333473c70a893770dbe990783904002c4f1a960447b4b53dca","affectsGlobalScope":true,"impliedFormat":1},{"version":"3af97acf03cc97de58a3a4bc91f8f616408099bc4233f6d0852e72a8ffb91ac9","affectsGlobalScope":true,"impliedFormat":1},{"version":"808069bba06b6768b62fd22429b53362e7af342da4a236ed2d2e1c89fcca3b4a","affectsGlobalScope":true,"impliedFormat":1},{"version":"1db0b7dca579049ca4193d034d835f6bfe73096c73663e5ef9a0b5779939f3d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","affectsGlobalScope":true,"impliedFormat":1},{"version":"f26b11d8d8e4b8028f1c7d618b22274c892e4b0ef5b3678a8ccbad85419aef43","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e9c23ba78aabc2e0a27033f18737a6df754067731e69dc5f52823957d60a4b6","impliedFormat":1},{"version":"5929864ce17fba74232584d90cb721a89b7ad277220627cc97054ba15a98ea8f","impliedFormat":1},{"version":"763fe0f42b3d79b440a9b6e51e9ba3f3f91352469c1e4b3b67bfa4ff6352f3f4","impliedFormat":1},{"version":"25c8056edf4314820382a5fdb4bb7816999acdcb929c8f75e3f39473b87e85bc","impliedFormat":1},{"version":"c464d66b20788266e5353b48dc4aa6bc0dc4a707276df1e7152ab0c9ae21fad8","impliedFormat":1},{"version":"78d0d27c130d35c60b5e5566c9f1e5be77caf39804636bc1a40133919a949f21","impliedFormat":1},{"version":"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","impliedFormat":1},{"version":"1d6e127068ea8e104a912e42fc0a110e2aa5a66a356a917a163e8cf9a65e4a75","impliedFormat":1},{"version":"5ded6427296cdf3b9542de4471d2aa8d3983671d4cac0f4bf9c637208d1ced43","impliedFormat":1},{"version":"7f182617db458e98fc18dfb272d40aa2fff3a353c44a89b2c0ccb3937709bfb5","impliedFormat":1},{"version":"cadc8aced301244057c4e7e73fbcae534b0f5b12a37b150d80e5a45aa4bebcbd","impliedFormat":1},{"version":"385aab901643aa54e1c36f5ef3107913b10d1b5bb8cbcd933d4263b80a0d7f20","impliedFormat":1},{"version":"9670d44354bab9d9982eca21945686b5c24a3f893db73c0dae0fd74217a4c219","impliedFormat":1},{"version":"0b8a9268adaf4da35e7fa830c8981cfa22adbbe5b3f6f5ab91f6658899e657a7","impliedFormat":1},{"version":"11396ed8a44c02ab9798b7dca436009f866e8dae3c9c25e8c1fbc396880bf1bb","impliedFormat":1},{"version":"ba7bc87d01492633cb5a0e5da8a4a42a1c86270e7b3d2dea5d156828a84e4882","impliedFormat":1},{"version":"4893a895ea92c85345017a04ed427cbd6a1710453338df26881a6019432febdd","impliedFormat":1},{"version":"c21dc52e277bcfc75fac0436ccb75c204f9e1b3fa5e12729670910639f27343e","impliedFormat":1},{"version":"13f6f39e12b1518c6650bbb220c8985999020fe0f21d818e28f512b7771d00f9","impliedFormat":1},{"version":"9b5369969f6e7175740bf51223112ff209f94ba43ecd3bb09eefff9fd675624a","impliedFormat":1},{"version":"4fe9e626e7164748e8769bbf74b538e09607f07ed17c2f20af8d680ee49fc1da","impliedFormat":1},{"version":"24515859bc0b836719105bb6cc3d68255042a9f02a6022b3187948b204946bd2","impliedFormat":1},{"version":"ea0148f897b45a76544ae179784c95af1bd6721b8610af9ffa467a518a086a43","impliedFormat":1},{"version":"24c6a117721e606c9984335f71711877293a9651e44f59f3d21c1ea0856f9cc9","impliedFormat":1},{"version":"dd3273ead9fbde62a72949c97dbec2247ea08e0c6952e701a483d74ef92d6a17","impliedFormat":1},{"version":"405822be75ad3e4d162e07439bac80c6bcc6dbae1929e179cf467ec0b9ee4e2e","impliedFormat":1},{"version":"0db18c6e78ea846316c012478888f33c11ffadab9efd1cc8bcc12daded7a60b6","impliedFormat":1},{"version":"e61be3f894b41b7baa1fbd6a66893f2579bfad01d208b4ff61daef21493ef0a8","impliedFormat":1},{"version":"bd0532fd6556073727d28da0edfd1736417a3f9f394877b6d5ef6ad88fba1d1a","impliedFormat":1},{"version":"89167d696a849fce5ca508032aabfe901c0868f833a8625d5a9c6e861ef935d2","impliedFormat":1},{"version":"615ba88d0128ed16bf83ef8ccbb6aff05c3ee2db1cc0f89ab50a4939bfc1943f","impliedFormat":1},{"version":"a4d551dbf8746780194d550c88f26cf937caf8d56f102969a110cfaed4b06656","impliedFormat":1},{"version":"8bd86b8e8f6a6aa6c49b71e14c4ffe1211a0e97c80f08d2c8cc98838006e4b88","impliedFormat":1},{"version":"317e63deeb21ac07f3992f5b50cdca8338f10acd4fbb7257ebf56735bf52ab00","impliedFormat":1},{"version":"4732aec92b20fb28c5fe9ad99521fb59974289ed1e45aecb282616202184064f","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"bf67d53d168abc1298888693338cb82854bdb2e69ef83f8a0092093c2d562107","impliedFormat":1},{"version":"2cbe0621042e2a68c7cbce5dfed3906a1862a16a7d496010636cdbdb91341c0f","affectsGlobalScope":true,"impliedFormat":1},{"version":"f9501cc13ce624c72b61f12b3963e84fad210fbdf0ffbc4590e08460a3f04eba","affectsGlobalScope":true,"impliedFormat":1},{"version":"e7721c4f69f93c91360c26a0a84ee885997d748237ef78ef665b153e622b36c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"df48adbf5b82b79ed989ec3bef2979d988b94978907fd86b4f30ba2b668e49de","impliedFormat":1},{"version":"7394959e5a741b185456e1ef5d64599c36c60a323207450991e7a42e08911419","impliedFormat":1},{"version":"2b06b93fd01bcd49d1a6bd1f9b65ddcae6480b9a86e9061634d6f8e354c1468f","impliedFormat":1},{"version":"6a0cd27e5dc2cfbe039e731cf879d12b0e2dded06d1b1dedad07f7712de0d7f4","affectsGlobalScope":true,"impliedFormat":1},{"version":"13f5c844119c43e51ce777c509267f14d6aaf31eafb2c2b002ca35584cd13b29","impliedFormat":1},{"version":"e60477649d6ad21542bd2dc7e3d9ff6853d0797ba9f689ba2f6653818999c264","impliedFormat":1},{"version":"c2510f124c0293ab80b1777c44d80f812b75612f297b9857406468c0f4dafe29","affectsGlobalScope":true,"impliedFormat":1},{"version":"5524481e56c48ff486f42926778c0a3cce1cc85dc46683b92b1271865bcf015a","impliedFormat":1},{"version":"4c829ab315f57c5442c6667b53769975acbf92003a66aef19bce151987675bd1","affectsGlobalScope":true,"impliedFormat":1},{"version":"b2ade7657e2db96d18315694789eff2ddd3d8aea7215b181f8a0b303277cc579","impliedFormat":1},{"version":"9855e02d837744303391e5623a531734443a5f8e6e8755e018c41d63ad797db2","impliedFormat":1},{"version":"6851b67b164a1e84add721ea67ac52696b5aee426b800f68283c6a13046f95ec","impliedFormat":1},{"version":"836a356aae992ff3c28a0212e3eabcb76dd4b0cc06bcb9607aeef560661b860d","impliedFormat":1},{"version":"1e0d1f8b0adfa0b0330e028c7941b5a98c08b600efe7f14d2d2a00854fb2f393","impliedFormat":1},{"version":"41670ee38943d9cbb4924e436f56fc19ee94232bc96108562de1a734af20dc2c","affectsGlobalScope":true,"impliedFormat":1},{"version":"c906fb15bd2aabc9ed1e3f44eb6a8661199d6c320b3aa196b826121552cb3695","impliedFormat":1},{"version":"22295e8103f1d6d8ea4b5d6211e43421fe4564e34d0dd8e09e520e452d89e659","impliedFormat":1},{"version":"a0c4ff5309f0108772562412d15aaa15b32f5db3326288a4490fd461f66a95c8","impliedFormat":1},{"version":"6b4e081d55ac24fc8a4631d5dd77fe249fa25900abd7d046abb87d90e3b45645","impliedFormat":1},{"version":"a10f0e1854f3316d7ee437b79649e5a6ae3ae14ffe6322b02d4987071a95362e","impliedFormat":1},{"version":"ed58b9974bb3114f39806c9c2c6258c4ffa6a255921976a7c53dfa94bf178f42","impliedFormat":1},{"version":"e6fa9ad47c5f71ff733744a029d1dc472c618de53804eae08ffc243b936f87ff","affectsGlobalScope":true,"impliedFormat":1},{"version":"b57f8f7247d878e1bb5c329acbd4c1ad8efb073f4ffa6dc120d52924b1e30e40","impliedFormat":1},{"version":"24826ed94a78d5c64bd857570fdbd96229ad41b5cb654c08d75a9845e3ab7dde","impliedFormat":1},{"version":"45875bcae57270aeb3ebc73a5e3fb4c7b9d91d6b045f107c1d8513c28ece71c0","impliedFormat":1},{"version":"928af3d90454bf656a52a48679f199f64c1435247d6189d1caf4c68f2eaf921f","affectsGlobalScope":true,"impliedFormat":1},{"version":"6fac322c7864146db8daa23389f979fdb9fb97f63f212a9424a302f01c2a40d1","affectsGlobalScope":true,"impliedFormat":1},{"version":"3f16a7e4deafa527ed9995a772bb380eb7d3c2c0fd4ae178c5263ed18394db2c","impliedFormat":1},{"version":"933921f0bb0ec12ef45d1062a1fc0f27635318f4d294e4d99de9a5493e618ca2","impliedFormat":1},{"version":"71a0f3ad612c123b57239a7749770017ecfe6b66411488000aba83e4546fde25","impliedFormat":1},{"version":"77fbe5eecb6fac4b6242bbf6eebfc43e98ce5ccba8fa44e0ef6a95c945ff4d98","impliedFormat":1},{"version":"4f9d8ca0c417b67b69eeb54c7ca1bedd7b56034bb9bfd27c5d4f3bc4692daca7","impliedFormat":1},{"version":"814118df420c4e38fe5ae1b9a3bafb6e9c2aa40838e528cde908381867be6466","impliedFormat":1},{"version":"248bc21b92f51ce0b8a67140f2e55887c5f598095e9a790a05fdd2729b531e7a","impliedFormat":1},{"version":"f27524f4bef4b6519c604bdb23bf4465bddcccbf3f003abb901acbd0d7404d99","impliedFormat":1},{"version":"37ba7b45141a45ce6e80e66f2a96c8a5ab1bcef0fc2d0f56bb58df96ec67e972","impliedFormat":1},{"version":"45650f47bfb376c8a8ed39d4bcda5902ab899a3150029684ee4c10676d9fbaee","impliedFormat":1},{"version":"dba28a419aec76ed864ef43e5f577a5c99a010c32e5949fe4e17a4d57c58dd11","affectsGlobalScope":true,"impliedFormat":1},{"version":"18fd40412d102c5564136f29735e5d1c3b455b8a37f920da79561f1fde068208","impliedFormat":1},{"version":"c959a391a75be9789b43c8468f71e3fa06488b4d691d5729dde1416dcd38225b","impliedFormat":1},{"version":"f0be1b8078cd549d91f37c30c222c2a187ac1cf981d994fb476a1adc61387b14","affectsGlobalScope":true,"impliedFormat":1},{"version":"0aaed1d72199b01234152f7a60046bc947f1f37d78d182e9ae09c4289e06a592","impliedFormat":1},{"version":"8c66347a2f5b28e579a306672633dd900751e72bb5c3150ceb10b1fcfcf3d12d","impliedFormat":1},{"version":"66ba1b2c3e3a3644a1011cd530fb444a96b1b2dfe2f5e837a002d41a1a799e60","impliedFormat":1},{"version":"7e514f5b852fdbc166b539fdd1f4e9114f29911592a5eb10a94bb3a13ccac3c4","impliedFormat":1},{"version":"ea2e8e586844e2ebda7986fd859c4c7b283dc90198cd485250498a5412db27a0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4bf07b8f59b43670a28f14d0d5668a5f4abfe106d481fad660f8a1010529609a","affectsGlobalScope":true,"impliedFormat":1},{"version":"ea08a0345023ade2b47fbff5a76d0d0ed8bff10bc9d22b83f40858a8e941501c","impliedFormat":1},{"version":"47613031a5a31510831304405af561b0ffaedb734437c595256bb61a90f9311b","impliedFormat":1},{"version":"ae062ce7d9510060c5d7e7952ae379224fb3f8f2dd74e88959878af2057c143b","impliedFormat":1},{"version":"71122b94871f11a2be216426470523b679a318b08b34dab23e5e4ba9bdf54c23","affectsGlobalScope":true,"impliedFormat":1},{"version":"02aa5feb7aaa41b25b86c7a873839a799d4c6beee4f7d1789aa338905554e495","impliedFormat":1},{"version":"bdf1feb266c87edbee61f12ceaaef60ab0e2e5dba70ca19360b6448911c53d52","impliedFormat":1},{"version":"104c67f0da1bdf0d94865419247e20eded83ce7f9911a1aa75fc675c077ca66e","impliedFormat":1},{"version":"cc0d0b339f31ce0ab3b7a5b714d8e578ce698f1e13d7f8c60bfb766baeb1d35c","impliedFormat":1},{"version":"f9e22729fa06ed20f8b1fe60670b7c74933fdfd44d869ddfb1919c15a5cf12fb","impliedFormat":1},{"version":"d34aa8df2d0b18fb56b1d772ff9b3c7aea7256cf0d692f969be6e1d27b74d660","impliedFormat":1},{"version":"baac9896d29bcc55391d769e408ff400d61273d832dd500f21de766205255acb","impliedFormat":1},{"version":"2f5747b1508ccf83fad0c251ba1e5da2f5a30b78b09ffa1cfaf633045160afed","impliedFormat":1},{"version":"94ee9ee71018d54902c3fe6730090a8a421dcad95fc372d9b69a6d5351194885","affectsGlobalScope":true,"impliedFormat":1},{"version":"689be50b735f145624c6f391042155ae2ff6b90a93bac11ca5712bc866f6010c","impliedFormat":1},{"version":"fb893a0dfc3c9fb0f9ca93d0648694dd95f33cbad2c0f2c629f842981dfd4e2e","impliedFormat":1},{"version":"3eb11dbf3489064a47a2e1cf9d261b1f100ef0b3b50ffca6c44dd99d6dd81ac1","impliedFormat":1},{"version":"1800025430a0f210d5d1476459447a8a635bfbd9140111fd987e711ad1a770da","impliedFormat":1},{"version":"151ff381ef9ff8da2da9b9663ebf657eac35c4c9a19183420c05728f31a6761d","impliedFormat":1},{"version":"f3d8c757e148ad968f0d98697987db363070abada5f503da3c06aefd9d4248c1","impliedFormat":1},{"version":"a4a39b5714adfcadd3bbea6698ca2e942606d833bde62ad5fb6ec55f5e438ff8","impliedFormat":1},{"version":"bbc1d029093135d7d9bfa4b38cbf8761db505026cc458b5e9c8b74f4000e5e75","impliedFormat":1},{"version":"ac450542cbfd50a4d7bf0f3ec8aeedb9e95791ecc6f2b2b19367696bd303e8c6","impliedFormat":99},{"version":"8a190298d0ff502ad1c7294ba6b0abb3a290fc905b3a00603016a97c363a4c7a","impliedFormat":1},{"version":"5ba4a4a1f9fae0550de86889fb06cd997c8406795d85647cbcd992245625680c","impliedFormat":1},{"version":"1f68ab0e055994eb337b67aa87d2a15e0200951e9664959b3866ee6f6b11a0fe","impliedFormat":1},{"version":"5d08a179b846f5ee674624b349ebebe2121c455e3a265dc93da4e8d9e89722b4","impliedFormat":1},{"version":"b71c603a539078a5e3a039b20f2b0a0d1708967530cf97dec8850a9ca45baa2b","impliedFormat":1},{"version":"d3f2d715f57df3f04bf7b16dde01dec10366f64fce44503c92b8f78f614c1769","impliedFormat":1},{"version":"cb90077223cc1365fa21ef0911a1f9b8f2f878943523d97350dc557973ca3823","impliedFormat":1},{"version":"18f1541b81b80d806120a3489af683edfb811deb91aeca19735d9bb2613e6311","impliedFormat":1},{"version":"232f118ae64ab84dcd26ddb60eaed5a6e44302d36249abf05e9e3fc2cbb701a2","impliedFormat":1},{"version":"89121c1bf2990f5219bfd802a3e7fc557de447c62058d6af68d6b6348d64499a","impliedFormat":1},{"version":"79b4369233a12c6fa4a07301ecb7085802c98f3a77cf9ab97eee27e1656f82e6","impliedFormat":1},{"version":"d7dbe0ad36bdca8a6ecf143422a48e72cc8927bab7b23a1a2485c2f78a7022c6","impliedFormat":1},{"version":"26b7d0cd4b41ab557ef9e3bfeec42dcf24252843633e3d29f38d2c0b13aaa528","impliedFormat":1},{"version":"035a5df183489c2e22f3cf59fc1ed2b043d27f357eecc0eb8d8e840059d44245","impliedFormat":1},{"version":"a4809f4d92317535e6b22b01019437030077a76fec1d93b9881c9ed4738fcc54","impliedFormat":1},{"version":"5f53fa0bd22096d2a78533f94e02c899143b8f0f9891a46965294ee8b91a9434","impliedFormat":1},{"version":"d934a06d62d87a7e2d75a3586b5f9fb2d94d5fe4725ff07252d5f4651485100f","impliedFormat":1},{"version":"0d14fa22c41fdc7277e6f71473b20ebc07f40f00e38875142335d5b63cdfc9d2","impliedFormat":1},{"version":"b104e2da53231a529373174880dc0abfbc80184bb473b6bf2a9a0746bebb663d","impliedFormat":1},{"version":"ee91a5fbbd1627c632df89cce5a4054f9cc6e7413ebdccc82b27c7ffeedf982d","impliedFormat":1},{"version":"85c8731ca285809fc248abf21b921fe00a67b6121d27060d6194eddc0e042b1a","impliedFormat":1},{"version":"6bac0cbdf1bc85ae707f91fdf037e1b600e39fb05df18915d4ecab04a1e59d3c","impliedFormat":1},{"version":"5688b21a05a2a11c25f56e53359e2dcda0a34cb1a582dbeb1eaacdeca55cb699","impliedFormat":1},{"version":"35558bf15f773acbe3ed5ac07dd27c278476630d85245f176e85f9a95128b6e0","impliedFormat":1},{"version":"951f54e4a63e82b310439993170e866dba0f28bb829cbc14d2f2103935cea381","impliedFormat":1},{"version":"4454a999dc1676b866450e8cddd9490be87b391b5526a33f88c7e45129d30c5d","impliedFormat":1},{"version":"99013139312db746c142f27515a14cdebb61ff37f20ee1de6a58ce30d36a4f0d","impliedFormat":1},{"version":"71da852f38ac50d2ae43a7b7f2899b10a2000727fee293b0b72123ed2e7e2ad6","impliedFormat":1},{"version":"74dd1096fca1fec76b951cf5eacf609feaf919e67e13af02fed49ec3b77ea797","impliedFormat":1},{"version":"a0691153ccf5aa1b687b1500239722fff4d755481c20e16d9fcd7fb2d659c7c7","impliedFormat":1},{"version":"fe2201d73ae56b1b4946c10e18549a93bf4c390308af9d422f1ffd3c7989ffc8","impliedFormat":1},{"version":"cad63667f992149cee390c3e98f38c00eee56a2dae3541c6d9929641b835f987","impliedFormat":1},{"version":"f497cad2b33824d8b566fa276cfe3561553f905fdc6b40406c92bcfcaec96552","impliedFormat":1},{"version":"eb58c4dbc6fec60617d80f8ccf23900a64d3190fda7cfb2558b389506ec69be0","impliedFormat":1},{"version":"578929b1c1e3adaed503c0a0f9bda8ba3fea598cc41ad5c38932f765684d9888","impliedFormat":1},{"version":"7cc9d600b2070b1e5c220044a8d5a58b40da1c11399b6c8968711de9663dc6b2","impliedFormat":1},{"version":"45f36cf09d3067cd98b39a7d430e0e531f02911dd6d63b6d784b1955eef86435","impliedFormat":1},{"version":"80419a23b4182c256fa51d71cb9c4d872256ca6873701ceabbd65f8426591e49","impliedFormat":1},{"version":"5aa046aaab44da1a63d229bd67a7a1344afbd6f64db20c2bbe3981ceb2db3b07","impliedFormat":1},{"version":"ed9ad5b51c6faf9d6f597aa0ab11cb1d3a361c51ba59d1220557ef21ad5b0146","impliedFormat":1},{"version":"73db7984e8a35e6b48e3879a6d024803dd990022def2750b3c23c01eb58bc30f","impliedFormat":1},{"version":"c9ecb910b3b4c0cf67bc74833fc41585141c196b5660d2eb3a74cfffbf5aa266","impliedFormat":1},{"version":"33dcfba8a7e4acbe23974d342c44c36d7382c3d1d261f8aef28261a7a5df2969","impliedFormat":1},{"version":"de26700eb7277e8cfdde32ebb21b3d9ad1d713b64fdc2019068b857611e8f0c4","impliedFormat":1},{"version":"e481bd2c07c8e93eb58a857a9e66f22cb0b5ddfd86bbf273816fd31ef3a80613","impliedFormat":1},{"version":"ef156ba4043f6228d37645d6d9c6230a311e1c7a86669518d5f2ebc26e6559bf","impliedFormat":1},{"version":"457fd1e6d6f359d7fa2ca453353f4317efccae5c902b13f15c587597015212bc","impliedFormat":1},{"version":"473b2b42af720ebdb539988c06e040fd9600facdeb23cb297d72ee0098d8598f","impliedFormat":1},{"version":"22bc373ca556de33255faaddb373fec49e08336638958ad17fbd6361c7461eed","impliedFormat":1},{"version":"b3d58358675095fef03ec71bddc61f743128682625f1336df2fc31e29499ab25","impliedFormat":1},{"version":"5b1ef94b03042629c76350fe18be52e17ab70f1c3be8f606102b30a5cd86c1b3","impliedFormat":1},{"version":"a7b6046c44d5fda21d39b3266805d37a2811c2f639bf6b40a633b9a5fb4f5d88","impliedFormat":1},{"version":"80b036a132f3def4623aad73d526c6261dcae3c5f7013857f9ecf6589b72951f","impliedFormat":1},{"version":"0a347c2088c3b1726b95ccde77953bede00dd9dd2fda84585fa6f9f6e9573c18","impliedFormat":1},{"version":"8cc3abb4586d574a3faeea6747111b291e0c9981003a0d72711351a6bcc01421","impliedFormat":1},{"version":"0a516adfde610035e31008b170da29166233678216ef3646822c1b9af98879da","impliedFormat":1},{"version":"70d48a1faa86f67c9cb8a39babc5049246d7c67b6617cd08f64e29c055897ca9","impliedFormat":1},{"version":"a8d7795fcf72b0b91fe2ad25276ea6ab34fdb0f8f42aa1dd4e64ee7d02727031","impliedFormat":1},{"version":"082b818038423de54be877cebdb344a2e3cf3f6abcfc48218d8acf95c030426a","impliedFormat":1},{"version":"813514ef625cb8fc3befeec97afddfb3b80b80ced859959339d99f3ad538d8fe","impliedFormat":1},{"version":"039cd54028eb988297e189275764df06c18f9299b14c063e93bd3f30c046fee6","impliedFormat":1},{"version":"e91cfd040e6da28427c5c4396912874902c26605240bdc3457cc75b6235a80f2","impliedFormat":1},{"version":"b4347f0b45e4788c18241ac4dee20ceab96d172847f1c11d42439d3de3c09a3e","impliedFormat":1},{"version":"16fe6721dc0b4144a0cdcef98857ee19025bf3c2a3cc210bcd0b9d0e25f7cec8","impliedFormat":1},{"version":"346d903799e8ea99e9674ba5745642d47c0d77b003cc7bb93e1d4c21c9e37101","impliedFormat":1},{"version":"3997421bb1889118b1bbfc53dd198c3f653bf566fd13c663e02eb08649b985c4","impliedFormat":1},{"version":"2d1ac54184d897cb5b2e732d501fa4591f751678717fd0c1fd4a368236b75cba","impliedFormat":1},{"version":"bade30041d41945c54d16a6ec7046fba6d1a279aade69dfdef9e70f71f2b7226","impliedFormat":1},{"version":"56fbea100bd7dd903dc49a1001995d3c6eee10a419c66a79cdb194bff7250eb7","impliedFormat":1},{"version":"fe8d26b2b3e519e37ceea31b1790b17d7c5ab30334ca2b56d376501388ba80d6","impliedFormat":1},{"version":"37ad0a0c2b296442072cd928d55ef6a156d50793c46c2e2497da1c2750d27c1e","impliedFormat":1},{"version":"be93d07586d09e1b6625e51a1591d6119c9f1cbd95718497636a406ec42babee","impliedFormat":1},{"version":"a062b507ed5fc23fbc5850fd101bc9a39e9a0940bb52a45cd4624176337ad6b8","impliedFormat":1},{"version":"cf01f601ef1e10b90cad69312081ce0350f26a18330913487a26d6d4f7ce5a73","impliedFormat":1},{"version":"a9de7b9a5deaed116c9c89ad76fdcc469226a22b79c80736de585af4f97b17cd","impliedFormat":1},{"version":"5bde81e8b0efb2d977c6795f9425f890770d54610764b1d8df340ce35778c4f8","impliedFormat":1},{"version":"20fd0402351907669405355eeae8db00b3cf0331a3a86d8142f7b33805174f57","impliedFormat":1},{"version":"da6949af729eca1ec1fe867f93a601988b5b206b6049c027d0c849301d20af6f","impliedFormat":1},{"version":"7008f240ea3a5a344be4e5f9b5dbf26721aad3c5cfef5ff79d133fa7450e48fa","impliedFormat":1},{"version":"eb13c8624f5747a845aea0df1dfde0f2b8f5ed90ca3bc550b12777797cb1b1e3","impliedFormat":1},{"version":"2452fc0f47d3b5b466bda412397831dd5138e62f77aa5e11270e6ca3ecb8328d","impliedFormat":1},{"version":"33c2ebbdd9a62776ca0091a8d1f445fa2ea4b4f378bc92f524031a70dfbeec86","impliedFormat":1},{"version":"3ac3a5b34331a56a3f76de9baf619def3f3073961ce0a012b6ffa72cf8a91f1f","impliedFormat":1},{"version":"d5e9d32cc9813a5290a17492f554999e33f1aa083a128d3e857779548537a778","impliedFormat":1},{"version":"776f49489fa2e461b40370e501d8e775ddb32433c2d1b973f79d9717e1d79be5","impliedFormat":1},{"version":"be94ea1bfaa2eeef1e821a024914ef94cf0cba05be8f2e7df7e9556231870a1d","impliedFormat":1},{"version":"40cd13782413c7195ad8f189f81174850cc083967d056b23d529199d64f02c79","impliedFormat":1},{"version":"05e041810faf710c1dcd03f3ffde100c4a744672d93512314b1f3cfffccdaf20","impliedFormat":1},{"version":"15a8f79b1557978d752c0be488ee5a70daa389638d79570507a3d4cfc620d49d","impliedFormat":1},{"version":"968ee57037c469cffb3b0e268ab824a9c31e4205475b230011895466a1e72da4","impliedFormat":1},{"version":"77debd777927059acbaf1029dfc95900b3ab8ed0434ce3914775efb0574e747b","impliedFormat":1},{"version":"921e3bd6325acb712cd319eaec9392c9ad81f893dead509ab2f4e688f265e536","impliedFormat":1},{"version":"60f6768c96f54b870966957fb9a1b176336cd82895ded088980fb506c032be1c","impliedFormat":1},{"version":"755d9b267084db4ea40fa29653ea5fc43e125792b1940f2909ec70a4c7f712d8","impliedFormat":1},{"version":"7e3056d5333f2d8a9e54324c2e2293027e4cd9874615692a53ad69090894d116","impliedFormat":1},{"version":"1e25b848c58ad80be5c31b794d49092d94df2b7e492683974c436bcdbefb983c","impliedFormat":1},{"version":"3df6fc700b8d787974651680ae6e37b6b50726cf5401b7887f669ab195c2f2ef","impliedFormat":1},{"version":"145df08c171ec616645a353d5eaa5d5f57a5fbce960a47d847548abd9215a99e","impliedFormat":1},{"version":"dcfd2ca9e033077f9125eeca6890bb152c6c0bc715d0482595abc93c05d02d92","impliedFormat":1},{"version":"8056fa6beb8297f160e13c9b677ba2be92ab23adfb6940e5a974b05acd33163b","impliedFormat":1},{"version":"86dda1e79020fad844010b39abb68fafed2f3b2156e3302820c4d0a161f88b03","impliedFormat":1},{"version":"dea0dcec8d5e0153d6f0eacebb163d7c3a4b322a9304048adffc6d26084054bd","impliedFormat":1},{"version":"2afd081a65d595d806b0ff434d2a96dc3d6dcd8f0d1351c0a0968568c6944e0b","impliedFormat":1},{"version":"10ca40958b0dbba6426cf142c0347559cdd97d66c10083e829b10eb3c0ebc75c","impliedFormat":1},{"version":"2f1f7c65e8ee58e3e7358f9b8b3c37d8447549ecc85046f9405a0fc67fbdf54b","impliedFormat":1},{"version":"e3f3964ff78dee11a07ae589f1319ff682f62f3c6c8afa935e3d8616cf21b431","impliedFormat":1},{"version":"2762c2dbee294ffb8fdbcae6db32c3dae09e477d6a348b48578b4145b15d1818","impliedFormat":1},{"version":"e0f1c55e727739d4918c80cd9f82cf8a94274838e5ac48ff0c36529e23b79dc5","impliedFormat":1},{"version":"24bd135b687da453ea7bd98f7ece72e610a3ff8ca6ec23d321c0e32f19d32db6","impliedFormat":1},{"version":"64d45d55ba6e42734ac326d2ea1f674c72837443eb7ff66c82f95e4544980713","impliedFormat":1},{"version":"f9b0dc747f13dcc09e40c26ddcc118b1bafc3152f771fdc32757a7f8916a11fc","impliedFormat":1},{"version":"7035fc608c297fd38dfe757d44d3483a570e2d6c8824b2d6b20294d617da64c6","impliedFormat":1},{"version":"22160a296186123d2df75280a1fab70d2105ce1677af1ebb344ffcb88eef6e42","impliedFormat":1},{"version":"9067b3fd7d71165d4c34fcbbf29f883860fd722b7e8f92e87da036b355a6c625","impliedFormat":1},{"version":"e01ab4b99cc4a775d06155e9cadd2ebd93e4af46e2723cb9361f24a4e1f178ef","impliedFormat":1},{"version":"9a13410635d5cc9c2882e67921c59fb26e77b9d99efa1a80b5a46fdc2954afce","impliedFormat":1},{"version":"eabf68d666f0568b6439f4a58559d42287c3397a03fa6335758b1c8811d4174a","impliedFormat":1},{"version":"fa894bdddb2ba0e6c65ad0d88942cf15328941246410c502576124ef044746f9","impliedFormat":1},{"version":"59c5a06fa4bf2fa320a3c5289b6f199a3e4f9562480f59c0987c91dc135a1adf","impliedFormat":1},{"version":"456a9a12ad5d57af0094edf99ceab1804449f6e7bc773d85d09c56a18978a177","impliedFormat":1},{"version":"a8e2a77f445a8a1ce61bfd4b7b22664d98cf19b84ec6a966544d0decec18e143","impliedFormat":1},{"version":"6f6b0b477db6c4039410c7a13fe1ebed4910dedf644330269816df419cdb1c65","impliedFormat":1},{"version":"960b6e1edfb9aafbd560eceaae0093b31a9232ab273f4ed776c647b2fb9771da","impliedFormat":1},{"version":"3bf44073402d2489e61cdf6769c5c4cf37529e3a1cd02f01c58b7cf840308393","impliedFormat":1},{"version":"a0db48d42371b223cea8fd7a41763d48f9166ecd4baecc9d29d9bb44cc3c2d83","impliedFormat":1},{"version":"aaf3c2e268f27514eb28255835f38445a200cd8bcfdff2c07c6227f67aaaf657","impliedFormat":1},{"version":"6ade56d2afdf75a9bd55cd9c8593ed1d78674804d9f6d9aba04f807f3179979e","impliedFormat":1},{"version":"b67acb619b761e91e3a11dddb98c51ee140361bc361eb17538f1c3617e3ec157","impliedFormat":1},{"version":"81b097e0f9f8d8c3d5fe6ba9dc86139e2d95d1e24c5ce7396a276dfbb2713371","impliedFormat":1},{"version":"692d56fff4fb60948fe16e9fed6c4c4eac9b263c06a8c6e63726e28ed4844fd4","impliedFormat":1},{"version":"f13228f2c0e145fc6dc64917eeef690fb2883a0ac3fa9ebfbd99616fd12f5629","impliedFormat":1},{"version":"d89b2b41a42c04853037408080a2740f8cd18beee1c422638d54f8aefe95c5b8","impliedFormat":1},{"version":"be5d39e513e3e0135068e4ebed5473ab465ae441405dce90ab95055a14403f64","impliedFormat":1},{"version":"97e320c56905d9fa6ac8bd652cea750265384f048505870831e273050e2878cc","impliedFormat":1},{"version":"9932f390435192eb93597f89997500626fb31005416ce08a614f66ec475c5c42","impliedFormat":1},{"version":"5d89ca552233ac2d61aee34b0587f49111a54a02492e7a1098e0701dedca60c9","impliedFormat":1},{"version":"369773458c84d91e1bfcb3b94948a9768f15bf2829538188abd467bad57553cd","impliedFormat":1},{"version":"fdc4fd2c610b368104746960b45216bc32685927529dd871a5330f4871d14906","impliedFormat":1},{"version":"7b5d77c769a6f54ea64b22f1877d64436f038d9c81f1552ad11ed63f394bd351","impliedFormat":1},{"version":"4f7d54c603949113f45505330caae6f41e8dbb59841d4ae20b42307dc4579835","impliedFormat":1},{"version":"a71fd01a802624c3fce6b09c14b461cc7c7758aa199c202d423a7c89ad89943c","impliedFormat":1},{"version":"1ed0dc05908eb15f46379bc1cb64423760e59d6c3de826a970b2e2f6da290bf5","impliedFormat":1},{"version":"db89ef053f209839606e770244031688c47624b771ff5c65f0fa1ec10a6919f1","impliedFormat":1},{"version":"4d45b88987f32b2ac744f633ff5ddb95cd10f64459703f91f1633ff457d6c30d","impliedFormat":1},{"version":"8512fd4a480cd8ef8bf923a85ff5e97216fa93fb763ec871144a9026e1c9dade","impliedFormat":1},{"version":"2aa58b491183eedf2c8ae6ef9a610cd43433fcd854f4cc3e2492027fbe63f5ca","impliedFormat":1},{"version":"ce1f3439cb1c5a207f47938e68752730892fc3e66222227effc6a8b693450b82","impliedFormat":1},{"version":"295ce2cf585c26a9b71ba34fbb026d2b5a5f0d738b06a356e514f39c20bf38ba","impliedFormat":1},{"version":"342f10cf9ba3fbf52d54253db5c0ac3de50360b0a3c28e648a449e28a4ac8a8c","impliedFormat":1},{"version":"c485987c684a51c30e375d70f70942576fa86e9d30ee8d5849b6017931fccc6f","impliedFormat":1},{"version":"320bd1aa480e22cdd7cd3d385157258cc252577f4948cbf7cfdf78ded9d6d0a8","impliedFormat":1},{"version":"4ee053dfa1fce5266ecfae2bf8b6b0cb78a6a76060a1dcf66fb7215b9ff46b0b","impliedFormat":1},{"version":"1f84d8b133284b596328df47453d3b3f3817ad206cf3facf5eb64b0a2c14f6d7","impliedFormat":1},{"version":"5c75e05bc62bffe196a9b2e9adfa824ffa7b90d62345a766c21585f2ce775001","impliedFormat":1},{"version":"cc2eb5b23140bbceadf000ef2b71d27ac011d1c325b0fc5ecd42a3221db5fb2e","impliedFormat":1},{"version":"fd75cc24ea5ec28a44c0afc2f8f33da5736be58737ba772318ae3bdc1c079dc3","impliedFormat":1},{"version":"5ae43407346e6f7d5408292a7d957a663cc7b6d858a14526714a23466ac83ef9","impliedFormat":1},{"version":"c72001118edc35bbe4fff17674dc5f2032ccdbcc5bec4bd7894a6ed55739d31b","impliedFormat":1},{"version":"353196fd0dd1d05e933703d8dad664651ed172b8dfb3beaef38e66522b1e0219","impliedFormat":1},{"version":"670aef817baea9332d7974295938cf0201a2d533c5721fccf4801ba9a4571c75","impliedFormat":1},{"version":"3f5736e735ee01c6ecc6d4ab35b2d905418bb0d2128de098b73e11dd5decc34f","impliedFormat":1},{"version":"b64e159c49afc6499005756f5a7c2397c917525ceab513995f047cdd80b04bdf","impliedFormat":1},{"version":"f72b400dbf8f27adbda4c39a673884cb05daf8e0a1d8152eec2480f5700db36c","impliedFormat":1},{"version":"24509d0601fc00c4d77c20cacddbca6b878025f4e0712bddd171c7917f8cdcde","impliedFormat":1},{"version":"5f5baa59149d3d6d6cef2c09d46bb4d19beb10d6bee8c05b7850c33535b3c438","impliedFormat":1},{"version":"f17a51aae728f9f1a2290919cf29a927621b27f6ae91697aee78f41d48851690","impliedFormat":1},{"version":"be02e3c3cb4e187fd252e7ae12f6383f274e82288c8772bb0daf1a4e4af571ad","impliedFormat":1},{"version":"82ca40fb541799273571b011cd9de6ee9b577ef68acc8408135504ae69365b74","impliedFormat":1},{"version":"8fb6646db72914d6ef0692ea88b25670bbf5e504891613a1f46b42783ec18cce","impliedFormat":1},{"version":"07b0cb8b69e71d34804bde3e6dc6faaae8299f0118e9566b94e1f767b8ba9d64","impliedFormat":1},{"version":"213aa21650a910d95c4d0bee4bb936ecd51e230c1a9e5361e008830dcc73bc86","impliedFormat":1},{"version":"874a8c5125ad187e47e4a8eacc809c866c0e71b619a863cc14794dd3ccf23940","impliedFormat":1},{"version":"c31db8e51e85ee67018ac2a40006910efbb58e46baea774cf1f245d99bf178b5","impliedFormat":1},{"version":"31fac222250b18ebac0158938ede4b5d245e67d29cd2ef1e6c8a5859d137d803","impliedFormat":1},{"version":"a9dfb793a7e10949f4f3ea9f282b53d3bd8bf59f5459bc6e618e3457ed2529f5","impliedFormat":1},{"version":"2a77167687b0ec0c36ef581925103f1dc0c69993f61a9dbd299dcd30601af487","impliedFormat":1},{"version":"0f23b5ce60c754c2816c2542b9b164d6cb15243f4cbcd11cfafcab14b60e04d0","impliedFormat":1},{"version":"813ce40a8c02b172fdbeb8a07fdd427ac68e821f0e20e3dc699fb5f5bdf1ef0a","impliedFormat":1},{"version":"5ce6b24d5fd5ebb1e38fe817b8775e2e00c94145ad6eedaf26e3adf8bb3903d0","impliedFormat":1},{"version":"6babca69d3ae17be168cfceb91011eed881d41ce973302ee4e97d68a81c514b4","impliedFormat":1},{"version":"3e0832bc2533c0ec6ffcd61b7c055adedcca1a45364b3275c03343b83c71f5b3","impliedFormat":1},{"version":"342418c52b55f721b043183975052fb3956dae3c1f55f965fedfbbf4ad540501","impliedFormat":1},{"version":"6a6ab1edb5440ee695818d76f66d1a282a31207707e0d835828341e88e0c1160","impliedFormat":1},{"version":"7e9b4669774e97f5dc435ddb679aa9e7d77a1e5a480072c1d1291892d54bf45c","impliedFormat":1},{"version":"de439ddbed60296fbd1e5b4d242ce12aad718dffe6432efcae1ad6cd996defd3","impliedFormat":1},{"version":"ce5fb71799f4dbb0a9622bf976a192664e6c574d125d3773d0fa57926387b8b2","impliedFormat":1},{"version":"b9c0de070a5876c81540b1340baac0d7098ea9657c6653731a3199fcb2917cef","impliedFormat":1},{"version":"cbc91ecd74d8f9ddcbcbdc2d9245f14eff5b2f6ae38371283c97ca7dc3c4a45f","impliedFormat":1},{"version":"3ca1d6f016f36c61a59483c80d8b9f9d50301fbe52a0dde288c1381862b13636","impliedFormat":1},{"version":"ecfef0c0ff0c80ac9a6c2fab904a06b680fb5dfe8d9654bb789e49c6973cb781","impliedFormat":1},{"version":"0ee2eb3f7c0106ccf6e388bc0a16e1b3d346e88ac31b6a5bbc15766e43992167","impliedFormat":1},{"version":"f9592b77fd32a7a1262c1e9363d2e43027f513d1d2ff6b21e1cfdac4303d5a73","impliedFormat":1},{"version":"7e46dd61422e5afe88c34e5f1894ae89a37b7a07393440c092e9dc4399820172","impliedFormat":1},{"version":"9df4f57d7279173b0810154c174aa03fd60f5a1f0c3acfe8805e55e935bdecd4","impliedFormat":1},{"version":"a02a51b68a60a06d4bd0c747d6fbade0cb87eefda5f985fb4650e343da424f12","impliedFormat":1},{"version":"0cf851e2f0ecf61cabe64efd72de360246bcb8c19c6ef7b5cbb702293e1ff755","impliedFormat":1},{"version":"0c0e0aaf37ab0552dffc13eb584d8c56423b597c1c49f7974695cb45e2973de6","impliedFormat":1},{"version":"e2e0cd8f6470bc69bbfbc5e758e917a4e0f9259da7ffc93c0930516b0aa99520","impliedFormat":1},{"version":"180de8975eff720420697e7b5d95c0ecaf80f25d0cea4f8df7fe9cf817d44884","impliedFormat":1},{"version":"424a7394f9704d45596dce70bd015c5afec74a1cc5760781dfda31bc300df88f","impliedFormat":1},{"version":"044a62b9c967ee8c56dcb7b2090cf07ef2ac15c07e0e9c53d99fab7219ee3d67","impliedFormat":1},{"version":"3903b01a9ba327aae8c7ea884cdabc115d27446fba889afc95fddca8a9b4f6e2","impliedFormat":1},{"version":"78fd8f2504fbfb0070569729bf2fe41417fdf59f8c3e975ab3143a96f03e0a4a","impliedFormat":1},{"version":"8afd4f91e3a060a886a249f22b23da880ec12d4a20b6404acc5e283ef01bdd46","impliedFormat":1},{"version":"72e72e3dea4081877925442f67b23be151484ef0a1565323c9af7f1c5a0820f0","impliedFormat":1},{"version":"fa8c21bafd5d8991019d58887add8971ccbe88243c79bbcaec2e2417a40af4e8","impliedFormat":1},{"version":"ab35597fd103b902484b75a583606f606ab2cef7c069fae6c8aca0f058cee77d","impliedFormat":1},{"version":"ca54ec33929149dded2199dca95fd8ad7d48a04f6e8500f3f84a050fa77fee45","impliedFormat":1},{"version":"cac7dcf6f66d12979cc6095f33edc7fbb4266a44c8554cd44cd04572a4623fd0","impliedFormat":1},{"version":"98af566e6d420e54e4d8d942973e7fbe794e5168133ad6658b589d9dfb4409d8","impliedFormat":1},{"version":"772b2865dd86088c6e0cab71e23534ad7254961c1f791bdeaf31a57a2254df43","impliedFormat":1},{"version":"786d837fba58af9145e7ad685bc1990f52524dc4f84f3e60d9382a0c3f4a0f77","impliedFormat":1},{"version":"539dd525bf1d52094e7a35c2b4270bee757d3a35770462bcb01cd07683b4d489","impliedFormat":1},{"version":"69135303a105f3b058d79ea7e582e170721e621b1222e8f8e51ea29c61cd3acf","impliedFormat":1},{"version":"e92e6f0d63e0675fe2538e8031e1ece36d794cb6ecc07a036d82c33fa3e091a9","impliedFormat":1},{"version":"1fdb07843cdb9bd7e24745d357c6c1fde5e7f2dd7c668dd68b36c0dff144a390","impliedFormat":1},{"version":"3e2f739bdfb6b194ae2af13316b4c5bb18b3fe81ac340288675f92ba2061b370","affectsGlobalScope":true,"impliedFormat":1},{"version":"96d14f21b7652903852eef49379d04dbda28c16ed36468f8c9fa08f7c14c9538","impliedFormat":1},{"version":"d4a22007b481fe2a2e6bfd3a42c00cd62d41edb36d30fc4697df2692e9891fc8","impliedFormat":1},{"version":"29f72ec1289ae3aeda78bf14b38086d3d803262ac13904b400422941a26a3636","affectsGlobalScope":true,"impliedFormat":1},{"version":"7fadb2778688ebf3fd5b8d04f63d5bf27a43a3e420bc80732d3c6239067d1a4b","impliedFormat":1},{"version":"22293bd6fa12747929f8dfca3ec1684a3fe08638aa18023dd286ab337e88a592","impliedFormat":1},{"version":"170d4db14678c68178ee8a3d5a990d5afb759ecb6ec44dbd885c50f6da6204f6","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac51dd7d31333793807a6abaa5ae168512b6131bd41d9c5b98477fc3b7800f9f","impliedFormat":1},{"version":"5e76305d58bcdc924ff2bf14f6a9dc2aa5441ed06464b7e7bd039e611d66a89b","impliedFormat":1},{"version":"be1cc4d94ea60cbe567bc29ed479d42587bf1e6cba490f123d329976b0fe4ee5","impliedFormat":1},{"version":"510616459e6edd01acbce333fb256e06bdffdad43ca233a9090164bf8bb83912","impliedFormat":1},{"version":"d1e9031cfefebb12d6672ef7d85faf2c5a23472f5b5be1909358db426fe82eef","impliedFormat":1},{"version":"ce6a3f09b8db73a7e9701aca91a04b4fabaf77436dd35b24482f9ee816016b17","impliedFormat":1},{"version":"20e086e5b64fdd52396de67761cc0e94693494deadb731264aac122adf08de3f","impliedFormat":1},{"version":"6e78f75403b3ec65efb41c70d392aeda94360f11cedc9fb2c039c9ea23b30962","impliedFormat":1},{"version":"c863198dae89420f3c552b5a03da6ed6d0acfa3807a64772b895db624b0de707","impliedFormat":1},{"version":"8b03a5e327d7db67112ebbc93b4f744133eda2c1743dbb0a990c61a8007823ef","impliedFormat":1},{"version":"42fad1f540271e35ca37cecda12c4ce2eef27f0f5cf0f8dd761d723c744d3159","impliedFormat":1},{"version":"ff3743a5de32bee10906aff63d1de726f6a7fd6ee2da4b8229054dfa69de2c34","impliedFormat":1},{"version":"83acd370f7f84f203e71ebba33ba61b7f1291ca027d7f9a662c6307d74e4ac22","impliedFormat":1},{"version":"1445cec898f90bdd18b2949b9590b3c012f5b7e1804e6e329fb0fe053946d5ec","impliedFormat":1},{"version":"0e5318ec2275d8da858b541920d9306650ae6ac8012f0e872fe66eb50321a669","impliedFormat":1},{"version":"cf530297c3fb3a92ec9591dd4fa229d58b5981e45fe6702a0bd2bea53a5e59be","impliedFormat":1},{"version":"c1f6f7d08d42148ddfe164d36d7aba91f467dbcb3caa715966ff95f55048b3a4","impliedFormat":1},{"version":"eefd2bbc8edb14c3bd1246794e5c070a80f9b8f3730bd42efb80df3cc50b9039","impliedFormat":1},{"version":"0c1ee27b8f6a00097c2d6d91a21ee4d096ab52c1e28350f6362542b55380059a","impliedFormat":1},{"version":"7677d5b0db9e020d3017720f853ba18f415219fb3a9597343b1b1012cfd699f7","impliedFormat":1},{"version":"bc1c6bc119c1784b1a2be6d9c47addec0d83ef0d52c8fbe1f14a51b4dfffc675","impliedFormat":1},{"version":"52cf2ce99c2a23de70225e252e9822a22b4e0adb82643ab0b710858810e00bf1","impliedFormat":1},{"version":"770625067bb27a20b9826255a8d47b6b5b0a2d3dfcbd21f89904c731f671ba77","impliedFormat":1},{"version":"d1ed6765f4d7906a05968fb5cd6d1db8afa14dbe512a4884e8ea5c0f5e142c80","impliedFormat":1},{"version":"799c0f1b07c092626cf1efd71d459997635911bb5f7fc1196efe449bba87e965","impliedFormat":1},{"version":"2a184e4462b9914a30b1b5c41cf80c6d3428f17b20d3afb711fff3f0644001fd","impliedFormat":1},{"version":"9eabde32a3aa5d80de34af2c2206cdc3ee094c6504a8d0c2d6d20c7c179503cc","impliedFormat":1},{"version":"397c8051b6cfcb48aa22656f0faca2553c5f56187262135162ee79d2b2f6c966","impliedFormat":1},{"version":"a8ead142e0c87dcd5dc130eba1f8eeed506b08952d905c47621dc2f583b1bff9","impliedFormat":1},{"version":"a02f10ea5f73130efca046429254a4e3c06b5475baecc8f7b99a0014731be8b3","impliedFormat":1},{"version":"c2576a4083232b0e2d9bd06875dd43d371dee2e090325a9eac0133fd5650c1cb","impliedFormat":1},{"version":"4c9a0564bb317349de6a24eb4efea8bb79898fa72ad63a1809165f5bd42970dd","impliedFormat":1},{"version":"f40ac11d8859092d20f953aae14ba967282c3bb056431a37fced1866ec7a2681","impliedFormat":1},{"version":"cc11e9e79d4746cc59e0e17473a59d6f104692fd0eeea1bdb2e206eabed83b03","impliedFormat":1},{"version":"b444a410d34fb5e98aa5ee2b381362044f4884652e8bc8a11c8fe14bbd85518e","impliedFormat":1},{"version":"c35808c1f5e16d2c571aa65067e3cb95afeff843b259ecfa2fc107a9519b5392","impliedFormat":1},{"version":"14d5dc055143e941c8743c6a21fa459f961cbc3deedf1bfe47b11587ca4b3ef5","impliedFormat":1},{"version":"a3ad4e1fc542751005267d50a6298e6765928c0c3a8dce1572f2ba6ca518661c","impliedFormat":1},{"version":"f237e7c97a3a89f4591afd49ecb3bd8d14f51a1c4adc8fcae3430febedff5eb6","impliedFormat":1},{"version":"3ffdfbec93b7aed71082af62b8c3e0cc71261cc68d796665faa1e91604fbae8f","impliedFormat":1},{"version":"662201f943ed45b1ad600d03a90dffe20841e725203ced8b708c91fcd7f9379a","impliedFormat":1},{"version":"c9ef74c64ed051ea5b958621e7fb853fe3b56e8787c1587aefc6ea988b3c7e79","impliedFormat":1},{"version":"2462ccfac5f3375794b861abaa81da380f1bbd9401de59ffa43119a0b644253d","impliedFormat":1},{"version":"34baf65cfee92f110d6653322e2120c2d368ee64b3c7981dff08ed105c4f19b0","impliedFormat":1},{"version":"a56fe175741cc8841835eb72e61fa5a34adcbc249ede0e3494c229f0750f6b85","impliedFormat":1},{"version":"ddef25f825320de051dcb0e62ffce621b41c67712b5b4105740c32fd83f4c449","impliedFormat":1},{"version":"1b3dffaa4ca8e38ac434856843505af767a614d187fb3a5ef4fcebb023c355aa","impliedFormat":1},{"version":"cbb2be0de4e2a597ba2b7050389629fb4c2592a923e989d45095d1546913a696","impliedFormat":1},{"version":"aadcf59329c6f0f8978cfa1810dab147e0b67f241a39c965c4904459fcc98c27","impliedFormat":1},{"version":"ab82804a14454734010dcdcd43f564ff7b0389bee4c5692eec76ff5b30d4cf66","impliedFormat":1},{"version":"15fe687c59d62741b4494d5e623d497d55eb38966ecf5bea7f36e48fc3fbe15e","impliedFormat":1},{"version":"2c3b8be03577c98530ef9cb1a76e2c812636a871f367e9edf4c5f3ce702b77f8","affectsGlobalScope":true,"impliedFormat":1},{"version":"f874ea4d0091b0a44362a5f74d26caab2e66dec306c2bf7e8965f5106e784c3b","impliedFormat":1},{"version":"caaf6bf4dd0a3f88b50670f5786141b209edb54ba17eb6268c2d28919c813606","affectsGlobalScope":true,"impliedFormat":1},{"version":"bc3d87d494138b037f883840bef538bc25c1432fcd9afb0b71e438319e08780e","affectsGlobalScope":true,"impliedFormat":1},{"version":"f2f23fe34b735887db1d5597714ae37a6ffae530cafd6908c9d79d485667c956","impliedFormat":1},{"version":"5bba0e6cd8375fd37047e99a080d1bd9a808c95ecb7f3043e3adc125196f6607","impliedFormat":1},{"version":"1ba59c8bbeed2cb75b239bb12041582fa3e8ef32f8d0bd0ec802e38442d3f317","impliedFormat":1},{"version":"bae8d023ef6b23df7da26f51cea44321f95817c190342a36882e93b80d07a960","impliedFormat":1},{"version":"26a770cec4bd2e7dbba95c6e536390fffe83c6268b78974a93727903b515c4e7","impliedFormat":1}],"root":[65,66,[68,71]],"options":{"allowSyntheticDefaultImports":true,"composite":true,"declaration":true,"declarationMap":true,"esModuleInterop":true,"module":1,"outDir":"./dist","rootDir":"./src","skipLibCheck":true,"sourceMap":true,"strict":true,"target":10},"referencedMap":[[74,1],[72,2],[197,3],[214,2],[408,4],[407,5],[218,6],[219,7],[356,6],[357,8],[338,9],[339,10],[222,11],[223,12],[293,13],[294,14],[267,6],[268,15],[261,6],[262,16],[353,17],[351,18],[352,2],[367,19],[368,20],[237,21],[238,22],[369,23],[370,24],[371,25],[372,26],[229,27],[230,28],[355,29],[354,30],[340,6],[341,31],[233,32],[234,33],[257,2],[258,34],[375,35],[373,36],[374,37],[376,38],[377,39],[380,40],[378,41],[381,18],[379,42],[382,43],[385,44],[383,45],[384,46],[386,47],[235,27],[236,48],[361,49],[358,50],[359,51],[360,2],[336,52],[337,53],[281,54],[280,55],[278,56],[277,57],[279,58],[388,59],[387,60],[390,61],[389,62],[266,63],[265,6],[244,64],[242,65],[241,11],[243,66],[393,67],[397,68],[391,69],[392,70],[394,67],[395,67],[396,67],[283,71],[282,11],[299,72],[297,73],[298,18],[295,74],[296,75],[232,76],[231,6],[289,77],[220,6],[221,78],[288,79],[326,80],[329,81],[327,82],[328,83],[240,84],[239,6],[331,85],[330,11],[309,86],[308,6],[264,87],[263,6],[335,88],[334,89],[303,90],[302,91],[300,92],[301,93],[292,94],[291,95],[290,96],[399,97],[398,98],[316,99],[315,100],[314,101],[363,102],[362,2],[307,103],[306,104],[304,105],[305,106],[285,107],[284,11],[228,108],[227,109],[226,110],[225,111],[224,112],[320,113],[319,114],[250,115],[249,11],[254,116],[253,117],[318,118],[317,6],[364,2],[366,119],[365,2],[323,120],[322,121],[321,122],[401,123],[400,124],[403,125],[402,126],[349,127],[350,128],[348,129],[287,130],[286,2],[333,131],[332,132],[260,133],[259,6],[311,134],[310,6],[217,135],[216,2],[270,136],[271,137],[276,138],[269,139],[273,140],[272,141],[274,142],[275,143],[325,144],[324,11],[256,145],[255,11],[406,146],[405,147],[404,148],[343,149],[342,6],[313,150],[312,6],[248,151],[246,152],[245,11],[247,153],[345,154],[344,6],[252,155],[251,6],[347,156],[346,6],[77,157],[73,1],[75,158],[76,1],[183,159],[184,160],[189,161],[182,162],[191,163],[192,164],[200,165],[196,166],[195,167],[201,168],[193,2],[188,169],[206,170],[208,171],[209,2],[202,2],[210,172],[211,2],[212,173],[213,174],[414,175],[194,2],[415,2],[416,171],[203,2],[417,2],[190,2],[418,164],[129,176],[130,176],[131,177],[83,178],[132,179],[133,180],[134,181],[78,2],[81,182],[79,2],[80,2],[135,183],[136,184],[137,185],[138,186],[139,187],[140,188],[141,188],[142,189],[143,190],[144,191],[145,192],[84,2],[82,2],[146,193],[147,194],[148,195],[181,196],[149,197],[150,198],[151,199],[152,200],[153,201],[154,202],[155,203],[156,204],[157,205],[158,206],[159,206],[160,207],[161,2],[162,208],[163,209],[165,210],[164,211],[166,212],[167,213],[168,214],[169,215],[170,216],[171,217],[172,218],[173,219],[174,220],[175,221],[176,222],[177,223],[178,224],[85,2],[86,2],[87,2],[126,225],[127,2],[128,2],[179,226],[180,227],[419,2],[186,2],[187,2],[423,228],[420,2],[422,229],[424,2],[425,2],[450,230],[451,231],[427,232],[430,233],[448,230],[449,230],[439,230],[438,234],[436,230],[431,230],[444,230],[442,230],[446,230],[426,230],[443,230],[447,230],[432,230],[433,230],[445,230],[428,230],[434,230],[435,230],[437,230],[441,230],[452,235],[440,230],[429,230],[465,236],[464,2],[459,235],[461,237],[460,235],[453,235],[454,235],[456,235],[458,235],[462,237],[463,237],[455,237],[457,237],[185,238],[466,239],[205,240],[204,241],[467,162],[469,242],[468,243],[470,2],[472,244],[471,2],[207,2],[473,2],[475,2],[474,2],[476,2],[477,2],[478,245],[479,2],[480,246],[63,2],[88,2],[215,2],[421,2],[67,2],[199,247],[198,248],[413,249],[410,250],[411,251],[412,2],[64,252],[409,253],[61,2],[62,2],[12,2],[11,2],[2,2],[13,2],[14,2],[15,2],[16,2],[17,2],[18,2],[19,2],[20,2],[3,2],[21,2],[22,2],[4,2],[23,2],[27,2],[24,2],[25,2],[26,2],[28,2],[29,2],[30,2],[5,2],[31,2],[32,2],[33,2],[34,2],[6,2],[38,2],[35,2],[36,2],[37,2],[39,2],[7,2],[40,2],[45,2],[46,2],[41,2],[42,2],[43,2],[44,2],[8,2],[50,2],[47,2],[48,2],[49,2],[51,2],[9,2],[52,2],[53,2],[54,2],[56,2],[55,2],[57,2],[58,2],[10,2],[59,2],[1,2],[60,2],[104,254],[114,255],[103,254],[124,256],[95,257],[94,258],[123,164],[117,259],[122,260],[97,261],[111,262],[96,263],[120,264],[92,265],[91,164],[121,266],[93,267],[98,268],[99,2],[102,268],[89,2],[125,269],[115,270],[106,271],[107,272],[109,273],[105,274],[108,275],[118,164],[100,276],[101,277],[110,278],[90,279],[113,270],[112,268],[116,2],[119,280],[65,281],[66,2],[69,282],[70,283],[68,284],[71,285]],"latestChangedDtsFile":"./dist/index.d.ts","version":"5.9.3"} \ No newline at end of file diff --git a/src/documentdb/ClusterSession.ts b/src/documentdb/ClusterSession.ts index cfa065196..27ae9b8e4 100644 --- a/src/documentdb/ClusterSession.ts +++ b/src/documentdb/ClusterSession.ts @@ -3,13 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { + SchemaAnalyzer, + getPropertyNamesAtLevel, + type FieldEntry, + type JSONSchema, +} from '@vscode-documentdb/schema-analyzer'; import * as l10n from '@vscode/l10n'; import { EJSON } from 'bson'; import { ObjectId, type Document, type Filter, type WithId } from 'mongodb'; import { ext } from '../extensionVariables'; -import { type JSONSchema } from '../utils/json/JSONSchema'; -import { SchemaAnalyzer, getPropertyNamesAtLevel } from '../utils/json/data-api/SchemaAnalyzer'; -import { type FieldEntry } from '../utils/json/data-api/autocomplete/getKnownFields'; import { getDataAtPath } from '../utils/slickgrid/mongo/toSlickGridTable'; import { toSlickGridTree, type TreeData } from '../utils/slickgrid/mongo/toSlickGridTree'; import { ClustersClient, type FindQueryParams } from './ClustersClient'; diff --git a/src/utils/json/data-api/autocomplete/generateDescriptions.test.ts b/src/utils/json/data-api/autocomplete/generateDescriptions.test.ts index 7500cc95c..32a103431 100644 --- a/src/utils/json/data-api/autocomplete/generateDescriptions.test.ts +++ b/src/utils/json/data-api/autocomplete/generateDescriptions.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { type JSONSchema } from '../../JSONSchema'; +import { type JSONSchema } from '@vscode-documentdb/schema-analyzer'; import { generateDescriptions } from './generateDescriptions'; describe('generateDescriptions', () => { diff --git a/src/utils/json/data-api/autocomplete/generateDescriptions.ts b/src/utils/json/data-api/autocomplete/generateDescriptions.ts index 261549bc2..2f4f28867 100644 --- a/src/utils/json/data-api/autocomplete/generateDescriptions.ts +++ b/src/utils/json/data-api/autocomplete/generateDescriptions.ts @@ -3,9 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { BSONTypes, type JSONSchema } from '@vscode-documentdb/schema-analyzer'; import Denque from 'denque'; -import { type JSONSchema } from '../../JSONSchema'; -import { BSONTypes } from '../BSONTypes'; /** * Work item for BFS traversal of the schema tree. diff --git a/src/utils/json/data-api/autocomplete/generateMongoFindJsonSchema.ts b/src/utils/json/data-api/autocomplete/generateMongoFindJsonSchema.ts index 0f0fa7bbe..edaa8a64a 100644 --- a/src/utils/json/data-api/autocomplete/generateMongoFindJsonSchema.ts +++ b/src/utils/json/data-api/autocomplete/generateMongoFindJsonSchema.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { type FieldEntry } from './getKnownFields'; +import { type FieldEntry } from '@vscode-documentdb/schema-analyzer'; /** * Generates a JSON schema for MongoDB find filter queries. diff --git a/src/utils/json/data-api/autocomplete/getKnownFields.test.ts b/src/utils/json/data-api/autocomplete/getKnownFields.test.ts index 351dab7ff..d0680e2f3 100644 --- a/src/utils/json/data-api/autocomplete/getKnownFields.test.ts +++ b/src/utils/json/data-api/autocomplete/getKnownFields.test.ts @@ -3,9 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { type FieldEntry, getKnownFields, SchemaAnalyzer } from '@vscode-documentdb/schema-analyzer'; import { ObjectId } from 'bson'; -import { SchemaAnalyzer } from '../SchemaAnalyzer'; -import { getKnownFields, type FieldEntry } from './getKnownFields'; describe('getKnownFields', () => { it('returns bsonType for primitive fields', () => { diff --git a/src/utils/json/data-api/autocomplete/toFieldCompletionItems.test.ts b/src/utils/json/data-api/autocomplete/toFieldCompletionItems.test.ts index 307b0c9d5..947b90629 100644 --- a/src/utils/json/data-api/autocomplete/toFieldCompletionItems.test.ts +++ b/src/utils/json/data-api/autocomplete/toFieldCompletionItems.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { type FieldEntry } from './getKnownFields'; +import { type FieldEntry } from '@vscode-documentdb/schema-analyzer'; import { toFieldCompletionItems } from './toFieldCompletionItems'; describe('toFieldCompletionItems', () => { diff --git a/src/utils/json/data-api/autocomplete/toFieldCompletionItems.ts b/src/utils/json/data-api/autocomplete/toFieldCompletionItems.ts index 03d1a9a89..4998dad64 100644 --- a/src/utils/json/data-api/autocomplete/toFieldCompletionItems.ts +++ b/src/utils/json/data-api/autocomplete/toFieldCompletionItems.ts @@ -3,8 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { BSONTypes } from '../BSONTypes'; -import { type FieldEntry } from './getKnownFields'; +import { BSONTypes, type FieldEntry } from '@vscode-documentdb/schema-analyzer'; /** * Completion-ready data for a single field entry. diff --git a/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.test.ts b/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.test.ts index 256d59a27..db2ed0176 100644 --- a/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.test.ts +++ b/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { type JSONSchema } from '../../JSONSchema'; +import { type JSONSchema } from '@vscode-documentdb/schema-analyzer'; import { toTypeScriptDefinition } from './toTypeScriptDefinition'; describe('toTypeScriptDefinition', () => { diff --git a/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.ts b/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.ts index 588eb6987..559162fc0 100644 --- a/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.ts +++ b/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.ts @@ -3,8 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { type JSONSchema } from '../../JSONSchema'; -import { BSONTypes } from '../BSONTypes'; +import { BSONTypes, type JSONSchema } from '@vscode-documentdb/schema-analyzer'; /** * Maps a BSON type string to the corresponding TypeScript type representation. diff --git a/src/utils/slickgrid/mongo/toSlickGridTable.ts b/src/utils/slickgrid/mongo/toSlickGridTable.ts index ece8d02e6..5deb51fe0 100644 --- a/src/utils/slickgrid/mongo/toSlickGridTable.ts +++ b/src/utils/slickgrid/mongo/toSlickGridTable.ts @@ -3,11 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { BSONTypes, valueToDisplayString } from '@vscode-documentdb/schema-analyzer'; import { EJSON } from 'bson'; import { type Document, type WithId } from 'mongodb'; import { type TableDataEntry } from '../../../documentdb/ClusterSession'; -import { BSONTypes } from '../../json/data-api/BSONTypes'; -import { valueToDisplayString } from '../../json/data-api/ValueFormatters'; /** * Extracts data from a list of MongoDB documents at a specified path. diff --git a/src/utils/slickgrid/mongo/toSlickGridTree.ts b/src/utils/slickgrid/mongo/toSlickGridTree.ts index 1db9e979d..849ad42b6 100644 --- a/src/utils/slickgrid/mongo/toSlickGridTree.ts +++ b/src/utils/slickgrid/mongo/toSlickGridTree.ts @@ -3,9 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { BSONTypes, valueToDisplayString } from '@vscode-documentdb/schema-analyzer'; import { type Document, type ObjectId, type WithId } from 'mongodb'; -import { BSONTypes } from '../../json/data-api/BSONTypes'; -import { valueToDisplayString } from '../../json/data-api/ValueFormatters'; /** * The data structure for a single node entry in the tree data structure for SlickGrid. diff --git a/src/webviews/documentdb/collectionView/collectionViewRouter.ts b/src/webviews/documentdb/collectionView/collectionViewRouter.ts index 0e4b6a81b..62ef2f476 100644 --- a/src/webviews/documentdb/collectionView/collectionViewRouter.ts +++ b/src/webviews/documentdb/collectionView/collectionViewRouter.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { callWithTelemetryAndErrorHandling, type IActionContext } from '@microsoft/vscode-azext-utils'; +import { type FieldEntry } from '@vscode-documentdb/schema-analyzer'; import * as fs from 'fs'; import { type Document } from 'mongodb'; import * as path from 'path'; @@ -12,7 +13,6 @@ import { type JSONSchema } from 'vscode-json-languageservice'; import { z } from 'zod'; import { ClusterSession } from '../../../documentdb/ClusterSession'; import { getConfirmationAsInSettings } from '../../../utils/dialogs/getConfirmation'; -import { type FieldEntry } from '../../../utils/json/data-api/autocomplete/getKnownFields'; import { publicProcedureWithTelemetry, router, type WithTelemetry } from '../../api/extension-server/trpc'; import * as l10n from '@vscode/l10n'; diff --git a/tsconfig.json b/tsconfig.json index 894220ad0..ae69b6474 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -30,5 +30,6 @@ ] */ }, - "exclude": ["node_modules", ".vscode-test"] + "exclude": ["node_modules", ".vscode-test", "packages/*/dist"], + "references": [{ "path": "packages/schema-analyzer" }] } From 2fec69db4cccedc13b8afef0dfb4855fc940bb68 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Tue, 17 Feb 2026 11:36:38 +0100 Subject: [PATCH 14/25] docs: add README and bump schema-analyzer to v1.0.0 --- packages/schema-analyzer/README.md | 43 +++++++++++++++++++++++++++ packages/schema-analyzer/package.json | 2 +- 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 packages/schema-analyzer/README.md diff --git a/packages/schema-analyzer/README.md b/packages/schema-analyzer/README.md new file mode 100644 index 000000000..145611fe1 --- /dev/null +++ b/packages/schema-analyzer/README.md @@ -0,0 +1,43 @@ +# @vscode-documentdb/schema-analyzer + +Incremental JSON Schema analyzer for MongoDB and Azure Cosmos DB documents. Processes documents one at a time (or in batches) and produces an extended JSON Schema with statistical metadata — field occurrence counts, BSON type distributions, min/max values, and array length stats. + +> **Note:** This package is not yet published to npm. We plan to publish it once the API stabilizes. For now, it is consumed internally via npm workspaces within the [vscode-documentdb](https://github.com/microsoft/vscode-documentdb) repository. + +## Overview + +The `SchemaAnalyzer` incrementally builds a JSON Schema by inspecting MongoDB/DocumentDB documents. It is designed for scenarios where documents arrive over time (streaming, pagination) and the schema needs to evolve as new documents are observed. + +Key capabilities: + +- **Incremental analysis** — add documents one at a time or in batches; the schema updates in place. +- **BSON type awareness** — recognizes all MongoDB BSON types (`ObjectId`, `Decimal128`, `Binary`, `UUID`, etc.) and annotates them with `x-bsonType`. +- **Statistical extensions** — tracks field occurrence (`x-occurrence`), type frequency (`x-typeOccurrence`), min/max values, string lengths, array sizes, and document counts (`x-documentsInspected`). +- **Known fields extraction** — derives a flat list of known field paths with their types and occurrence probabilities, useful for autocomplete and UI rendering. +- **Version tracking & caching** — a monotonic version counter enables efficient cache invalidation for derived data like `getKnownFields()`. + +## Usage + +```typescript +import { SchemaAnalyzer } from '@vscode-documentdb/schema-analyzer'; + +// Create an analyzer and feed it documents +const analyzer = new SchemaAnalyzer(); +analyzer.addDocument(doc1); +analyzer.addDocuments([doc2, doc3, doc4]); + +// Get the JSON Schema with statistical extensions +const schema = analyzer.getSchema(); + +// Get a flat list of known fields (cached, version-aware) +const fields = analyzer.getKnownFields(); +``` + +## Requirements + +- **Node.js** ≥ 18 +- **mongodb** driver ≥ 6.0.0 (peer dependency) + +## License + +[MIT](../../LICENSE.md) diff --git a/packages/schema-analyzer/package.json b/packages/schema-analyzer/package.json index f5b10840b..ab9c3f5cd 100644 --- a/packages/schema-analyzer/package.json +++ b/packages/schema-analyzer/package.json @@ -1,6 +1,6 @@ { "name": "@vscode-documentdb/schema-analyzer", - "version": "0.1.0", + "version": "1.0.0", "description": "Incremental JSON Schema analyzer for MongoDB/DocumentDB documents with statistical extensions", "main": "dist/index.js", "types": "dist/index.d.ts", From a667c35275ed9aff703f930af1009ca9abd9107c Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Tue, 17 Feb 2026 11:55:17 +0100 Subject: [PATCH 15/25] build: add prebuild and prejesttest scripts for workspace package builds --- .github/workflows/main.yml | 3 +++ package.json | 2 ++ 2 files changed, 5 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ac02668fb..7ea04b6ef 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -55,6 +55,9 @@ jobs: - name: 📦 Install Dependencies (npm ci) run: npm ci --prefer-offline --no-audit --no-fund --progress=false --verbose + - name: 🔨 Build Workspace Packages + run: npm run build --workspaces --if-present + - name: 🌐 Check Localization Files run: npm run l10n:check diff --git a/package.json b/package.json index 887ba0ecd..69fc03a84 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "onUri" ], "scripts": { + "prebuild": "npm run build --workspaces --if-present", "build": "tsc", "clean": "git clean -dfx", "compile": "tsc -watch", @@ -73,6 +74,7 @@ "prettier-fix": "prettier -w \"(src|test|l10n|grammar|docs|packages)/**/*.@(js|ts|jsx|tsx|json)\" \"./*.@(js|ts|jsx|tsx|json)\"", "pretest": "npm run build", "test": "vscode-test", + "prejesttest": "npm run build --workspaces --if-present", "jesttest": "jest", "update-grammar": "antlr4ts -visitor ./grammar/mongo.g4 -o src/documentdb/grammar", "webpack-dev": "rimraf ./dist && npm run webpack-dev-ext && npm run webpack-dev-wv", From cbaa57390b6bf3c69f874abb23292880fb7f44da Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Tue, 17 Feb 2026 11:55:25 +0100 Subject: [PATCH 16/25] chore: bump schema-analyzer version to 1.0.0 in package-lock.json --- package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 2a0a6f451..06ce802e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22220,7 +22220,7 @@ }, "packages/schema-analyzer": { "name": "@vscode-documentdb/schema-analyzer", - "version": "0.1.0", + "version": "1.0.0", "license": "MIT", "dependencies": { "denque": "~2.1.0" From f1d006d27b58325fbedb6db46316a5783efee035 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Tue, 17 Feb 2026 12:20:49 +0100 Subject: [PATCH 17/25] Refactor code structure for improved readability and maintainability --- .gitignore | 1 + package.json | 2 +- packages/schema-analyzer/package.json | 2 +- packages/schema-analyzer/tsconfig.tsbuildinfo | 1 - 4 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 packages/schema-analyzer/tsconfig.tsbuildinfo diff --git a/.gitignore b/.gitignore index a234ba0aa..f8c691f1e 100644 --- a/.gitignore +++ b/.gitignore @@ -274,6 +274,7 @@ dist stats.json *.tgz *.zip +*.tsbuildinfo # Scrapbooks *.mongo diff --git a/package.json b/package.json index 69fc03a84..5071f6698 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "scripts": { "prebuild": "npm run build --workspaces --if-present", "build": "tsc", - "clean": "git clean -dfx", + "clean": "rimraf out dist coverage && npm run clean --workspaces --if-present", "compile": "tsc -watch", "package": "run-script-os", "package:win32": "npm run webpack-prod && cd dist && npm pkg delete \"scripts.vscode:prepublish\" && npx vsce package --no-dependencies --out ../%npm_package_name%-%npm_package_version%.vsix", diff --git a/packages/schema-analyzer/package.json b/packages/schema-analyzer/package.json index ab9c3f5cd..78a54cc65 100644 --- a/packages/schema-analyzer/package.json +++ b/packages/schema-analyzer/package.json @@ -9,7 +9,7 @@ ], "scripts": { "build": "tsc -p .", - "clean": "rimraf dist", + "clean": "rimraf dist tsconfig.tsbuildinfo", "test": "jest --config jest.config.js" }, "repository": { diff --git a/packages/schema-analyzer/tsconfig.tsbuildinfo b/packages/schema-analyzer/tsconfig.tsbuildinfo deleted file mode 100644 index bfaffb0b6..000000000 --- a/packages/schema-analyzer/tsconfig.tsbuildinfo +++ /dev/null @@ -1 +0,0 @@ -{"fileNames":["../../node_modules/typescript/lib/lib.es5.d.ts","../../node_modules/typescript/lib/lib.es2015.d.ts","../../node_modules/typescript/lib/lib.es2016.d.ts","../../node_modules/typescript/lib/lib.es2017.d.ts","../../node_modules/typescript/lib/lib.es2018.d.ts","../../node_modules/typescript/lib/lib.es2019.d.ts","../../node_modules/typescript/lib/lib.es2020.d.ts","../../node_modules/typescript/lib/lib.es2021.d.ts","../../node_modules/typescript/lib/lib.es2022.d.ts","../../node_modules/typescript/lib/lib.es2023.d.ts","../../node_modules/typescript/lib/lib.es2015.core.d.ts","../../node_modules/typescript/lib/lib.es2015.collection.d.ts","../../node_modules/typescript/lib/lib.es2015.generator.d.ts","../../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.promise.d.ts","../../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../node_modules/typescript/lib/lib.es2016.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../node_modules/typescript/lib/lib.es2017.date.d.ts","../../node_modules/typescript/lib/lib.es2017.object.d.ts","../../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2017.string.d.ts","../../node_modules/typescript/lib/lib.es2017.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../node_modules/typescript/lib/lib.es2018.intl.d.ts","../../node_modules/typescript/lib/lib.es2018.promise.d.ts","../../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../node_modules/typescript/lib/lib.es2019.array.d.ts","../../node_modules/typescript/lib/lib.es2019.object.d.ts","../../node_modules/typescript/lib/lib.es2019.string.d.ts","../../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../node_modules/typescript/lib/lib.es2019.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../node_modules/typescript/lib/lib.es2020.date.d.ts","../../node_modules/typescript/lib/lib.es2020.promise.d.ts","../../node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2020.string.d.ts","../../node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2020.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.number.d.ts","../../node_modules/typescript/lib/lib.es2021.promise.d.ts","../../node_modules/typescript/lib/lib.es2021.string.d.ts","../../node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../node_modules/typescript/lib/lib.es2021.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.array.d.ts","../../node_modules/typescript/lib/lib.es2022.error.d.ts","../../node_modules/typescript/lib/lib.es2022.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.object.d.ts","../../node_modules/typescript/lib/lib.es2022.string.d.ts","../../node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../node_modules/typescript/lib/lib.es2023.array.d.ts","../../node_modules/typescript/lib/lib.es2023.collection.d.ts","../../node_modules/typescript/lib/lib.es2023.intl.d.ts","../../node_modules/typescript/lib/lib.esnext.disposable.d.ts","../../node_modules/typescript/lib/lib.decorators.d.ts","../../node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../node_modules/bson/bson.d.ts","../../node_modules/mongodb/mongodb.d.ts","./src/BSONTypes.ts","./src/JSONSchema.ts","../../node_modules/denque/index.d.ts","./src/getKnownFields.ts","./src/SchemaAnalyzer.ts","./src/ValueFormatters.ts","./src/index.ts","../../node_modules/@babel/types/lib/index.d.ts","../../node_modules/@types/babel__generator/index.d.ts","../../node_modules/@babel/parser/typings/babel-parser.d.ts","../../node_modules/@types/babel__template/index.d.ts","../../node_modules/@types/babel__traverse/index.d.ts","../../node_modules/@types/babel__core/index.d.ts","../../node_modules/@types/node/compatibility/disposable.d.ts","../../node_modules/@types/node/compatibility/indexable.d.ts","../../node_modules/@types/node/compatibility/iterators.d.ts","../../node_modules/@types/node/compatibility/index.d.ts","../../node_modules/@types/node/globals.typedarray.d.ts","../../node_modules/@types/node/buffer.buffer.d.ts","../../node_modules/@types/node/globals.d.ts","../../node_modules/@types/node/web-globals/abortcontroller.d.ts","../../node_modules/@types/node/web-globals/domexception.d.ts","../../node_modules/@types/node/web-globals/events.d.ts","../../node_modules/buffer/index.d.ts","../../node_modules/undici-types/header.d.ts","../../node_modules/undici-types/readable.d.ts","../../node_modules/undici-types/file.d.ts","../../node_modules/undici-types/fetch.d.ts","../../node_modules/undici-types/formdata.d.ts","../../node_modules/undici-types/connector.d.ts","../../node_modules/undici-types/client.d.ts","../../node_modules/undici-types/errors.d.ts","../../node_modules/undici-types/dispatcher.d.ts","../../node_modules/undici-types/global-dispatcher.d.ts","../../node_modules/undici-types/global-origin.d.ts","../../node_modules/undici-types/pool-stats.d.ts","../../node_modules/undici-types/pool.d.ts","../../node_modules/undici-types/handlers.d.ts","../../node_modules/undici-types/balanced-pool.d.ts","../../node_modules/undici-types/agent.d.ts","../../node_modules/undici-types/mock-interceptor.d.ts","../../node_modules/undici-types/mock-agent.d.ts","../../node_modules/undici-types/mock-client.d.ts","../../node_modules/undici-types/mock-pool.d.ts","../../node_modules/undici-types/mock-errors.d.ts","../../node_modules/undici-types/proxy-agent.d.ts","../../node_modules/undici-types/env-http-proxy-agent.d.ts","../../node_modules/undici-types/retry-handler.d.ts","../../node_modules/undici-types/retry-agent.d.ts","../../node_modules/undici-types/api.d.ts","../../node_modules/undici-types/interceptors.d.ts","../../node_modules/undici-types/util.d.ts","../../node_modules/undici-types/cookies.d.ts","../../node_modules/undici-types/patch.d.ts","../../node_modules/undici-types/websocket.d.ts","../../node_modules/undici-types/eventsource.d.ts","../../node_modules/undici-types/filereader.d.ts","../../node_modules/undici-types/diagnostics-channel.d.ts","../../node_modules/undici-types/content-type.d.ts","../../node_modules/undici-types/cache.d.ts","../../node_modules/undici-types/index.d.ts","../../node_modules/@types/node/web-globals/fetch.d.ts","../../node_modules/@types/node/web-globals/navigator.d.ts","../../node_modules/@types/node/web-globals/storage.d.ts","../../node_modules/@types/node/assert.d.ts","../../node_modules/@types/node/assert/strict.d.ts","../../node_modules/@types/node/async_hooks.d.ts","../../node_modules/@types/node/buffer.d.ts","../../node_modules/@types/node/child_process.d.ts","../../node_modules/@types/node/cluster.d.ts","../../node_modules/@types/node/console.d.ts","../../node_modules/@types/node/constants.d.ts","../../node_modules/@types/node/crypto.d.ts","../../node_modules/@types/node/dgram.d.ts","../../node_modules/@types/node/diagnostics_channel.d.ts","../../node_modules/@types/node/dns.d.ts","../../node_modules/@types/node/dns/promises.d.ts","../../node_modules/@types/node/domain.d.ts","../../node_modules/@types/node/events.d.ts","../../node_modules/@types/node/fs.d.ts","../../node_modules/@types/node/fs/promises.d.ts","../../node_modules/@types/node/http.d.ts","../../node_modules/@types/node/http2.d.ts","../../node_modules/@types/node/https.d.ts","../../node_modules/@types/node/inspector.generated.d.ts","../../node_modules/@types/node/module.d.ts","../../node_modules/@types/node/net.d.ts","../../node_modules/@types/node/os.d.ts","../../node_modules/@types/node/path.d.ts","../../node_modules/@types/node/perf_hooks.d.ts","../../node_modules/@types/node/process.d.ts","../../node_modules/@types/node/punycode.d.ts","../../node_modules/@types/node/querystring.d.ts","../../node_modules/@types/node/readline.d.ts","../../node_modules/@types/node/readline/promises.d.ts","../../node_modules/@types/node/repl.d.ts","../../node_modules/@types/node/sea.d.ts","../../node_modules/@types/node/sqlite.d.ts","../../node_modules/@types/node/stream.d.ts","../../node_modules/@types/node/stream/promises.d.ts","../../node_modules/@types/node/stream/consumers.d.ts","../../node_modules/@types/node/stream/web.d.ts","../../node_modules/@types/node/string_decoder.d.ts","../../node_modules/@types/node/test.d.ts","../../node_modules/@types/node/timers.d.ts","../../node_modules/@types/node/timers/promises.d.ts","../../node_modules/@types/node/tls.d.ts","../../node_modules/@types/node/trace_events.d.ts","../../node_modules/@types/node/tty.d.ts","../../node_modules/@types/node/url.d.ts","../../node_modules/@types/node/util.d.ts","../../node_modules/@types/node/v8.d.ts","../../node_modules/@types/node/vm.d.ts","../../node_modules/@types/node/wasi.d.ts","../../node_modules/@types/node/worker_threads.d.ts","../../node_modules/@types/node/zlib.d.ts","../../node_modules/@types/node/index.d.ts","../../node_modules/@types/connect/index.d.ts","../../node_modules/@types/body-parser/index.d.ts","../../node_modules/@types/bonjour/index.d.ts","../../node_modules/@types/send/index.d.ts","../../node_modules/@types/qs/index.d.ts","../../node_modules/@types/range-parser/index.d.ts","../../node_modules/@types/express-serve-static-core/index.d.ts","../../node_modules/@types/connect-history-api-fallback/index.d.ts","../../node_modules/@types/ms/index.d.ts","../../node_modules/@types/debug/index.d.ts","../../node_modules/@types/documentdb/index.d.ts","../../node_modules/@types/estree/index.d.ts","../../node_modules/@types/json-schema/index.d.ts","../../node_modules/@types/eslint/use-at-your-own-risk.d.ts","../../node_modules/@types/eslint/index.d.ts","../../node_modules/@eslint/core/dist/esm/types.d.ts","../../node_modules/eslint/lib/types/use-at-your-own-risk.d.ts","../../node_modules/eslint/lib/types/index.d.ts","../../node_modules/@types/eslint-scope/index.d.ts","../../node_modules/@types/estree-jsx/index.d.ts","../../node_modules/@types/http-errors/index.d.ts","../../node_modules/@types/mime/index.d.ts","../../node_modules/@types/serve-static/node_modules/@types/send/index.d.ts","../../node_modules/@types/serve-static/index.d.ts","../../node_modules/@types/express/index.d.ts","../../node_modules/@types/unist/index.d.ts","../../node_modules/@types/hast/index.d.ts","../../node_modules/@types/http-cache-semantics/index.d.ts","../../node_modules/@types/http-proxy/index.d.ts","../../node_modules/@types/istanbul-lib-coverage/index.d.ts","../../node_modules/@types/istanbul-lib-report/index.d.ts","../../node_modules/@types/istanbul-reports/index.d.ts","../../node_modules/@jest/expect-utils/build/index.d.ts","../../node_modules/chalk/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/symbols/symbols.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/symbols/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/any/any.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/any/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/mapped/mapped-key.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/mapped/mapped-result.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/async-iterator/async-iterator.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/async-iterator/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/readonly/readonly.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/readonly/readonly-from-mapped-result.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/readonly/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/readonly-optional/readonly-optional.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/readonly-optional/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/constructor/constructor.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/constructor/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/literal/literal.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/literal/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/enum/enum.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/enum/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/function/function.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/function/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/computed/computed.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/computed/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/never/never.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/never/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/intersect/intersect-type.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/intersect/intersect-evaluated.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/intersect/intersect.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/intersect/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/union/union-type.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/union/union-evaluated.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/union/union.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/union/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/recursive/recursive.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/recursive/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/unsafe/unsafe.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/unsafe/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/ref/ref.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/ref/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/tuple/tuple.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/tuple/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/error/error.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/error/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/string/string.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/string/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/boolean/boolean.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/boolean/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/number/number.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/number/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/integer/integer.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/integer/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/bigint/bigint.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/bigint/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/template-literal/parse.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/template-literal/finite.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/template-literal/generate.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/template-literal/syntax.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/template-literal/pattern.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/template-literal/template-literal.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/template-literal/union.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/template-literal/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/indexed/indexed-property-keys.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/indexed/indexed-from-mapped-result.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/indexed/indexed.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/indexed/indexed-from-mapped-key.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/indexed/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/iterator/iterator.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/iterator/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/promise/promise.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/promise/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/sets/set.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/sets/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/mapped/mapped.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/mapped/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/optional/optional.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/optional/optional-from-mapped-result.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/optional/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/awaited/awaited.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/awaited/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/keyof/keyof-property-keys.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/keyof/keyof.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/keyof/keyof-from-mapped-result.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/keyof/keyof-property-entries.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/keyof/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/omit/omit-from-mapped-result.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/omit/omit.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/omit/omit-from-mapped-key.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/omit/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/pick/pick-from-mapped-result.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/pick/pick.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/pick/pick-from-mapped-key.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/pick/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/null/null.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/null/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/symbol/symbol.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/symbol/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/undefined/undefined.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/undefined/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/partial/partial.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/partial/partial-from-mapped-result.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/partial/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/regexp/regexp.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/regexp/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/record/record.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/record/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/required/required.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/required/required-from-mapped-result.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/required/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/transform/transform.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/transform/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/module/compute.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/module/infer.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/module/module.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/module/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/not/not.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/not/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/static/static.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/static/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/object/object.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/object/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/helpers/helpers.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/helpers/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/array/array.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/array/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/date/date.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/date/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/uint8array/uint8array.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/uint8array/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/unknown/unknown.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/unknown/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/void/void.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/void/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/schema/schema.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/schema/anyschema.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/schema/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/clone/type.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/clone/value.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/clone/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/create/type.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/create/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/argument/argument.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/argument/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/guard/kind.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/guard/type.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/guard/value.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/guard/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/patterns/patterns.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/patterns/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/registry/format.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/registry/type.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/registry/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/composite/composite.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/composite/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/const/const.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/const/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/constructor-parameters/constructor-parameters.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/constructor-parameters/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/exclude/exclude-from-template-literal.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/exclude/exclude.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/exclude/exclude-from-mapped-result.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/exclude/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/extends/extends-check.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/extends/extends-from-mapped-result.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/extends/extends.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/extends/extends-from-mapped-key.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/extends/extends-undefined.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/extends/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/extract/extract-from-template-literal.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/extract/extract.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/extract/extract-from-mapped-result.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/extract/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/instance-type/instance-type.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/instance-type/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/instantiate/instantiate.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/instantiate/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/intrinsic/intrinsic-from-mapped-key.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/intrinsic/intrinsic.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/intrinsic/capitalize.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/intrinsic/lowercase.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/intrinsic/uncapitalize.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/intrinsic/uppercase.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/intrinsic/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/parameters/parameters.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/parameters/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/rest/rest.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/rest/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/return-type/return-type.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/return-type/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/type/json.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/type/javascript.d.ts","../../node_modules/@sinclair/typebox/build/cjs/type/type/index.d.ts","../../node_modules/@sinclair/typebox/build/cjs/index.d.ts","../../node_modules/@jest/schemas/build/index.d.ts","../../node_modules/pretty-format/build/index.d.ts","../../node_modules/jest-diff/build/index.d.ts","../../node_modules/jest-matcher-utils/build/index.d.ts","../../node_modules/jest-mock/build/index.d.ts","../../node_modules/expect/build/index.d.ts","../../node_modules/@types/jest/index.d.ts","../../node_modules/@types/json5/index.d.ts","../../node_modules/@types/mdast/index.d.ts","../../node_modules/@types/mocha/index.d.ts","../../node_modules/@types/node-forge/index.d.ts","../../node_modules/@types/normalize-package-data/index.d.ts","../../node_modules/@types/react/global.d.ts","../../node_modules/csstype/index.d.ts","../../node_modules/@types/react/index.d.ts","../../node_modules/@types/react-dom/index.d.ts","../../node_modules/@types/retry/index.d.ts","../../node_modules/@types/sarif/index.d.ts","../../node_modules/@types/semver/functions/inc.d.ts","../../node_modules/@types/semver/classes/semver.d.ts","../../node_modules/@types/semver/functions/parse.d.ts","../../node_modules/@types/semver/functions/valid.d.ts","../../node_modules/@types/semver/functions/clean.d.ts","../../node_modules/@types/semver/functions/diff.d.ts","../../node_modules/@types/semver/functions/major.d.ts","../../node_modules/@types/semver/functions/minor.d.ts","../../node_modules/@types/semver/functions/patch.d.ts","../../node_modules/@types/semver/functions/prerelease.d.ts","../../node_modules/@types/semver/functions/compare.d.ts","../../node_modules/@types/semver/functions/rcompare.d.ts","../../node_modules/@types/semver/functions/compare-loose.d.ts","../../node_modules/@types/semver/functions/compare-build.d.ts","../../node_modules/@types/semver/functions/sort.d.ts","../../node_modules/@types/semver/functions/rsort.d.ts","../../node_modules/@types/semver/functions/gt.d.ts","../../node_modules/@types/semver/functions/lt.d.ts","../../node_modules/@types/semver/functions/eq.d.ts","../../node_modules/@types/semver/functions/neq.d.ts","../../node_modules/@types/semver/functions/gte.d.ts","../../node_modules/@types/semver/functions/lte.d.ts","../../node_modules/@types/semver/functions/cmp.d.ts","../../node_modules/@types/semver/functions/coerce.d.ts","../../node_modules/@types/semver/classes/comparator.d.ts","../../node_modules/@types/semver/classes/range.d.ts","../../node_modules/@types/semver/functions/satisfies.d.ts","../../node_modules/@types/semver/ranges/max-satisfying.d.ts","../../node_modules/@types/semver/ranges/min-satisfying.d.ts","../../node_modules/@types/semver/ranges/to-comparators.d.ts","../../node_modules/@types/semver/ranges/min-version.d.ts","../../node_modules/@types/semver/ranges/valid.d.ts","../../node_modules/@types/semver/ranges/outside.d.ts","../../node_modules/@types/semver/ranges/gtr.d.ts","../../node_modules/@types/semver/ranges/ltr.d.ts","../../node_modules/@types/semver/ranges/intersects.d.ts","../../node_modules/@types/semver/ranges/simplify.d.ts","../../node_modules/@types/semver/ranges/subset.d.ts","../../node_modules/@types/semver/internals/identifiers.d.ts","../../node_modules/@types/semver/index.d.ts","../../node_modules/@types/serve-index/index.d.ts","../../node_modules/@types/sockjs/index.d.ts","../../node_modules/@types/sortablejs/plugins.d.ts","../../node_modules/@types/sortablejs/index.d.ts","../../node_modules/@types/stack-utils/index.d.ts","../../node_modules/@types/trusted-types/lib/index.d.ts","../../node_modules/@types/trusted-types/index.d.ts","../../node_modules/@types/uuid/index.d.ts","../../node_modules/@types/vscode/index.d.ts","../../node_modules/@types/vscode-webview/index.d.ts","../../node_modules/@types/webidl-conversions/index.d.ts","../../node_modules/@types/whatwg-url/index.d.ts","../../node_modules/@types/ws/index.d.ts","../../node_modules/@types/yargs-parser/index.d.ts","../../node_modules/@types/yargs/index.d.ts"],"fileIdsList":[[72,83,132],[83,132],[83,132,194],[83,132,407],[83,132,217,219,223,226,228,230,232,234,236,240,244,248,250,252,254,256,258,260,262,264,266,268,276,281,283,285,287,289,292,294,299,303,307,309,311,313,316,318,320,323,325,329,331,333,335,337,339,341,343,345,347,350,353,355,357,361,363,366,368,370,372,376,382,386,388,390,397,399,401,403,406],[83,132,217,350],[83,132,218],[83,132,356],[83,132,217,333,337,350],[83,132,338],[83,132,217,333,350],[83,132,222],[83,132,238,244,248,254,285,337,350],[83,132,293],[83,132,267],[83,132,261],[83,132,351,352],[83,132,350],[83,132,240,244,281,287,299,335,337,350],[83,132,367],[83,132,216,350],[83,132,237],[83,132,219,226,232,236,240,256,268,309,311,313,335,337,341,343,345,350],[83,132,369],[83,132,230,240,256,350],[83,132,371],[83,132,217,226,228,292,333,337,350],[83,132,229],[83,132,354],[83,132,348],[83,132,340],[83,132,217,232,350],[83,132,233],[83,132,257],[83,132,289,335,350,374],[83,132,276,350,374],[83,132,240,248,276,289,333,337,350,373,375],[83,132,373,374,375],[83,132,258,350],[83,132,232,289,335,337,350,379],[83,132,289,335,350,379],[83,132,248,289,333,337,350,378,380],[83,132,377,378,379,380,381],[83,132,289,335,350,384],[83,132,276,350,384],[83,132,240,248,276,289,333,337,350,383,385],[83,132,383,384,385],[83,132,235],[83,132,358,359,360],[83,132,217,219,223,226,230,232,236,238,240,244,248,250,252,254,256,260,262,264,266,268,276,283,285,289,292,309,311,313,318,320,325,329,331,335,339,341,343,345,347,350,357],[83,132,217,219,223,226,230,232,236,238,240,244,248,250,252,254,256,258,260,262,264,266,268,276,283,285,289,292,309,311,313,318,320,325,329,331,335,339,341,343,345,347,350,357],[83,132,240,335,350],[83,132,336],[83,132,277,278,279,280],[83,132,279,289,335,337,350],[83,132,277,281,289,335,350],[83,132,232,248,264,266,276,350],[83,132,238,240,244,248,250,254,256,277,278,280,289,335,337,339,350],[83,132,387],[83,132,230,240,350],[83,132,389],[83,132,223,226,228,230,236,244,248,256,283,285,292,320,335,339,345,350,357],[83,132,265],[83,132,241,242,243],[83,132,226,240,241,292,350],[83,132,240,241,350],[83,132,350,392],[83,132,391,392,393,394,395,396],[83,132,232,289,335,337,350,392],[83,132,232,248,276,289,350,391],[83,132,282],[83,132,295,296,297,298],[83,132,289,296,335,337,350],[83,132,244,248,250,256,287,335,337,339,350],[83,132,232,238,248,254,264,289,295,297,337,350],[83,132,231],[83,132,220,221,288],[83,132,217,335,350],[83,132,220,221,223,226,230,232,234,236,244,248,256,281,283,285,287,292,335,337,339,350],[83,132,223,226,230,234,236,238,240,244,248,254,256,281,283,292,294,299,303,307,316,320,323,325,335,337,339,350],[83,132,328],[83,132,223,226,230,234,236,244,248,250,254,256,283,292,320,333,335,337,339,350],[83,132,217,326,327,333,335,350],[83,132,239],[83,132,330],[83,132,308],[83,132,263],[83,132,334],[83,132,217,226,292,333,337,350],[83,132,300,301,302],[83,132,289,301,335,350],[83,132,289,301,335,337,350],[83,132,232,238,244,248,250,254,281,289,300,302,335,337,350],[83,132,290,291],[83,132,289,290,335],[83,132,217,289,291,337,350],[83,132,398],[83,132,236,240,256,350],[83,132,314,315],[83,132,289,314,335,337,350],[83,132,226,228,232,238,244,248,250,254,260,262,264,266,268,289,292,309,311,313,315,335,337,350],[83,132,362],[83,132,304,305,306],[83,132,289,305,335,350],[83,132,289,305,335,337,350],[83,132,232,238,244,248,250,254,281,289,304,306,335,337,350],[83,132,284],[83,132,227],[83,132,226,292,350],[83,132,224,225],[83,132,224,289,335],[83,132,217,225,289,337,350],[83,132,319],[83,132,217,219,232,234,240,248,260,262,264,266,276,318,333,335,337,350],[83,132,249],[83,132,253],[83,132,217,252,333,350],[83,132,317],[83,132,364,365],[83,132,321,322],[83,132,289,321,335,337,350],[83,132,226,228,232,238,244,248,250,254,260,262,264,266,268,289,292,309,311,313,322,335,337,350],[83,132,400],[83,132,244,248,256,350],[83,132,402],[83,132,236,240,350],[83,132,219,223,230,232,234,236,244,248,250,254,256,260,262,264,266,268,276,283,285,309,311,313,318,320,331,335,339,341,343,345,347,348],[83,132,348,349],[83,132,217],[83,132,286],[83,132,332],[83,132,223,226,230,234,236,240,244,248,250,252,254,256,283,285,292,320,325,329,331,335,337,339,350],[83,132,259],[83,132,310],[83,132,216],[83,132,232,248,258,260,262,264,266,268,269,276],[83,132,232,248,258,262,269,270,276,337],[83,132,269,270,271,272,273,274,275],[83,132,258],[83,132,258,276],[83,132,232,248,260,262,264,268,276,337],[83,132,217,232,240,248,260,262,264,266,268,272,333,337,350],[83,132,232,248,274,333,337],[83,132,324],[83,132,255],[83,132,404,405],[83,132,223,230,236,268,283,285,294,311,313,318,341,343,347,350,357,372,388,390,399,403,404],[83,132,219,226,228,232,234,240,244,248,250,252,254,256,260,262,264,266,276,281,289,292,299,303,307,309,316,320,323,325,329,331,335,339,345,350,368,370,376,382,386,397,401],[83,132,342],[83,132,312],[83,132,245,246,247],[83,132,226,240,245,292,350],[83,132,240,245,350],[83,132,344],[83,132,251],[83,132,346],[72,73,74,75,76,83,132],[72,74,83,132],[83,132,146,181,182],[83,132,138,181],[83,132,174,181,188],[83,132,146,181],[83,132,190],[83,132,181],[83,132,193,199,201],[83,132,193,194,195,201],[83,132,196],[83,132,193,201],[83,132,143,146,181,185,186,187],[83,132,183,186,188,205],[83,132,207],[83,132,143,146,148,151,163,174,181],[83,132,211],[83,132,212],[83,132,409,413],[83,129,132],[83,131,132],[132],[83,132,137,166],[83,132,133,138,143,151,163,174],[83,132,133,134,143,151],[78,79,80,83,132],[83,132,135,175],[83,132,136,137,144,152],[83,132,137,163,171],[83,132,138,140,143,151],[83,131,132,139],[83,132,140,141],[83,132,142,143],[83,131,132,143],[83,132,143,144,145,163,174],[83,132,143,144,145,158,163,166],[83,125,132,140,143,146,151,163,174],[83,132,143,144,146,147,151,163,171,174],[83,132,146,148,163,171,174],[81,82,83,84,85,86,87,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180],[83,132,143,149],[83,132,150,174],[83,132,140,143,151,163],[83,132,152],[83,132,153],[83,131,132,154],[83,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180],[83,132,156],[83,132,157],[83,132,143,158,159],[83,132,158,160,175,177],[83,132,144],[83,132,143,163,164,166],[83,132,165,166],[83,132,163,164],[83,132,166],[83,132,167],[83,129,132,163,168],[83,132,143,169,170],[83,132,169,170],[83,132,137,151,163,171],[83,132,172],[83,132,151,173],[83,132,146,157,174],[83,132,137,175],[83,132,163,176],[83,132,150,177],[83,132,178],[83,125,132],[83,125,132,143,145,154,163,166,174,176,177,179],[83,132,163,180],[83,132,422],[83,132,420,421],[83,132,427,465],[83,132,427,450,465],[83,132,426,465],[83,132,465],[83,132,427],[83,132,427,451,465],[83,132,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464],[83,132,451,465],[83,132,144,163,181],[83,132,144,206],[83,132,146,181,202,204],[83,132,144,163,181,203],[83,132,468],[83,132,469],[83,132,471],[83,132,143,146,148,151,163,171,174,180,181],[83,132,479],[83,132,193,197,198,201],[83,132,199],[83,132,214,411,412],[83,132,409],[83,132,215,410],[63,83,132,140,143,151,163,171],[83,132,408],[83,97,101,132,174],[83,97,132,163,174],[83,92,132],[83,94,97,132,171,174],[83,132,151,171],[83,92,132,181],[83,94,97,132,151,174],[83,89,90,93,96,132,143,163,174],[83,97,104,132],[83,89,95,132],[83,97,118,119,132],[83,93,97,132,166,174,181],[83,118,132,181],[83,91,92,132,181],[83,97,132],[83,91,92,93,94,95,96,97,98,99,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,119,120,121,122,123,124,132],[83,97,112,132],[83,97,104,105,132],[83,95,97,105,106,132],[83,96,132],[83,89,92,97,132],[83,97,101,105,106,132],[83,101,132],[83,95,97,100,132,174],[83,89,94,97,104,132],[83,132,163],[83,92,97,118,132,179,181],[64,83,132],[64,65,66,67,68,83,132,135],[64,65,83,132],[66,67,83,132],[65,66,68,69,70,83,132]],"fileInfos":[{"version":"c430d44666289dae81f30fa7b2edebf186ecc91a2d4c71266ea6ae76388792e1","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","impliedFormat":1},{"version":"ee7bad0c15b58988daa84371e0b89d313b762ab83cb5b31b8a2d1162e8eb41c2","impliedFormat":1},{"version":"27bdc30a0e32783366a5abeda841bc22757c1797de8681bbe81fbc735eeb1c10","impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fb0f136d372979348d59b3f5020b4cdb81b5504192b1cacff5d1fbba29378aa1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"a680117f487a4d2f30ea46f1b4b7f58bef1480456e18ba53ee85c2746eeca012","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"d6d7ae4d1f1f3772e2a3cde568ed08991a8ae34a080ff1151af28b7f798e22ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true,"impliedFormat":1},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true,"impliedFormat":1},{"version":"959d36cddf5e7d572a65045b876f2956c973a586da58e5d26cde519184fd9b8a","affectsGlobalScope":true,"impliedFormat":1},{"version":"965f36eae237dd74e6cca203a43e9ca801ce38824ead814728a2807b1910117d","affectsGlobalScope":true,"impliedFormat":1},{"version":"3925a6c820dcb1a06506c90b1577db1fdbf7705d65b62b99dce4be75c637e26b","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a3d63ef2b853447ec4f749d3f368ce642264246e02911fcb1590d8c161b8005","affectsGlobalScope":true,"impliedFormat":1},{"version":"8cdf8847677ac7d20486e54dd3fcf09eda95812ac8ace44b4418da1bbbab6eb8","affectsGlobalScope":true,"impliedFormat":1},{"version":"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a","affectsGlobalScope":true,"impliedFormat":1},{"version":"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004","affectsGlobalScope":true,"impliedFormat":1},{"version":"b4b67b1a91182421f5df999988c690f14d813b9850b40acd06ed44691f6727ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"df83c2a6c73228b625b0beb6669c7ee2a09c914637e2d35170723ad49c0f5cd4","affectsGlobalScope":true,"impliedFormat":1},{"version":"436aaf437562f276ec2ddbee2f2cdedac7664c1e4c1d2c36839ddd582eeb3d0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e3c06ea092138bf9fa5e874a1fdbc9d54805d074bee1de31b99a11e2fec239d","affectsGlobalScope":true,"impliedFormat":1},{"version":"51ad4c928303041605b4d7ae32e0c1ee387d43a24cd6f1ebf4a2699e1076d4fa","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"26db0a6b70d251b4b95101d8f58dbb8a5fe0643bf010fb69f1deed06a0cc33d2","impliedFormat":1},{"version":"683ab7efcc7cf6e47280404f2bf848a542e306a0ba42cabcd254b22e26de8366","impliedFormat":1},{"version":"c20b8ce38122db4689718a20fdf6d0b4cf15093dbe6d78d37d4e356a873957ef","signature":"044267f133d8dbc6c2dc45938fc8e461ef1ffad97b3c6d578350e519e4c03317"},{"version":"6d5904e3c33d51ca70c178233b3423e975d44fb4ad59e0441a4e84cae477b21f","signature":"923380dfd6d15329e7825751cd55100d7bf79053444d24bc3b3572703de44dc3"},{"version":"ed849d616865076f44a41c87f27698f7cdf230290c44bafc71d7c2bc6919b202","impliedFormat":1},{"version":"0c848b07fd0572ab734788358318459d068d60146b97359931abf6fe9fd17aab","signature":"6530b505e10569b7eed5b4d593175563872adbe29d1897eec015c8292871ddd5"},{"version":"3ca4d95a29ae030bb7ce79baa791bf788ba666a10db998abb776a7eb2cb5d156","signature":"183f4c08a7ba0ebd372749702c05e0c3454deb66b437a8448ac53747c13dbd84"},{"version":"bb9f1c2e030d9a92e814fc030e3c640eee615690ba04e8b8a0c6f09cfb0443de","signature":"ab2bbb6e2856bc23e0d289e364b8472e694c92552b9913d552dace15ceee922d"},{"version":"a3f1ad37c61f8dc9b59d8352259fbaf26ee2e4101347c886216206f1069a969f","signature":"5a03ca98612bedcc00eeae60c16279d58c0f40051a1197ddc56dcec1e645428c"},{"version":"c2c2a861a338244d7dd700d0c52a78916b4bb75b98fc8ca5e7c501899fc03796","impliedFormat":1},{"version":"b6d03c9cfe2cf0ba4c673c209fcd7c46c815b2619fd2aad59fc4229aaef2ed43","impliedFormat":1},{"version":"adb467429462e3891de5bb4a82a4189b92005d61c7f9367c089baf03997c104e","impliedFormat":1},{"version":"670a76db379b27c8ff42f1ba927828a22862e2ab0b0908e38b671f0e912cc5ed","impliedFormat":1},{"version":"13b77ab19ef7aadd86a1e54f2f08ea23a6d74e102909e3c00d31f231ed040f62","impliedFormat":1},{"version":"069bebfee29864e3955378107e243508b163e77ab10de6a5ee03ae06939f0bb9","impliedFormat":1},{"version":"6c7176368037af28cb72f2392010fa1cef295d6d6744bca8cfb54985f3a18c3e","affectsGlobalScope":true,"impliedFormat":1},{"version":"ab41ef1f2cdafb8df48be20cd969d875602483859dc194e9c97c8a576892c052","affectsGlobalScope":true,"impliedFormat":1},{"version":"437e20f2ba32abaeb7985e0afe0002de1917bc74e949ba585e49feba65da6ca1","affectsGlobalScope":true,"impliedFormat":1},{"version":"21d819c173c0cf7cc3ce57c3276e77fd9a8a01d35a06ad87158781515c9a438a","impliedFormat":1},{"version":"98cffbf06d6bab333473c70a893770dbe990783904002c4f1a960447b4b53dca","affectsGlobalScope":true,"impliedFormat":1},{"version":"3af97acf03cc97de58a3a4bc91f8f616408099bc4233f6d0852e72a8ffb91ac9","affectsGlobalScope":true,"impliedFormat":1},{"version":"808069bba06b6768b62fd22429b53362e7af342da4a236ed2d2e1c89fcca3b4a","affectsGlobalScope":true,"impliedFormat":1},{"version":"1db0b7dca579049ca4193d034d835f6bfe73096c73663e5ef9a0b5779939f3d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","affectsGlobalScope":true,"impliedFormat":1},{"version":"f26b11d8d8e4b8028f1c7d618b22274c892e4b0ef5b3678a8ccbad85419aef43","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e9c23ba78aabc2e0a27033f18737a6df754067731e69dc5f52823957d60a4b6","impliedFormat":1},{"version":"5929864ce17fba74232584d90cb721a89b7ad277220627cc97054ba15a98ea8f","impliedFormat":1},{"version":"763fe0f42b3d79b440a9b6e51e9ba3f3f91352469c1e4b3b67bfa4ff6352f3f4","impliedFormat":1},{"version":"25c8056edf4314820382a5fdb4bb7816999acdcb929c8f75e3f39473b87e85bc","impliedFormat":1},{"version":"c464d66b20788266e5353b48dc4aa6bc0dc4a707276df1e7152ab0c9ae21fad8","impliedFormat":1},{"version":"78d0d27c130d35c60b5e5566c9f1e5be77caf39804636bc1a40133919a949f21","impliedFormat":1},{"version":"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","impliedFormat":1},{"version":"1d6e127068ea8e104a912e42fc0a110e2aa5a66a356a917a163e8cf9a65e4a75","impliedFormat":1},{"version":"5ded6427296cdf3b9542de4471d2aa8d3983671d4cac0f4bf9c637208d1ced43","impliedFormat":1},{"version":"7f182617db458e98fc18dfb272d40aa2fff3a353c44a89b2c0ccb3937709bfb5","impliedFormat":1},{"version":"cadc8aced301244057c4e7e73fbcae534b0f5b12a37b150d80e5a45aa4bebcbd","impliedFormat":1},{"version":"385aab901643aa54e1c36f5ef3107913b10d1b5bb8cbcd933d4263b80a0d7f20","impliedFormat":1},{"version":"9670d44354bab9d9982eca21945686b5c24a3f893db73c0dae0fd74217a4c219","impliedFormat":1},{"version":"0b8a9268adaf4da35e7fa830c8981cfa22adbbe5b3f6f5ab91f6658899e657a7","impliedFormat":1},{"version":"11396ed8a44c02ab9798b7dca436009f866e8dae3c9c25e8c1fbc396880bf1bb","impliedFormat":1},{"version":"ba7bc87d01492633cb5a0e5da8a4a42a1c86270e7b3d2dea5d156828a84e4882","impliedFormat":1},{"version":"4893a895ea92c85345017a04ed427cbd6a1710453338df26881a6019432febdd","impliedFormat":1},{"version":"c21dc52e277bcfc75fac0436ccb75c204f9e1b3fa5e12729670910639f27343e","impliedFormat":1},{"version":"13f6f39e12b1518c6650bbb220c8985999020fe0f21d818e28f512b7771d00f9","impliedFormat":1},{"version":"9b5369969f6e7175740bf51223112ff209f94ba43ecd3bb09eefff9fd675624a","impliedFormat":1},{"version":"4fe9e626e7164748e8769bbf74b538e09607f07ed17c2f20af8d680ee49fc1da","impliedFormat":1},{"version":"24515859bc0b836719105bb6cc3d68255042a9f02a6022b3187948b204946bd2","impliedFormat":1},{"version":"ea0148f897b45a76544ae179784c95af1bd6721b8610af9ffa467a518a086a43","impliedFormat":1},{"version":"24c6a117721e606c9984335f71711877293a9651e44f59f3d21c1ea0856f9cc9","impliedFormat":1},{"version":"dd3273ead9fbde62a72949c97dbec2247ea08e0c6952e701a483d74ef92d6a17","impliedFormat":1},{"version":"405822be75ad3e4d162e07439bac80c6bcc6dbae1929e179cf467ec0b9ee4e2e","impliedFormat":1},{"version":"0db18c6e78ea846316c012478888f33c11ffadab9efd1cc8bcc12daded7a60b6","impliedFormat":1},{"version":"e61be3f894b41b7baa1fbd6a66893f2579bfad01d208b4ff61daef21493ef0a8","impliedFormat":1},{"version":"bd0532fd6556073727d28da0edfd1736417a3f9f394877b6d5ef6ad88fba1d1a","impliedFormat":1},{"version":"89167d696a849fce5ca508032aabfe901c0868f833a8625d5a9c6e861ef935d2","impliedFormat":1},{"version":"615ba88d0128ed16bf83ef8ccbb6aff05c3ee2db1cc0f89ab50a4939bfc1943f","impliedFormat":1},{"version":"a4d551dbf8746780194d550c88f26cf937caf8d56f102969a110cfaed4b06656","impliedFormat":1},{"version":"8bd86b8e8f6a6aa6c49b71e14c4ffe1211a0e97c80f08d2c8cc98838006e4b88","impliedFormat":1},{"version":"317e63deeb21ac07f3992f5b50cdca8338f10acd4fbb7257ebf56735bf52ab00","impliedFormat":1},{"version":"4732aec92b20fb28c5fe9ad99521fb59974289ed1e45aecb282616202184064f","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"bf67d53d168abc1298888693338cb82854bdb2e69ef83f8a0092093c2d562107","impliedFormat":1},{"version":"2cbe0621042e2a68c7cbce5dfed3906a1862a16a7d496010636cdbdb91341c0f","affectsGlobalScope":true,"impliedFormat":1},{"version":"f9501cc13ce624c72b61f12b3963e84fad210fbdf0ffbc4590e08460a3f04eba","affectsGlobalScope":true,"impliedFormat":1},{"version":"e7721c4f69f93c91360c26a0a84ee885997d748237ef78ef665b153e622b36c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"df48adbf5b82b79ed989ec3bef2979d988b94978907fd86b4f30ba2b668e49de","impliedFormat":1},{"version":"7394959e5a741b185456e1ef5d64599c36c60a323207450991e7a42e08911419","impliedFormat":1},{"version":"2b06b93fd01bcd49d1a6bd1f9b65ddcae6480b9a86e9061634d6f8e354c1468f","impliedFormat":1},{"version":"6a0cd27e5dc2cfbe039e731cf879d12b0e2dded06d1b1dedad07f7712de0d7f4","affectsGlobalScope":true,"impliedFormat":1},{"version":"13f5c844119c43e51ce777c509267f14d6aaf31eafb2c2b002ca35584cd13b29","impliedFormat":1},{"version":"e60477649d6ad21542bd2dc7e3d9ff6853d0797ba9f689ba2f6653818999c264","impliedFormat":1},{"version":"c2510f124c0293ab80b1777c44d80f812b75612f297b9857406468c0f4dafe29","affectsGlobalScope":true,"impliedFormat":1},{"version":"5524481e56c48ff486f42926778c0a3cce1cc85dc46683b92b1271865bcf015a","impliedFormat":1},{"version":"4c829ab315f57c5442c6667b53769975acbf92003a66aef19bce151987675bd1","affectsGlobalScope":true,"impliedFormat":1},{"version":"b2ade7657e2db96d18315694789eff2ddd3d8aea7215b181f8a0b303277cc579","impliedFormat":1},{"version":"9855e02d837744303391e5623a531734443a5f8e6e8755e018c41d63ad797db2","impliedFormat":1},{"version":"6851b67b164a1e84add721ea67ac52696b5aee426b800f68283c6a13046f95ec","impliedFormat":1},{"version":"836a356aae992ff3c28a0212e3eabcb76dd4b0cc06bcb9607aeef560661b860d","impliedFormat":1},{"version":"1e0d1f8b0adfa0b0330e028c7941b5a98c08b600efe7f14d2d2a00854fb2f393","impliedFormat":1},{"version":"41670ee38943d9cbb4924e436f56fc19ee94232bc96108562de1a734af20dc2c","affectsGlobalScope":true,"impliedFormat":1},{"version":"c906fb15bd2aabc9ed1e3f44eb6a8661199d6c320b3aa196b826121552cb3695","impliedFormat":1},{"version":"22295e8103f1d6d8ea4b5d6211e43421fe4564e34d0dd8e09e520e452d89e659","impliedFormat":1},{"version":"a0c4ff5309f0108772562412d15aaa15b32f5db3326288a4490fd461f66a95c8","impliedFormat":1},{"version":"6b4e081d55ac24fc8a4631d5dd77fe249fa25900abd7d046abb87d90e3b45645","impliedFormat":1},{"version":"a10f0e1854f3316d7ee437b79649e5a6ae3ae14ffe6322b02d4987071a95362e","impliedFormat":1},{"version":"ed58b9974bb3114f39806c9c2c6258c4ffa6a255921976a7c53dfa94bf178f42","impliedFormat":1},{"version":"e6fa9ad47c5f71ff733744a029d1dc472c618de53804eae08ffc243b936f87ff","affectsGlobalScope":true,"impliedFormat":1},{"version":"b57f8f7247d878e1bb5c329acbd4c1ad8efb073f4ffa6dc120d52924b1e30e40","impliedFormat":1},{"version":"24826ed94a78d5c64bd857570fdbd96229ad41b5cb654c08d75a9845e3ab7dde","impliedFormat":1},{"version":"45875bcae57270aeb3ebc73a5e3fb4c7b9d91d6b045f107c1d8513c28ece71c0","impliedFormat":1},{"version":"928af3d90454bf656a52a48679f199f64c1435247d6189d1caf4c68f2eaf921f","affectsGlobalScope":true,"impliedFormat":1},{"version":"6fac322c7864146db8daa23389f979fdb9fb97f63f212a9424a302f01c2a40d1","affectsGlobalScope":true,"impliedFormat":1},{"version":"3f16a7e4deafa527ed9995a772bb380eb7d3c2c0fd4ae178c5263ed18394db2c","impliedFormat":1},{"version":"933921f0bb0ec12ef45d1062a1fc0f27635318f4d294e4d99de9a5493e618ca2","impliedFormat":1},{"version":"71a0f3ad612c123b57239a7749770017ecfe6b66411488000aba83e4546fde25","impliedFormat":1},{"version":"77fbe5eecb6fac4b6242bbf6eebfc43e98ce5ccba8fa44e0ef6a95c945ff4d98","impliedFormat":1},{"version":"4f9d8ca0c417b67b69eeb54c7ca1bedd7b56034bb9bfd27c5d4f3bc4692daca7","impliedFormat":1},{"version":"814118df420c4e38fe5ae1b9a3bafb6e9c2aa40838e528cde908381867be6466","impliedFormat":1},{"version":"248bc21b92f51ce0b8a67140f2e55887c5f598095e9a790a05fdd2729b531e7a","impliedFormat":1},{"version":"f27524f4bef4b6519c604bdb23bf4465bddcccbf3f003abb901acbd0d7404d99","impliedFormat":1},{"version":"37ba7b45141a45ce6e80e66f2a96c8a5ab1bcef0fc2d0f56bb58df96ec67e972","impliedFormat":1},{"version":"45650f47bfb376c8a8ed39d4bcda5902ab899a3150029684ee4c10676d9fbaee","impliedFormat":1},{"version":"dba28a419aec76ed864ef43e5f577a5c99a010c32e5949fe4e17a4d57c58dd11","affectsGlobalScope":true,"impliedFormat":1},{"version":"18fd40412d102c5564136f29735e5d1c3b455b8a37f920da79561f1fde068208","impliedFormat":1},{"version":"c959a391a75be9789b43c8468f71e3fa06488b4d691d5729dde1416dcd38225b","impliedFormat":1},{"version":"f0be1b8078cd549d91f37c30c222c2a187ac1cf981d994fb476a1adc61387b14","affectsGlobalScope":true,"impliedFormat":1},{"version":"0aaed1d72199b01234152f7a60046bc947f1f37d78d182e9ae09c4289e06a592","impliedFormat":1},{"version":"8c66347a2f5b28e579a306672633dd900751e72bb5c3150ceb10b1fcfcf3d12d","impliedFormat":1},{"version":"66ba1b2c3e3a3644a1011cd530fb444a96b1b2dfe2f5e837a002d41a1a799e60","impliedFormat":1},{"version":"7e514f5b852fdbc166b539fdd1f4e9114f29911592a5eb10a94bb3a13ccac3c4","impliedFormat":1},{"version":"ea2e8e586844e2ebda7986fd859c4c7b283dc90198cd485250498a5412db27a0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4bf07b8f59b43670a28f14d0d5668a5f4abfe106d481fad660f8a1010529609a","affectsGlobalScope":true,"impliedFormat":1},{"version":"ea08a0345023ade2b47fbff5a76d0d0ed8bff10bc9d22b83f40858a8e941501c","impliedFormat":1},{"version":"47613031a5a31510831304405af561b0ffaedb734437c595256bb61a90f9311b","impliedFormat":1},{"version":"ae062ce7d9510060c5d7e7952ae379224fb3f8f2dd74e88959878af2057c143b","impliedFormat":1},{"version":"71122b94871f11a2be216426470523b679a318b08b34dab23e5e4ba9bdf54c23","affectsGlobalScope":true,"impliedFormat":1},{"version":"02aa5feb7aaa41b25b86c7a873839a799d4c6beee4f7d1789aa338905554e495","impliedFormat":1},{"version":"bdf1feb266c87edbee61f12ceaaef60ab0e2e5dba70ca19360b6448911c53d52","impliedFormat":1},{"version":"104c67f0da1bdf0d94865419247e20eded83ce7f9911a1aa75fc675c077ca66e","impliedFormat":1},{"version":"cc0d0b339f31ce0ab3b7a5b714d8e578ce698f1e13d7f8c60bfb766baeb1d35c","impliedFormat":1},{"version":"f9e22729fa06ed20f8b1fe60670b7c74933fdfd44d869ddfb1919c15a5cf12fb","impliedFormat":1},{"version":"d34aa8df2d0b18fb56b1d772ff9b3c7aea7256cf0d692f969be6e1d27b74d660","impliedFormat":1},{"version":"baac9896d29bcc55391d769e408ff400d61273d832dd500f21de766205255acb","impliedFormat":1},{"version":"2f5747b1508ccf83fad0c251ba1e5da2f5a30b78b09ffa1cfaf633045160afed","impliedFormat":1},{"version":"94ee9ee71018d54902c3fe6730090a8a421dcad95fc372d9b69a6d5351194885","affectsGlobalScope":true,"impliedFormat":1},{"version":"689be50b735f145624c6f391042155ae2ff6b90a93bac11ca5712bc866f6010c","impliedFormat":1},{"version":"fb893a0dfc3c9fb0f9ca93d0648694dd95f33cbad2c0f2c629f842981dfd4e2e","impliedFormat":1},{"version":"3eb11dbf3489064a47a2e1cf9d261b1f100ef0b3b50ffca6c44dd99d6dd81ac1","impliedFormat":1},{"version":"1800025430a0f210d5d1476459447a8a635bfbd9140111fd987e711ad1a770da","impliedFormat":1},{"version":"151ff381ef9ff8da2da9b9663ebf657eac35c4c9a19183420c05728f31a6761d","impliedFormat":1},{"version":"f3d8c757e148ad968f0d98697987db363070abada5f503da3c06aefd9d4248c1","impliedFormat":1},{"version":"a4a39b5714adfcadd3bbea6698ca2e942606d833bde62ad5fb6ec55f5e438ff8","impliedFormat":1},{"version":"bbc1d029093135d7d9bfa4b38cbf8761db505026cc458b5e9c8b74f4000e5e75","impliedFormat":1},{"version":"ac450542cbfd50a4d7bf0f3ec8aeedb9e95791ecc6f2b2b19367696bd303e8c6","impliedFormat":99},{"version":"8a190298d0ff502ad1c7294ba6b0abb3a290fc905b3a00603016a97c363a4c7a","impliedFormat":1},{"version":"5ba4a4a1f9fae0550de86889fb06cd997c8406795d85647cbcd992245625680c","impliedFormat":1},{"version":"1f68ab0e055994eb337b67aa87d2a15e0200951e9664959b3866ee6f6b11a0fe","impliedFormat":1},{"version":"5d08a179b846f5ee674624b349ebebe2121c455e3a265dc93da4e8d9e89722b4","impliedFormat":1},{"version":"b71c603a539078a5e3a039b20f2b0a0d1708967530cf97dec8850a9ca45baa2b","impliedFormat":1},{"version":"d3f2d715f57df3f04bf7b16dde01dec10366f64fce44503c92b8f78f614c1769","impliedFormat":1},{"version":"cb90077223cc1365fa21ef0911a1f9b8f2f878943523d97350dc557973ca3823","impliedFormat":1},{"version":"18f1541b81b80d806120a3489af683edfb811deb91aeca19735d9bb2613e6311","impliedFormat":1},{"version":"232f118ae64ab84dcd26ddb60eaed5a6e44302d36249abf05e9e3fc2cbb701a2","impliedFormat":1},{"version":"89121c1bf2990f5219bfd802a3e7fc557de447c62058d6af68d6b6348d64499a","impliedFormat":1},{"version":"79b4369233a12c6fa4a07301ecb7085802c98f3a77cf9ab97eee27e1656f82e6","impliedFormat":1},{"version":"d7dbe0ad36bdca8a6ecf143422a48e72cc8927bab7b23a1a2485c2f78a7022c6","impliedFormat":1},{"version":"26b7d0cd4b41ab557ef9e3bfeec42dcf24252843633e3d29f38d2c0b13aaa528","impliedFormat":1},{"version":"035a5df183489c2e22f3cf59fc1ed2b043d27f357eecc0eb8d8e840059d44245","impliedFormat":1},{"version":"a4809f4d92317535e6b22b01019437030077a76fec1d93b9881c9ed4738fcc54","impliedFormat":1},{"version":"5f53fa0bd22096d2a78533f94e02c899143b8f0f9891a46965294ee8b91a9434","impliedFormat":1},{"version":"d934a06d62d87a7e2d75a3586b5f9fb2d94d5fe4725ff07252d5f4651485100f","impliedFormat":1},{"version":"0d14fa22c41fdc7277e6f71473b20ebc07f40f00e38875142335d5b63cdfc9d2","impliedFormat":1},{"version":"b104e2da53231a529373174880dc0abfbc80184bb473b6bf2a9a0746bebb663d","impliedFormat":1},{"version":"ee91a5fbbd1627c632df89cce5a4054f9cc6e7413ebdccc82b27c7ffeedf982d","impliedFormat":1},{"version":"85c8731ca285809fc248abf21b921fe00a67b6121d27060d6194eddc0e042b1a","impliedFormat":1},{"version":"6bac0cbdf1bc85ae707f91fdf037e1b600e39fb05df18915d4ecab04a1e59d3c","impliedFormat":1},{"version":"5688b21a05a2a11c25f56e53359e2dcda0a34cb1a582dbeb1eaacdeca55cb699","impliedFormat":1},{"version":"35558bf15f773acbe3ed5ac07dd27c278476630d85245f176e85f9a95128b6e0","impliedFormat":1},{"version":"951f54e4a63e82b310439993170e866dba0f28bb829cbc14d2f2103935cea381","impliedFormat":1},{"version":"4454a999dc1676b866450e8cddd9490be87b391b5526a33f88c7e45129d30c5d","impliedFormat":1},{"version":"99013139312db746c142f27515a14cdebb61ff37f20ee1de6a58ce30d36a4f0d","impliedFormat":1},{"version":"71da852f38ac50d2ae43a7b7f2899b10a2000727fee293b0b72123ed2e7e2ad6","impliedFormat":1},{"version":"74dd1096fca1fec76b951cf5eacf609feaf919e67e13af02fed49ec3b77ea797","impliedFormat":1},{"version":"a0691153ccf5aa1b687b1500239722fff4d755481c20e16d9fcd7fb2d659c7c7","impliedFormat":1},{"version":"fe2201d73ae56b1b4946c10e18549a93bf4c390308af9d422f1ffd3c7989ffc8","impliedFormat":1},{"version":"cad63667f992149cee390c3e98f38c00eee56a2dae3541c6d9929641b835f987","impliedFormat":1},{"version":"f497cad2b33824d8b566fa276cfe3561553f905fdc6b40406c92bcfcaec96552","impliedFormat":1},{"version":"eb58c4dbc6fec60617d80f8ccf23900a64d3190fda7cfb2558b389506ec69be0","impliedFormat":1},{"version":"578929b1c1e3adaed503c0a0f9bda8ba3fea598cc41ad5c38932f765684d9888","impliedFormat":1},{"version":"7cc9d600b2070b1e5c220044a8d5a58b40da1c11399b6c8968711de9663dc6b2","impliedFormat":1},{"version":"45f36cf09d3067cd98b39a7d430e0e531f02911dd6d63b6d784b1955eef86435","impliedFormat":1},{"version":"80419a23b4182c256fa51d71cb9c4d872256ca6873701ceabbd65f8426591e49","impliedFormat":1},{"version":"5aa046aaab44da1a63d229bd67a7a1344afbd6f64db20c2bbe3981ceb2db3b07","impliedFormat":1},{"version":"ed9ad5b51c6faf9d6f597aa0ab11cb1d3a361c51ba59d1220557ef21ad5b0146","impliedFormat":1},{"version":"73db7984e8a35e6b48e3879a6d024803dd990022def2750b3c23c01eb58bc30f","impliedFormat":1},{"version":"c9ecb910b3b4c0cf67bc74833fc41585141c196b5660d2eb3a74cfffbf5aa266","impliedFormat":1},{"version":"33dcfba8a7e4acbe23974d342c44c36d7382c3d1d261f8aef28261a7a5df2969","impliedFormat":1},{"version":"de26700eb7277e8cfdde32ebb21b3d9ad1d713b64fdc2019068b857611e8f0c4","impliedFormat":1},{"version":"e481bd2c07c8e93eb58a857a9e66f22cb0b5ddfd86bbf273816fd31ef3a80613","impliedFormat":1},{"version":"ef156ba4043f6228d37645d6d9c6230a311e1c7a86669518d5f2ebc26e6559bf","impliedFormat":1},{"version":"457fd1e6d6f359d7fa2ca453353f4317efccae5c902b13f15c587597015212bc","impliedFormat":1},{"version":"473b2b42af720ebdb539988c06e040fd9600facdeb23cb297d72ee0098d8598f","impliedFormat":1},{"version":"22bc373ca556de33255faaddb373fec49e08336638958ad17fbd6361c7461eed","impliedFormat":1},{"version":"b3d58358675095fef03ec71bddc61f743128682625f1336df2fc31e29499ab25","impliedFormat":1},{"version":"5b1ef94b03042629c76350fe18be52e17ab70f1c3be8f606102b30a5cd86c1b3","impliedFormat":1},{"version":"a7b6046c44d5fda21d39b3266805d37a2811c2f639bf6b40a633b9a5fb4f5d88","impliedFormat":1},{"version":"80b036a132f3def4623aad73d526c6261dcae3c5f7013857f9ecf6589b72951f","impliedFormat":1},{"version":"0a347c2088c3b1726b95ccde77953bede00dd9dd2fda84585fa6f9f6e9573c18","impliedFormat":1},{"version":"8cc3abb4586d574a3faeea6747111b291e0c9981003a0d72711351a6bcc01421","impliedFormat":1},{"version":"0a516adfde610035e31008b170da29166233678216ef3646822c1b9af98879da","impliedFormat":1},{"version":"70d48a1faa86f67c9cb8a39babc5049246d7c67b6617cd08f64e29c055897ca9","impliedFormat":1},{"version":"a8d7795fcf72b0b91fe2ad25276ea6ab34fdb0f8f42aa1dd4e64ee7d02727031","impliedFormat":1},{"version":"082b818038423de54be877cebdb344a2e3cf3f6abcfc48218d8acf95c030426a","impliedFormat":1},{"version":"813514ef625cb8fc3befeec97afddfb3b80b80ced859959339d99f3ad538d8fe","impliedFormat":1},{"version":"039cd54028eb988297e189275764df06c18f9299b14c063e93bd3f30c046fee6","impliedFormat":1},{"version":"e91cfd040e6da28427c5c4396912874902c26605240bdc3457cc75b6235a80f2","impliedFormat":1},{"version":"b4347f0b45e4788c18241ac4dee20ceab96d172847f1c11d42439d3de3c09a3e","impliedFormat":1},{"version":"16fe6721dc0b4144a0cdcef98857ee19025bf3c2a3cc210bcd0b9d0e25f7cec8","impliedFormat":1},{"version":"346d903799e8ea99e9674ba5745642d47c0d77b003cc7bb93e1d4c21c9e37101","impliedFormat":1},{"version":"3997421bb1889118b1bbfc53dd198c3f653bf566fd13c663e02eb08649b985c4","impliedFormat":1},{"version":"2d1ac54184d897cb5b2e732d501fa4591f751678717fd0c1fd4a368236b75cba","impliedFormat":1},{"version":"bade30041d41945c54d16a6ec7046fba6d1a279aade69dfdef9e70f71f2b7226","impliedFormat":1},{"version":"56fbea100bd7dd903dc49a1001995d3c6eee10a419c66a79cdb194bff7250eb7","impliedFormat":1},{"version":"fe8d26b2b3e519e37ceea31b1790b17d7c5ab30334ca2b56d376501388ba80d6","impliedFormat":1},{"version":"37ad0a0c2b296442072cd928d55ef6a156d50793c46c2e2497da1c2750d27c1e","impliedFormat":1},{"version":"be93d07586d09e1b6625e51a1591d6119c9f1cbd95718497636a406ec42babee","impliedFormat":1},{"version":"a062b507ed5fc23fbc5850fd101bc9a39e9a0940bb52a45cd4624176337ad6b8","impliedFormat":1},{"version":"cf01f601ef1e10b90cad69312081ce0350f26a18330913487a26d6d4f7ce5a73","impliedFormat":1},{"version":"a9de7b9a5deaed116c9c89ad76fdcc469226a22b79c80736de585af4f97b17cd","impliedFormat":1},{"version":"5bde81e8b0efb2d977c6795f9425f890770d54610764b1d8df340ce35778c4f8","impliedFormat":1},{"version":"20fd0402351907669405355eeae8db00b3cf0331a3a86d8142f7b33805174f57","impliedFormat":1},{"version":"da6949af729eca1ec1fe867f93a601988b5b206b6049c027d0c849301d20af6f","impliedFormat":1},{"version":"7008f240ea3a5a344be4e5f9b5dbf26721aad3c5cfef5ff79d133fa7450e48fa","impliedFormat":1},{"version":"eb13c8624f5747a845aea0df1dfde0f2b8f5ed90ca3bc550b12777797cb1b1e3","impliedFormat":1},{"version":"2452fc0f47d3b5b466bda412397831dd5138e62f77aa5e11270e6ca3ecb8328d","impliedFormat":1},{"version":"33c2ebbdd9a62776ca0091a8d1f445fa2ea4b4f378bc92f524031a70dfbeec86","impliedFormat":1},{"version":"3ac3a5b34331a56a3f76de9baf619def3f3073961ce0a012b6ffa72cf8a91f1f","impliedFormat":1},{"version":"d5e9d32cc9813a5290a17492f554999e33f1aa083a128d3e857779548537a778","impliedFormat":1},{"version":"776f49489fa2e461b40370e501d8e775ddb32433c2d1b973f79d9717e1d79be5","impliedFormat":1},{"version":"be94ea1bfaa2eeef1e821a024914ef94cf0cba05be8f2e7df7e9556231870a1d","impliedFormat":1},{"version":"40cd13782413c7195ad8f189f81174850cc083967d056b23d529199d64f02c79","impliedFormat":1},{"version":"05e041810faf710c1dcd03f3ffde100c4a744672d93512314b1f3cfffccdaf20","impliedFormat":1},{"version":"15a8f79b1557978d752c0be488ee5a70daa389638d79570507a3d4cfc620d49d","impliedFormat":1},{"version":"968ee57037c469cffb3b0e268ab824a9c31e4205475b230011895466a1e72da4","impliedFormat":1},{"version":"77debd777927059acbaf1029dfc95900b3ab8ed0434ce3914775efb0574e747b","impliedFormat":1},{"version":"921e3bd6325acb712cd319eaec9392c9ad81f893dead509ab2f4e688f265e536","impliedFormat":1},{"version":"60f6768c96f54b870966957fb9a1b176336cd82895ded088980fb506c032be1c","impliedFormat":1},{"version":"755d9b267084db4ea40fa29653ea5fc43e125792b1940f2909ec70a4c7f712d8","impliedFormat":1},{"version":"7e3056d5333f2d8a9e54324c2e2293027e4cd9874615692a53ad69090894d116","impliedFormat":1},{"version":"1e25b848c58ad80be5c31b794d49092d94df2b7e492683974c436bcdbefb983c","impliedFormat":1},{"version":"3df6fc700b8d787974651680ae6e37b6b50726cf5401b7887f669ab195c2f2ef","impliedFormat":1},{"version":"145df08c171ec616645a353d5eaa5d5f57a5fbce960a47d847548abd9215a99e","impliedFormat":1},{"version":"dcfd2ca9e033077f9125eeca6890bb152c6c0bc715d0482595abc93c05d02d92","impliedFormat":1},{"version":"8056fa6beb8297f160e13c9b677ba2be92ab23adfb6940e5a974b05acd33163b","impliedFormat":1},{"version":"86dda1e79020fad844010b39abb68fafed2f3b2156e3302820c4d0a161f88b03","impliedFormat":1},{"version":"dea0dcec8d5e0153d6f0eacebb163d7c3a4b322a9304048adffc6d26084054bd","impliedFormat":1},{"version":"2afd081a65d595d806b0ff434d2a96dc3d6dcd8f0d1351c0a0968568c6944e0b","impliedFormat":1},{"version":"10ca40958b0dbba6426cf142c0347559cdd97d66c10083e829b10eb3c0ebc75c","impliedFormat":1},{"version":"2f1f7c65e8ee58e3e7358f9b8b3c37d8447549ecc85046f9405a0fc67fbdf54b","impliedFormat":1},{"version":"e3f3964ff78dee11a07ae589f1319ff682f62f3c6c8afa935e3d8616cf21b431","impliedFormat":1},{"version":"2762c2dbee294ffb8fdbcae6db32c3dae09e477d6a348b48578b4145b15d1818","impliedFormat":1},{"version":"e0f1c55e727739d4918c80cd9f82cf8a94274838e5ac48ff0c36529e23b79dc5","impliedFormat":1},{"version":"24bd135b687da453ea7bd98f7ece72e610a3ff8ca6ec23d321c0e32f19d32db6","impliedFormat":1},{"version":"64d45d55ba6e42734ac326d2ea1f674c72837443eb7ff66c82f95e4544980713","impliedFormat":1},{"version":"f9b0dc747f13dcc09e40c26ddcc118b1bafc3152f771fdc32757a7f8916a11fc","impliedFormat":1},{"version":"7035fc608c297fd38dfe757d44d3483a570e2d6c8824b2d6b20294d617da64c6","impliedFormat":1},{"version":"22160a296186123d2df75280a1fab70d2105ce1677af1ebb344ffcb88eef6e42","impliedFormat":1},{"version":"9067b3fd7d71165d4c34fcbbf29f883860fd722b7e8f92e87da036b355a6c625","impliedFormat":1},{"version":"e01ab4b99cc4a775d06155e9cadd2ebd93e4af46e2723cb9361f24a4e1f178ef","impliedFormat":1},{"version":"9a13410635d5cc9c2882e67921c59fb26e77b9d99efa1a80b5a46fdc2954afce","impliedFormat":1},{"version":"eabf68d666f0568b6439f4a58559d42287c3397a03fa6335758b1c8811d4174a","impliedFormat":1},{"version":"fa894bdddb2ba0e6c65ad0d88942cf15328941246410c502576124ef044746f9","impliedFormat":1},{"version":"59c5a06fa4bf2fa320a3c5289b6f199a3e4f9562480f59c0987c91dc135a1adf","impliedFormat":1},{"version":"456a9a12ad5d57af0094edf99ceab1804449f6e7bc773d85d09c56a18978a177","impliedFormat":1},{"version":"a8e2a77f445a8a1ce61bfd4b7b22664d98cf19b84ec6a966544d0decec18e143","impliedFormat":1},{"version":"6f6b0b477db6c4039410c7a13fe1ebed4910dedf644330269816df419cdb1c65","impliedFormat":1},{"version":"960b6e1edfb9aafbd560eceaae0093b31a9232ab273f4ed776c647b2fb9771da","impliedFormat":1},{"version":"3bf44073402d2489e61cdf6769c5c4cf37529e3a1cd02f01c58b7cf840308393","impliedFormat":1},{"version":"a0db48d42371b223cea8fd7a41763d48f9166ecd4baecc9d29d9bb44cc3c2d83","impliedFormat":1},{"version":"aaf3c2e268f27514eb28255835f38445a200cd8bcfdff2c07c6227f67aaaf657","impliedFormat":1},{"version":"6ade56d2afdf75a9bd55cd9c8593ed1d78674804d9f6d9aba04f807f3179979e","impliedFormat":1},{"version":"b67acb619b761e91e3a11dddb98c51ee140361bc361eb17538f1c3617e3ec157","impliedFormat":1},{"version":"81b097e0f9f8d8c3d5fe6ba9dc86139e2d95d1e24c5ce7396a276dfbb2713371","impliedFormat":1},{"version":"692d56fff4fb60948fe16e9fed6c4c4eac9b263c06a8c6e63726e28ed4844fd4","impliedFormat":1},{"version":"f13228f2c0e145fc6dc64917eeef690fb2883a0ac3fa9ebfbd99616fd12f5629","impliedFormat":1},{"version":"d89b2b41a42c04853037408080a2740f8cd18beee1c422638d54f8aefe95c5b8","impliedFormat":1},{"version":"be5d39e513e3e0135068e4ebed5473ab465ae441405dce90ab95055a14403f64","impliedFormat":1},{"version":"97e320c56905d9fa6ac8bd652cea750265384f048505870831e273050e2878cc","impliedFormat":1},{"version":"9932f390435192eb93597f89997500626fb31005416ce08a614f66ec475c5c42","impliedFormat":1},{"version":"5d89ca552233ac2d61aee34b0587f49111a54a02492e7a1098e0701dedca60c9","impliedFormat":1},{"version":"369773458c84d91e1bfcb3b94948a9768f15bf2829538188abd467bad57553cd","impliedFormat":1},{"version":"fdc4fd2c610b368104746960b45216bc32685927529dd871a5330f4871d14906","impliedFormat":1},{"version":"7b5d77c769a6f54ea64b22f1877d64436f038d9c81f1552ad11ed63f394bd351","impliedFormat":1},{"version":"4f7d54c603949113f45505330caae6f41e8dbb59841d4ae20b42307dc4579835","impliedFormat":1},{"version":"a71fd01a802624c3fce6b09c14b461cc7c7758aa199c202d423a7c89ad89943c","impliedFormat":1},{"version":"1ed0dc05908eb15f46379bc1cb64423760e59d6c3de826a970b2e2f6da290bf5","impliedFormat":1},{"version":"db89ef053f209839606e770244031688c47624b771ff5c65f0fa1ec10a6919f1","impliedFormat":1},{"version":"4d45b88987f32b2ac744f633ff5ddb95cd10f64459703f91f1633ff457d6c30d","impliedFormat":1},{"version":"8512fd4a480cd8ef8bf923a85ff5e97216fa93fb763ec871144a9026e1c9dade","impliedFormat":1},{"version":"2aa58b491183eedf2c8ae6ef9a610cd43433fcd854f4cc3e2492027fbe63f5ca","impliedFormat":1},{"version":"ce1f3439cb1c5a207f47938e68752730892fc3e66222227effc6a8b693450b82","impliedFormat":1},{"version":"295ce2cf585c26a9b71ba34fbb026d2b5a5f0d738b06a356e514f39c20bf38ba","impliedFormat":1},{"version":"342f10cf9ba3fbf52d54253db5c0ac3de50360b0a3c28e648a449e28a4ac8a8c","impliedFormat":1},{"version":"c485987c684a51c30e375d70f70942576fa86e9d30ee8d5849b6017931fccc6f","impliedFormat":1},{"version":"320bd1aa480e22cdd7cd3d385157258cc252577f4948cbf7cfdf78ded9d6d0a8","impliedFormat":1},{"version":"4ee053dfa1fce5266ecfae2bf8b6b0cb78a6a76060a1dcf66fb7215b9ff46b0b","impliedFormat":1},{"version":"1f84d8b133284b596328df47453d3b3f3817ad206cf3facf5eb64b0a2c14f6d7","impliedFormat":1},{"version":"5c75e05bc62bffe196a9b2e9adfa824ffa7b90d62345a766c21585f2ce775001","impliedFormat":1},{"version":"cc2eb5b23140bbceadf000ef2b71d27ac011d1c325b0fc5ecd42a3221db5fb2e","impliedFormat":1},{"version":"fd75cc24ea5ec28a44c0afc2f8f33da5736be58737ba772318ae3bdc1c079dc3","impliedFormat":1},{"version":"5ae43407346e6f7d5408292a7d957a663cc7b6d858a14526714a23466ac83ef9","impliedFormat":1},{"version":"c72001118edc35bbe4fff17674dc5f2032ccdbcc5bec4bd7894a6ed55739d31b","impliedFormat":1},{"version":"353196fd0dd1d05e933703d8dad664651ed172b8dfb3beaef38e66522b1e0219","impliedFormat":1},{"version":"670aef817baea9332d7974295938cf0201a2d533c5721fccf4801ba9a4571c75","impliedFormat":1},{"version":"3f5736e735ee01c6ecc6d4ab35b2d905418bb0d2128de098b73e11dd5decc34f","impliedFormat":1},{"version":"b64e159c49afc6499005756f5a7c2397c917525ceab513995f047cdd80b04bdf","impliedFormat":1},{"version":"f72b400dbf8f27adbda4c39a673884cb05daf8e0a1d8152eec2480f5700db36c","impliedFormat":1},{"version":"24509d0601fc00c4d77c20cacddbca6b878025f4e0712bddd171c7917f8cdcde","impliedFormat":1},{"version":"5f5baa59149d3d6d6cef2c09d46bb4d19beb10d6bee8c05b7850c33535b3c438","impliedFormat":1},{"version":"f17a51aae728f9f1a2290919cf29a927621b27f6ae91697aee78f41d48851690","impliedFormat":1},{"version":"be02e3c3cb4e187fd252e7ae12f6383f274e82288c8772bb0daf1a4e4af571ad","impliedFormat":1},{"version":"82ca40fb541799273571b011cd9de6ee9b577ef68acc8408135504ae69365b74","impliedFormat":1},{"version":"8fb6646db72914d6ef0692ea88b25670bbf5e504891613a1f46b42783ec18cce","impliedFormat":1},{"version":"07b0cb8b69e71d34804bde3e6dc6faaae8299f0118e9566b94e1f767b8ba9d64","impliedFormat":1},{"version":"213aa21650a910d95c4d0bee4bb936ecd51e230c1a9e5361e008830dcc73bc86","impliedFormat":1},{"version":"874a8c5125ad187e47e4a8eacc809c866c0e71b619a863cc14794dd3ccf23940","impliedFormat":1},{"version":"c31db8e51e85ee67018ac2a40006910efbb58e46baea774cf1f245d99bf178b5","impliedFormat":1},{"version":"31fac222250b18ebac0158938ede4b5d245e67d29cd2ef1e6c8a5859d137d803","impliedFormat":1},{"version":"a9dfb793a7e10949f4f3ea9f282b53d3bd8bf59f5459bc6e618e3457ed2529f5","impliedFormat":1},{"version":"2a77167687b0ec0c36ef581925103f1dc0c69993f61a9dbd299dcd30601af487","impliedFormat":1},{"version":"0f23b5ce60c754c2816c2542b9b164d6cb15243f4cbcd11cfafcab14b60e04d0","impliedFormat":1},{"version":"813ce40a8c02b172fdbeb8a07fdd427ac68e821f0e20e3dc699fb5f5bdf1ef0a","impliedFormat":1},{"version":"5ce6b24d5fd5ebb1e38fe817b8775e2e00c94145ad6eedaf26e3adf8bb3903d0","impliedFormat":1},{"version":"6babca69d3ae17be168cfceb91011eed881d41ce973302ee4e97d68a81c514b4","impliedFormat":1},{"version":"3e0832bc2533c0ec6ffcd61b7c055adedcca1a45364b3275c03343b83c71f5b3","impliedFormat":1},{"version":"342418c52b55f721b043183975052fb3956dae3c1f55f965fedfbbf4ad540501","impliedFormat":1},{"version":"6a6ab1edb5440ee695818d76f66d1a282a31207707e0d835828341e88e0c1160","impliedFormat":1},{"version":"7e9b4669774e97f5dc435ddb679aa9e7d77a1e5a480072c1d1291892d54bf45c","impliedFormat":1},{"version":"de439ddbed60296fbd1e5b4d242ce12aad718dffe6432efcae1ad6cd996defd3","impliedFormat":1},{"version":"ce5fb71799f4dbb0a9622bf976a192664e6c574d125d3773d0fa57926387b8b2","impliedFormat":1},{"version":"b9c0de070a5876c81540b1340baac0d7098ea9657c6653731a3199fcb2917cef","impliedFormat":1},{"version":"cbc91ecd74d8f9ddcbcbdc2d9245f14eff5b2f6ae38371283c97ca7dc3c4a45f","impliedFormat":1},{"version":"3ca1d6f016f36c61a59483c80d8b9f9d50301fbe52a0dde288c1381862b13636","impliedFormat":1},{"version":"ecfef0c0ff0c80ac9a6c2fab904a06b680fb5dfe8d9654bb789e49c6973cb781","impliedFormat":1},{"version":"0ee2eb3f7c0106ccf6e388bc0a16e1b3d346e88ac31b6a5bbc15766e43992167","impliedFormat":1},{"version":"f9592b77fd32a7a1262c1e9363d2e43027f513d1d2ff6b21e1cfdac4303d5a73","impliedFormat":1},{"version":"7e46dd61422e5afe88c34e5f1894ae89a37b7a07393440c092e9dc4399820172","impliedFormat":1},{"version":"9df4f57d7279173b0810154c174aa03fd60f5a1f0c3acfe8805e55e935bdecd4","impliedFormat":1},{"version":"a02a51b68a60a06d4bd0c747d6fbade0cb87eefda5f985fb4650e343da424f12","impliedFormat":1},{"version":"0cf851e2f0ecf61cabe64efd72de360246bcb8c19c6ef7b5cbb702293e1ff755","impliedFormat":1},{"version":"0c0e0aaf37ab0552dffc13eb584d8c56423b597c1c49f7974695cb45e2973de6","impliedFormat":1},{"version":"e2e0cd8f6470bc69bbfbc5e758e917a4e0f9259da7ffc93c0930516b0aa99520","impliedFormat":1},{"version":"180de8975eff720420697e7b5d95c0ecaf80f25d0cea4f8df7fe9cf817d44884","impliedFormat":1},{"version":"424a7394f9704d45596dce70bd015c5afec74a1cc5760781dfda31bc300df88f","impliedFormat":1},{"version":"044a62b9c967ee8c56dcb7b2090cf07ef2ac15c07e0e9c53d99fab7219ee3d67","impliedFormat":1},{"version":"3903b01a9ba327aae8c7ea884cdabc115d27446fba889afc95fddca8a9b4f6e2","impliedFormat":1},{"version":"78fd8f2504fbfb0070569729bf2fe41417fdf59f8c3e975ab3143a96f03e0a4a","impliedFormat":1},{"version":"8afd4f91e3a060a886a249f22b23da880ec12d4a20b6404acc5e283ef01bdd46","impliedFormat":1},{"version":"72e72e3dea4081877925442f67b23be151484ef0a1565323c9af7f1c5a0820f0","impliedFormat":1},{"version":"fa8c21bafd5d8991019d58887add8971ccbe88243c79bbcaec2e2417a40af4e8","impliedFormat":1},{"version":"ab35597fd103b902484b75a583606f606ab2cef7c069fae6c8aca0f058cee77d","impliedFormat":1},{"version":"ca54ec33929149dded2199dca95fd8ad7d48a04f6e8500f3f84a050fa77fee45","impliedFormat":1},{"version":"cac7dcf6f66d12979cc6095f33edc7fbb4266a44c8554cd44cd04572a4623fd0","impliedFormat":1},{"version":"98af566e6d420e54e4d8d942973e7fbe794e5168133ad6658b589d9dfb4409d8","impliedFormat":1},{"version":"772b2865dd86088c6e0cab71e23534ad7254961c1f791bdeaf31a57a2254df43","impliedFormat":1},{"version":"786d837fba58af9145e7ad685bc1990f52524dc4f84f3e60d9382a0c3f4a0f77","impliedFormat":1},{"version":"539dd525bf1d52094e7a35c2b4270bee757d3a35770462bcb01cd07683b4d489","impliedFormat":1},{"version":"69135303a105f3b058d79ea7e582e170721e621b1222e8f8e51ea29c61cd3acf","impliedFormat":1},{"version":"e92e6f0d63e0675fe2538e8031e1ece36d794cb6ecc07a036d82c33fa3e091a9","impliedFormat":1},{"version":"1fdb07843cdb9bd7e24745d357c6c1fde5e7f2dd7c668dd68b36c0dff144a390","impliedFormat":1},{"version":"3e2f739bdfb6b194ae2af13316b4c5bb18b3fe81ac340288675f92ba2061b370","affectsGlobalScope":true,"impliedFormat":1},{"version":"96d14f21b7652903852eef49379d04dbda28c16ed36468f8c9fa08f7c14c9538","impliedFormat":1},{"version":"d4a22007b481fe2a2e6bfd3a42c00cd62d41edb36d30fc4697df2692e9891fc8","impliedFormat":1},{"version":"29f72ec1289ae3aeda78bf14b38086d3d803262ac13904b400422941a26a3636","affectsGlobalScope":true,"impliedFormat":1},{"version":"7fadb2778688ebf3fd5b8d04f63d5bf27a43a3e420bc80732d3c6239067d1a4b","impliedFormat":1},{"version":"22293bd6fa12747929f8dfca3ec1684a3fe08638aa18023dd286ab337e88a592","impliedFormat":1},{"version":"170d4db14678c68178ee8a3d5a990d5afb759ecb6ec44dbd885c50f6da6204f6","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac51dd7d31333793807a6abaa5ae168512b6131bd41d9c5b98477fc3b7800f9f","impliedFormat":1},{"version":"5e76305d58bcdc924ff2bf14f6a9dc2aa5441ed06464b7e7bd039e611d66a89b","impliedFormat":1},{"version":"be1cc4d94ea60cbe567bc29ed479d42587bf1e6cba490f123d329976b0fe4ee5","impliedFormat":1},{"version":"510616459e6edd01acbce333fb256e06bdffdad43ca233a9090164bf8bb83912","impliedFormat":1},{"version":"d1e9031cfefebb12d6672ef7d85faf2c5a23472f5b5be1909358db426fe82eef","impliedFormat":1},{"version":"ce6a3f09b8db73a7e9701aca91a04b4fabaf77436dd35b24482f9ee816016b17","impliedFormat":1},{"version":"20e086e5b64fdd52396de67761cc0e94693494deadb731264aac122adf08de3f","impliedFormat":1},{"version":"6e78f75403b3ec65efb41c70d392aeda94360f11cedc9fb2c039c9ea23b30962","impliedFormat":1},{"version":"c863198dae89420f3c552b5a03da6ed6d0acfa3807a64772b895db624b0de707","impliedFormat":1},{"version":"8b03a5e327d7db67112ebbc93b4f744133eda2c1743dbb0a990c61a8007823ef","impliedFormat":1},{"version":"42fad1f540271e35ca37cecda12c4ce2eef27f0f5cf0f8dd761d723c744d3159","impliedFormat":1},{"version":"ff3743a5de32bee10906aff63d1de726f6a7fd6ee2da4b8229054dfa69de2c34","impliedFormat":1},{"version":"83acd370f7f84f203e71ebba33ba61b7f1291ca027d7f9a662c6307d74e4ac22","impliedFormat":1},{"version":"1445cec898f90bdd18b2949b9590b3c012f5b7e1804e6e329fb0fe053946d5ec","impliedFormat":1},{"version":"0e5318ec2275d8da858b541920d9306650ae6ac8012f0e872fe66eb50321a669","impliedFormat":1},{"version":"cf530297c3fb3a92ec9591dd4fa229d58b5981e45fe6702a0bd2bea53a5e59be","impliedFormat":1},{"version":"c1f6f7d08d42148ddfe164d36d7aba91f467dbcb3caa715966ff95f55048b3a4","impliedFormat":1},{"version":"eefd2bbc8edb14c3bd1246794e5c070a80f9b8f3730bd42efb80df3cc50b9039","impliedFormat":1},{"version":"0c1ee27b8f6a00097c2d6d91a21ee4d096ab52c1e28350f6362542b55380059a","impliedFormat":1},{"version":"7677d5b0db9e020d3017720f853ba18f415219fb3a9597343b1b1012cfd699f7","impliedFormat":1},{"version":"bc1c6bc119c1784b1a2be6d9c47addec0d83ef0d52c8fbe1f14a51b4dfffc675","impliedFormat":1},{"version":"52cf2ce99c2a23de70225e252e9822a22b4e0adb82643ab0b710858810e00bf1","impliedFormat":1},{"version":"770625067bb27a20b9826255a8d47b6b5b0a2d3dfcbd21f89904c731f671ba77","impliedFormat":1},{"version":"d1ed6765f4d7906a05968fb5cd6d1db8afa14dbe512a4884e8ea5c0f5e142c80","impliedFormat":1},{"version":"799c0f1b07c092626cf1efd71d459997635911bb5f7fc1196efe449bba87e965","impliedFormat":1},{"version":"2a184e4462b9914a30b1b5c41cf80c6d3428f17b20d3afb711fff3f0644001fd","impliedFormat":1},{"version":"9eabde32a3aa5d80de34af2c2206cdc3ee094c6504a8d0c2d6d20c7c179503cc","impliedFormat":1},{"version":"397c8051b6cfcb48aa22656f0faca2553c5f56187262135162ee79d2b2f6c966","impliedFormat":1},{"version":"a8ead142e0c87dcd5dc130eba1f8eeed506b08952d905c47621dc2f583b1bff9","impliedFormat":1},{"version":"a02f10ea5f73130efca046429254a4e3c06b5475baecc8f7b99a0014731be8b3","impliedFormat":1},{"version":"c2576a4083232b0e2d9bd06875dd43d371dee2e090325a9eac0133fd5650c1cb","impliedFormat":1},{"version":"4c9a0564bb317349de6a24eb4efea8bb79898fa72ad63a1809165f5bd42970dd","impliedFormat":1},{"version":"f40ac11d8859092d20f953aae14ba967282c3bb056431a37fced1866ec7a2681","impliedFormat":1},{"version":"cc11e9e79d4746cc59e0e17473a59d6f104692fd0eeea1bdb2e206eabed83b03","impliedFormat":1},{"version":"b444a410d34fb5e98aa5ee2b381362044f4884652e8bc8a11c8fe14bbd85518e","impliedFormat":1},{"version":"c35808c1f5e16d2c571aa65067e3cb95afeff843b259ecfa2fc107a9519b5392","impliedFormat":1},{"version":"14d5dc055143e941c8743c6a21fa459f961cbc3deedf1bfe47b11587ca4b3ef5","impliedFormat":1},{"version":"a3ad4e1fc542751005267d50a6298e6765928c0c3a8dce1572f2ba6ca518661c","impliedFormat":1},{"version":"f237e7c97a3a89f4591afd49ecb3bd8d14f51a1c4adc8fcae3430febedff5eb6","impliedFormat":1},{"version":"3ffdfbec93b7aed71082af62b8c3e0cc71261cc68d796665faa1e91604fbae8f","impliedFormat":1},{"version":"662201f943ed45b1ad600d03a90dffe20841e725203ced8b708c91fcd7f9379a","impliedFormat":1},{"version":"c9ef74c64ed051ea5b958621e7fb853fe3b56e8787c1587aefc6ea988b3c7e79","impliedFormat":1},{"version":"2462ccfac5f3375794b861abaa81da380f1bbd9401de59ffa43119a0b644253d","impliedFormat":1},{"version":"34baf65cfee92f110d6653322e2120c2d368ee64b3c7981dff08ed105c4f19b0","impliedFormat":1},{"version":"a56fe175741cc8841835eb72e61fa5a34adcbc249ede0e3494c229f0750f6b85","impliedFormat":1},{"version":"ddef25f825320de051dcb0e62ffce621b41c67712b5b4105740c32fd83f4c449","impliedFormat":1},{"version":"1b3dffaa4ca8e38ac434856843505af767a614d187fb3a5ef4fcebb023c355aa","impliedFormat":1},{"version":"cbb2be0de4e2a597ba2b7050389629fb4c2592a923e989d45095d1546913a696","impliedFormat":1},{"version":"aadcf59329c6f0f8978cfa1810dab147e0b67f241a39c965c4904459fcc98c27","impliedFormat":1},{"version":"ab82804a14454734010dcdcd43f564ff7b0389bee4c5692eec76ff5b30d4cf66","impliedFormat":1},{"version":"15fe687c59d62741b4494d5e623d497d55eb38966ecf5bea7f36e48fc3fbe15e","impliedFormat":1},{"version":"2c3b8be03577c98530ef9cb1a76e2c812636a871f367e9edf4c5f3ce702b77f8","affectsGlobalScope":true,"impliedFormat":1},{"version":"f874ea4d0091b0a44362a5f74d26caab2e66dec306c2bf7e8965f5106e784c3b","impliedFormat":1},{"version":"caaf6bf4dd0a3f88b50670f5786141b209edb54ba17eb6268c2d28919c813606","affectsGlobalScope":true,"impliedFormat":1},{"version":"bc3d87d494138b037f883840bef538bc25c1432fcd9afb0b71e438319e08780e","affectsGlobalScope":true,"impliedFormat":1},{"version":"f2f23fe34b735887db1d5597714ae37a6ffae530cafd6908c9d79d485667c956","impliedFormat":1},{"version":"5bba0e6cd8375fd37047e99a080d1bd9a808c95ecb7f3043e3adc125196f6607","impliedFormat":1},{"version":"1ba59c8bbeed2cb75b239bb12041582fa3e8ef32f8d0bd0ec802e38442d3f317","impliedFormat":1},{"version":"bae8d023ef6b23df7da26f51cea44321f95817c190342a36882e93b80d07a960","impliedFormat":1},{"version":"26a770cec4bd2e7dbba95c6e536390fffe83c6268b78974a93727903b515c4e7","impliedFormat":1}],"root":[65,66,[68,71]],"options":{"allowSyntheticDefaultImports":true,"composite":true,"declaration":true,"declarationMap":true,"esModuleInterop":true,"module":1,"outDir":"./dist","rootDir":"./src","skipLibCheck":true,"sourceMap":true,"strict":true,"target":10},"referencedMap":[[74,1],[72,2],[197,3],[214,2],[408,4],[407,5],[218,6],[219,7],[356,6],[357,8],[338,9],[339,10],[222,11],[223,12],[293,13],[294,14],[267,6],[268,15],[261,6],[262,16],[353,17],[351,18],[352,2],[367,19],[368,20],[237,21],[238,22],[369,23],[370,24],[371,25],[372,26],[229,27],[230,28],[355,29],[354,30],[340,6],[341,31],[233,32],[234,33],[257,2],[258,34],[375,35],[373,36],[374,37],[376,38],[377,39],[380,40],[378,41],[381,18],[379,42],[382,43],[385,44],[383,45],[384,46],[386,47],[235,27],[236,48],[361,49],[358,50],[359,51],[360,2],[336,52],[337,53],[281,54],[280,55],[278,56],[277,57],[279,58],[388,59],[387,60],[390,61],[389,62],[266,63],[265,6],[244,64],[242,65],[241,11],[243,66],[393,67],[397,68],[391,69],[392,70],[394,67],[395,67],[396,67],[283,71],[282,11],[299,72],[297,73],[298,18],[295,74],[296,75],[232,76],[231,6],[289,77],[220,6],[221,78],[288,79],[326,80],[329,81],[327,82],[328,83],[240,84],[239,6],[331,85],[330,11],[309,86],[308,6],[264,87],[263,6],[335,88],[334,89],[303,90],[302,91],[300,92],[301,93],[292,94],[291,95],[290,96],[399,97],[398,98],[316,99],[315,100],[314,101],[363,102],[362,2],[307,103],[306,104],[304,105],[305,106],[285,107],[284,11],[228,108],[227,109],[226,110],[225,111],[224,112],[320,113],[319,114],[250,115],[249,11],[254,116],[253,117],[318,118],[317,6],[364,2],[366,119],[365,2],[323,120],[322,121],[321,122],[401,123],[400,124],[403,125],[402,126],[349,127],[350,128],[348,129],[287,130],[286,2],[333,131],[332,132],[260,133],[259,6],[311,134],[310,6],[217,135],[216,2],[270,136],[271,137],[276,138],[269,139],[273,140],[272,141],[274,142],[275,143],[325,144],[324,11],[256,145],[255,11],[406,146],[405,147],[404,148],[343,149],[342,6],[313,150],[312,6],[248,151],[246,152],[245,11],[247,153],[345,154],[344,6],[252,155],[251,6],[347,156],[346,6],[77,157],[73,1],[75,158],[76,1],[183,159],[184,160],[189,161],[182,162],[191,163],[192,164],[200,165],[196,166],[195,167],[201,168],[193,2],[188,169],[206,170],[208,171],[209,2],[202,2],[210,172],[211,2],[212,173],[213,174],[414,175],[194,2],[415,2],[416,171],[203,2],[417,2],[190,2],[418,164],[129,176],[130,176],[131,177],[83,178],[132,179],[133,180],[134,181],[78,2],[81,182],[79,2],[80,2],[135,183],[136,184],[137,185],[138,186],[139,187],[140,188],[141,188],[142,189],[143,190],[144,191],[145,192],[84,2],[82,2],[146,193],[147,194],[148,195],[181,196],[149,197],[150,198],[151,199],[152,200],[153,201],[154,202],[155,203],[156,204],[157,205],[158,206],[159,206],[160,207],[161,2],[162,208],[163,209],[165,210],[164,211],[166,212],[167,213],[168,214],[169,215],[170,216],[171,217],[172,218],[173,219],[174,220],[175,221],[176,222],[177,223],[178,224],[85,2],[86,2],[87,2],[126,225],[127,2],[128,2],[179,226],[180,227],[419,2],[186,2],[187,2],[423,228],[420,2],[422,229],[424,2],[425,2],[450,230],[451,231],[427,232],[430,233],[448,230],[449,230],[439,230],[438,234],[436,230],[431,230],[444,230],[442,230],[446,230],[426,230],[443,230],[447,230],[432,230],[433,230],[445,230],[428,230],[434,230],[435,230],[437,230],[441,230],[452,235],[440,230],[429,230],[465,236],[464,2],[459,235],[461,237],[460,235],[453,235],[454,235],[456,235],[458,235],[462,237],[463,237],[455,237],[457,237],[185,238],[466,239],[205,240],[204,241],[467,162],[469,242],[468,243],[470,2],[472,244],[471,2],[207,2],[473,2],[475,2],[474,2],[476,2],[477,2],[478,245],[479,2],[480,246],[63,2],[88,2],[215,2],[421,2],[67,2],[199,247],[198,248],[413,249],[410,250],[411,251],[412,2],[64,252],[409,253],[61,2],[62,2],[12,2],[11,2],[2,2],[13,2],[14,2],[15,2],[16,2],[17,2],[18,2],[19,2],[20,2],[3,2],[21,2],[22,2],[4,2],[23,2],[27,2],[24,2],[25,2],[26,2],[28,2],[29,2],[30,2],[5,2],[31,2],[32,2],[33,2],[34,2],[6,2],[38,2],[35,2],[36,2],[37,2],[39,2],[7,2],[40,2],[45,2],[46,2],[41,2],[42,2],[43,2],[44,2],[8,2],[50,2],[47,2],[48,2],[49,2],[51,2],[9,2],[52,2],[53,2],[54,2],[56,2],[55,2],[57,2],[58,2],[10,2],[59,2],[1,2],[60,2],[104,254],[114,255],[103,254],[124,256],[95,257],[94,258],[123,164],[117,259],[122,260],[97,261],[111,262],[96,263],[120,264],[92,265],[91,164],[121,266],[93,267],[98,268],[99,2],[102,268],[89,2],[125,269],[115,270],[106,271],[107,272],[109,273],[105,274],[108,275],[118,164],[100,276],[101,277],[110,278],[90,279],[113,270],[112,268],[116,2],[119,280],[65,281],[66,2],[69,282],[70,283],[68,284],[71,285]],"latestChangedDtsFile":"./dist/index.d.ts","version":"5.9.3"} \ No newline at end of file From 62a3e5768e762705dab09cfd015799a18434ebae Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Tue, 17 Feb 2026 12:49:44 +0100 Subject: [PATCH 18/25] docs: add future work item for undeclared BSON type names in TS definitions The bsonToTypeScriptMap emits non-built-in type names (ObjectId, Binary, Timestamp, etc.) without corresponding import statements or declare stubs. Currently harmless since the output is for display/hover only, but should be addressed if the TS definition is ever consumed by a real TS language service. Addresses PR #506 review comment from copilot. --- .../json/data-api/autocomplete/future-work.md | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/utils/json/data-api/autocomplete/future-work.md b/src/utils/json/data-api/autocomplete/future-work.md index 6e77d2913..1ee321564 100644 --- a/src/utils/json/data-api/autocomplete/future-work.md +++ b/src/utils/json/data-api/autocomplete/future-work.md @@ -138,3 +138,47 @@ This is a **breaking change** to the `FieldEntry` interface. Affected consumers: - `SchemaAnalyzer.ts` (returns `FieldEntry[]` via `getKnownFields`) **Recommendation:** Defer until the completion provider is built. The ambiguity only matters for fields with literal dots, which are uncommon. When fixing, do it as a single atomic change across all consumers. + +--- + +## 4. TypeScript definition output references undeclared BSON type names + +**Severity:** Low — the TS definition is for display/hover only, not compiled or type-checked +**File:** `toTypeScriptDefinition.ts` — `bsonToTypeScriptMap` +**When to fix:** Before the TS definition is used in a context where type correctness matters (e.g., Monaco intellisense with an actual TS language service) + +### Problem + +The BSON-to-TypeScript type mapping emits non-built-in type names such as `ObjectId`, `Binary`, `Timestamp`, `MinKey`, `MaxKey`, `Code`, `DBRef`, and `UUID`. These are MongoDB BSON driver types, but the generated definition string doesn't include `import` statements or `declare` stubs for them. + +If the output is ever fed to a TypeScript compiler or language service (e.g., Monaco with full TS checking), it will report "Cannot find name 'ObjectId'" etc. + +### Current state + +The generated output is used for documentation/hover display only — it's rendered as syntax-highlighted text, not compiled. So this is purely cosmetic today. + +### Proposed fix (when needed) + +**Option A — Emit `import type`:** +```typescript +import type { ObjectId, Binary, Timestamp, MinKey, MaxKey, Code, DBRef, UUID } from 'mongodb'; +``` +Only include types that actually appear in the schema. + +**Option B — Emit `declare type` stubs:** +```typescript +declare type ObjectId = { toString(): string }; +declare type Binary = { length(): number }; +// ... etc. +``` +Lightweight, no dependency on the `mongodb` package. + +**Option C — Map everything to primitive types:** +```typescript +ObjectId → string // (its string representation) +Binary → Uint8Array +Timestamp → { t: number; i: number } +``` +Loses semantic precision but avoids the undeclared-type problem entirely. + +**Recommendation:** Option A is the most correct approach. Collect the set of non-built-in types actually used in the schema, then prepend a single `import type` line. Defer until the output is consumed by a real TS language service. From 4b3dd00d88d117d6b37cf50f690563dca697cb28 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Tue, 17 Feb 2026 12:50:33 +0100 Subject: [PATCH 19/25] fix: toInterfaceName handles digit-leading and separator-only collection names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Prefix with _ when PascalCase result starts with a digit (e.g. '123abc' → '_123abcDocument') - Fall back to 'CollectionDocument' when name is empty or only separators - Filter empty segments from split result - Add tests for edge cases Addresses PR #506 review comment from copilot. --- .../toTypeScriptDefinition.test.ts | 24 +++++++++++++++++++ .../autocomplete/toTypeScriptDefinition.ts | 15 +++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.test.ts b/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.test.ts index db2ed0176..a5628e7f1 100644 --- a/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.test.ts +++ b/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.test.ts @@ -207,6 +207,30 @@ describe('toTypeScriptDefinition', () => { ); }); + it('prefixes with _ when collection name starts with a digit', () => { + expect(toTypeScriptDefinition({ 'x-documentsInspected': 0 }, '123abc')).toContain( + 'interface _123abcDocument', + ); + expect(toTypeScriptDefinition({ 'x-documentsInspected': 0 }, '99_bottles')).toContain( + 'interface _99BottlesDocument', + ); + }); + + it('falls back to CollectionDocument when name is only separators', () => { + expect(toTypeScriptDefinition({ 'x-documentsInspected': 0 }, '---')).toContain( + 'interface CollectionDocument', + ); + expect(toTypeScriptDefinition({ 'x-documentsInspected': 0 }, '_ _ _')).toContain( + 'interface CollectionDocument', + ); + }); + + it('falls back to CollectionDocument for empty string', () => { + expect(toTypeScriptDefinition({ 'x-documentsInspected': 0 }, '')).toContain( + 'interface CollectionDocument', + ); + }); + describe('special character field names', () => { function makeSchemaWithField(fieldName: string): JSONSchema { return { diff --git a/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.ts b/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.ts index 559162fc0..5f23cf652 100644 --- a/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.ts +++ b/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.ts @@ -70,16 +70,29 @@ function safePropertyName(name: string): string { /** * Converts a collection name to PascalCase and appends "Document". + * If the result would start with a digit, a leading `_` is prepended. + * If the collection name contains only separators or is empty, falls back to "CollectionDocument". + * * Examples: * - "users" → "UsersDocument" * - "order_items" → "OrderItemsDocument" + * - "123abc" → "_123abcDocument" + * - "---" → "CollectionDocument" */ function toInterfaceName(collectionName: string): string { const pascal = collectionName .split(/[_\-\s]+/) + .filter((s) => s.length > 0) .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)) .join(''); - return `${pascal}Document`; + + if (pascal.length === 0) { + return 'CollectionDocument'; + } + + // Prefix with _ if the first character is a digit (invalid TS identifier start) + const prefix = /^[0-9]/.test(pascal) ? '_' : ''; + return `${prefix}${pascal}Document`; } /** From 37e64e5d8dfa7e4af384d4215b369508fe2b6bde Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Tue, 17 Feb 2026 12:50:57 +0100 Subject: [PATCH 20/25] docs: clarify boolean JSONSchemaRef safety in getKnownFields Add comment explaining why the cast to JSONSchema is safe: our SchemaAnalyzer never produces boolean schema refs. Notes that a typeof guard should be added if the function is ever reused with externally-sourced schemas. Addresses PR #506 review comment from copilot. --- packages/schema-analyzer/src/getKnownFields.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/schema-analyzer/src/getKnownFields.ts b/packages/schema-analyzer/src/getKnownFields.ts index d3f12ee89..5c5f82bdc 100644 --- a/packages/schema-analyzer/src/getKnownFields.ts +++ b/packages/schema-analyzer/src/getKnownFields.ts @@ -58,6 +58,13 @@ export function getKnownFields(schema: JSONSchema): FieldEntry[] { const queue: Denque = new Denque(); // Initialize the queue with root properties + // + // Note: JSON Schema allows boolean values as schema references (true = accept all, + // false = reject all), but our SchemaAnalyzer never produces boolean refs — it always + // emits full schema objects. The cast to JSONSchema below is therefore safe for our + // use case. If this function were ever reused with externally-sourced schemas, a + // `typeof propSchema === 'boolean'` guard should be added here and in the nested + // property loop below. if (schema.properties) { for (const propName of Object.keys(schema.properties)) { const propSchema = schema.properties[propName] as JSONSchema; From b17e9cc2cead212b66382f5bf08c4c2409affa98 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Tue, 17 Feb 2026 12:52:13 +0100 Subject: [PATCH 21/25] =?UTF-8?q?fix:=20insertText=20escaping=20=E2=80=94?= =?UTF-8?q?=20use=20identifier=20check=20+=20escape=20quotes/backslashes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace SPECIAL_CHARS_PATTERN with JS_IDENTIFIER_PATTERN for proper identifier validity check (catches dashes, brackets, digits, quotes, etc.) - Escape embedded double quotes and backslashes when quoting insertText - Add tests for all edge cases (dashes, brackets, digits, quotes, backslashes) - Mark future-work item #1 as resolved; item #2 (referenceText/$getField) remains open for aggregation completion provider phase Addresses PR #506 review comment from copilot. --- .../json/data-api/autocomplete/future-work.md | 39 +++------------- .../toFieldCompletionItems.test.ts | 44 +++++++++++++++++++ .../autocomplete/toFieldCompletionItems.ts | 27 +++++++----- 3 files changed, 65 insertions(+), 45 deletions(-) diff --git a/src/utils/json/data-api/autocomplete/future-work.md b/src/utils/json/data-api/autocomplete/future-work.md index 1ee321564..7554a383b 100644 --- a/src/utils/json/data-api/autocomplete/future-work.md +++ b/src/utils/json/data-api/autocomplete/future-work.md @@ -5,42 +5,13 @@ These must be resolved before the completion providers ship to users. --- -## 1. `SPECIAL_CHARS_PATTERN` is incomplete + `insertText` quoting doesn't escape +## ~~1. `SPECIAL_CHARS_PATTERN` is incomplete + `insertText` quoting doesn't escape~~ ✅ RESOLVED -**Severity:** Medium — will produce broken query expressions for real-world field names -**File:** `toFieldCompletionItems.ts` — `SPECIAL_CHARS_PATTERN` and `insertText` construction -**When to fix:** Before the `CompletionItemProvider` is wired up +**Resolved in:** PR #506 (commit addressing copilot review comment) -### Problem - -`SPECIAL_CHARS_PATTERN` (`/[.$\s]/`) only catches dots, `$`, and whitespace. MongoDB field names can also contain: - -| Character | Example field name | Current behavior | -| ---------------- | ------------------ | ----------------------------------------------- | -| Dash `-` | `order-items` | Inserted unquoted → breaks JSON key context | -| Brackets `[]` | `items[0]` | Inserted unquoted | -| Double quote `"` | `say"hi"` | Wrapped as `"say"hi""` → broken string | -| Single quote `'` | `it's` | Inserted unquoted (may break some contexts) | -| Backslash `\` | `back\slash` | Wrapped as `"back\slash"` → unescaped backslash | - -Additionally, when quoting _is_ triggered, the current logic (`"${entry.path}"`) does not escape embedded `"` or `\` inside the value. - -### Proposed fix - -1. Replace `SPECIAL_CHARS_PATTERN` with an identifier check (same as `safePropertyName` in `toTypeScriptDefinition.ts`): - ```typescript - const JS_IDENTIFIER = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/; - const needsQuoting = !JS_IDENTIFIER.test(entry.path); - ``` -2. When quoting, escape the content: - ```typescript - const escaped = entry.path.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); - insertText: needsQuoting ? `"${escaped}"` : entry.path, - ``` - -### Note on display vs insert - -The `fieldName` property intentionally stays unescaped (human-readable) for the completion list label. Only `insertText` gets escaped — this is by design, so users see clean names in the dropdown and the escaped form is inserted on selection. +Replaced `SPECIAL_CHARS_PATTERN` with `JS_IDENTIFIER_PATTERN` — a proper identifier validity check. +Added `\` → `\\` and `"` → `\"` escaping when quoting `insertText`. +Tests cover dashes, brackets, digits, embedded quotes, and backslashes. --- diff --git a/src/utils/json/data-api/autocomplete/toFieldCompletionItems.test.ts b/src/utils/json/data-api/autocomplete/toFieldCompletionItems.test.ts index 947b90629..37a7ecc4e 100644 --- a/src/utils/json/data-api/autocomplete/toFieldCompletionItems.test.ts +++ b/src/utils/json/data-api/autocomplete/toFieldCompletionItems.test.ts @@ -39,6 +39,50 @@ describe('toFieldCompletionItems', () => { expect(result[1].insertText).toBe('"user.profile.bio"'); }); + it('quotes field names with dashes', () => { + const fields: FieldEntry[] = [{ path: 'order-items', type: 'string', bsonType: 'string' }]; + const result = toFieldCompletionItems(fields); + expect(result[0].insertText).toBe('"order-items"'); + expect(result[0].fieldName).toBe('order-items'); // display stays unescaped + }); + + it('quotes field names with brackets', () => { + const fields: FieldEntry[] = [{ path: 'items[0]', type: 'string', bsonType: 'string' }]; + const result = toFieldCompletionItems(fields); + expect(result[0].insertText).toBe('"items[0]"'); + }); + + it('quotes field names starting with a digit', () => { + const fields: FieldEntry[] = [{ path: '123abc', type: 'string', bsonType: 'string' }]; + const result = toFieldCompletionItems(fields); + expect(result[0].insertText).toBe('"123abc"'); + }); + + it('escapes embedded double quotes in insertText', () => { + const fields: FieldEntry[] = [{ path: 'say"hi"', type: 'string', bsonType: 'string' }]; + const result = toFieldCompletionItems(fields); + expect(result[0].insertText).toBe('"say\\"hi\\""'); + expect(result[0].fieldName).toBe('say"hi"'); // display stays unescaped + }); + + it('escapes backslashes in insertText', () => { + const fields: FieldEntry[] = [{ path: 'back\\slash', type: 'string', bsonType: 'string' }]; + const result = toFieldCompletionItems(fields); + expect(result[0].insertText).toBe('"back\\\\slash"'); + }); + + it('does not quote valid identifiers', () => { + const fields: FieldEntry[] = [ + { path: 'name', type: 'string', bsonType: 'string' }, + { path: '_id', type: 'string', bsonType: 'objectid' }, + { path: '$type', type: 'string', bsonType: 'string' }, + ]; + const result = toFieldCompletionItems(fields); + expect(result[0].insertText).toBe('name'); + expect(result[1].insertText).toBe('_id'); + expect(result[2].insertText).toBe('$type'); + }); + it('adds $ prefix to referenceText', () => { const fields: FieldEntry[] = [ { path: 'age', type: 'number', bsonType: 'int32' }, diff --git a/src/utils/json/data-api/autocomplete/toFieldCompletionItems.ts b/src/utils/json/data-api/autocomplete/toFieldCompletionItems.ts index 4998dad64..63e85bd92 100644 --- a/src/utils/json/data-api/autocomplete/toFieldCompletionItems.ts +++ b/src/utils/json/data-api/autocomplete/toFieldCompletionItems.ts @@ -40,17 +40,14 @@ export interface FieldCompletionData { } /** - * Characters that require quoting in a field name for insert text. + * Matches valid JavaScript/TypeScript identifiers. + * A valid identifier starts with a letter, underscore, or dollar sign, + * followed by zero or more letters, digits, underscores, or dollar signs. * - * TODO: This pattern currently only catches dots, `$`, and whitespace. It misses other - * characters that are valid in MongoDB field names but problematic in query expressions: - * dashes (`-`), brackets (`[`, `]`), quotes (`"`, `'`), and backslashes (`\`). - * Additionally, the quoting logic (`"${path}"`) does not escape embedded double quotes - * or backslashes inside the field name. Both gaps should be addressed when the - * CompletionItemProvider is wired up — the fix is to (1) widen this to a "is valid - * unquoted identifier" check and (2) escape `"` → `\"` and `\` → `\\` in insertText. + * Field names that do NOT match this pattern must be quoted and escaped + * in `insertText` to produce valid query expressions. */ -const SPECIAL_CHARS_PATTERN = /[.$\s]/; +const JS_IDENTIFIER_PATTERN = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/; /** * Converts an array of FieldEntry objects into completion-ready FieldCompletionData items. @@ -61,14 +58,22 @@ const SPECIAL_CHARS_PATTERN = /[.$\s]/; export function toFieldCompletionItems(fields: FieldEntry[]): FieldCompletionData[] { return fields.map((entry) => { const displayType = BSONTypes.toDisplayString(entry.bsonType as BSONTypes); - const needsQuoting = SPECIAL_CHARS_PATTERN.test(entry.path); + const needsQuoting = !JS_IDENTIFIER_PATTERN.test(entry.path); + + let insertText: string; + if (needsQuoting) { + const escaped = entry.path.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); + insertText = `"${escaped}"`; + } else { + insertText = entry.path; + } return { fieldName: entry.path, displayType, bsonType: entry.bsonType, isSparse: entry.isSparse ?? false, - insertText: needsQuoting ? `"${entry.path}"` : entry.path, + insertText, referenceText: `$${entry.path}`, }; }); From 35a13a1d5bacb82afaddfe484812339b8d1373d1 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Tue, 17 Feb 2026 13:03:42 +0100 Subject: [PATCH 22/25] refactor: streamline TypeScript definition tests for improved readability --- src/utils/json/data-api/autocomplete/future-work.md | 6 ++++++ .../autocomplete/toTypeScriptDefinition.test.ts | 12 +++--------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/utils/json/data-api/autocomplete/future-work.md b/src/utils/json/data-api/autocomplete/future-work.md index 7554a383b..cac0d4ab0 100644 --- a/src/utils/json/data-api/autocomplete/future-work.md +++ b/src/utils/json/data-api/autocomplete/future-work.md @@ -131,25 +131,31 @@ The generated output is used for documentation/hover display only — it's rende ### Proposed fix (when needed) **Option A — Emit `import type`:** + ```typescript import type { ObjectId, Binary, Timestamp, MinKey, MaxKey, Code, DBRef, UUID } from 'mongodb'; ``` + Only include types that actually appear in the schema. **Option B — Emit `declare type` stubs:** + ```typescript declare type ObjectId = { toString(): string }; declare type Binary = { length(): number }; // ... etc. ``` + Lightweight, no dependency on the `mongodb` package. **Option C — Map everything to primitive types:** + ```typescript ObjectId → string // (its string representation) Binary → Uint8Array Timestamp → { t: number; i: number } ``` + Loses semantic precision but avoids the undeclared-type problem entirely. **Recommendation:** Option A is the most correct approach. Collect the set of non-built-in types actually used in the schema, then prepend a single `import type` line. Defer until the output is consumed by a real TS language service. diff --git a/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.test.ts b/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.test.ts index a5628e7f1..d003b9ded 100644 --- a/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.test.ts +++ b/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.test.ts @@ -208,27 +208,21 @@ describe('toTypeScriptDefinition', () => { }); it('prefixes with _ when collection name starts with a digit', () => { - expect(toTypeScriptDefinition({ 'x-documentsInspected': 0 }, '123abc')).toContain( - 'interface _123abcDocument', - ); + expect(toTypeScriptDefinition({ 'x-documentsInspected': 0 }, '123abc')).toContain('interface _123abcDocument'); expect(toTypeScriptDefinition({ 'x-documentsInspected': 0 }, '99_bottles')).toContain( 'interface _99BottlesDocument', ); }); it('falls back to CollectionDocument when name is only separators', () => { - expect(toTypeScriptDefinition({ 'x-documentsInspected': 0 }, '---')).toContain( - 'interface CollectionDocument', - ); + expect(toTypeScriptDefinition({ 'x-documentsInspected': 0 }, '---')).toContain('interface CollectionDocument'); expect(toTypeScriptDefinition({ 'x-documentsInspected': 0 }, '_ _ _')).toContain( 'interface CollectionDocument', ); }); it('falls back to CollectionDocument for empty string', () => { - expect(toTypeScriptDefinition({ 'x-documentsInspected': 0 }, '')).toContain( - 'interface CollectionDocument', - ); + expect(toTypeScriptDefinition({ 'x-documentsInspected': 0 }, '')).toContain('interface CollectionDocument'); }); describe('special character field names', () => { From 1cb9e5b78fd5ab8da7e443c2cf0b02a617d7afb9 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Tue, 17 Feb 2026 13:37:07 +0100 Subject: [PATCH 23/25] docs: add terminology guidelines for DocumentDB and MongoDB API usage --- .github/copilot-instructions.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index e5a438ee1..78b2d1f06 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -178,6 +178,22 @@ For Discovery View, both `treeId` and `clusterId` are sanitized (all `/` replace See `src/tree/models/BaseClusterModel.ts` and `docs/analysis/08-cluster-model-simplification-plan.md` for details. +## Terminology + +This is a **DocumentDB** extension that uses the **MongoDB-compatible wire protocol**. + +- Use **"DocumentDB"** when referring to the database service itself. +- Use **"MongoDB API"** or **"DocumentDB API"** when referring to the wire protocol, query language, or API compatibility layer. +- **Never use "MongoDB" alone** as a product name in code, comments, docs, or user-facing strings. + +| ✅ Do | ❌ Don't | +| ---------------------------------------------------- | -------------------------------- | +| `// Query operators supported by the DocumentDB API` | `// MongoDB query operators` | +| `// BSON types per the MongoDB API spec` | `// Uses MongoDB's $match stage` | +| `documentdbQuery` (variable name) | `mongoQuery` | + +This applies to: code comments, JSDoc/TSDoc, naming (prefer `documentdb` prefix), user-facing strings, docs, and test descriptions. + ## Additional Patterns For detailed patterns, see: From 43915a576782dcb495f6ec7d1681ae3a9d4798c7 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Tue, 17 Feb 2026 14:25:57 +0100 Subject: [PATCH 24/25] refactor: replace 'console' assert with 'node:assert/strict' for improved consistency --- packages/schema-analyzer/src/SchemaAnalyzer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/schema-analyzer/src/SchemaAnalyzer.ts b/packages/schema-analyzer/src/SchemaAnalyzer.ts index b26f6fa76..8f24d532a 100644 --- a/packages/schema-analyzer/src/SchemaAnalyzer.ts +++ b/packages/schema-analyzer/src/SchemaAnalyzer.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { assert } from 'console'; import Denque from 'denque'; import { type Document, type WithId } from 'mongodb'; +import assert from 'node:assert/strict'; import { BSONTypes } from './BSONTypes'; import { type JSONSchema, type JSONSchemaRef } from './JSONSchema'; import { type FieldEntry, getKnownFields as getKnownFieldsFromSchema } from './getKnownFields'; From 75536e9e160d8961a33b58aadb65c03269586d08 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Tue, 17 Feb 2026 14:52:37 +0100 Subject: [PATCH 25/25] refactor: update documentation to consistently reference DocumentDB API alongside MongoDB API --- .github/copilot-instructions.md | 2 +- packages/schema-analyzer/README.md | 6 +++--- packages/schema-analyzer/package.json | 2 +- packages/schema-analyzer/src/BSONTypes.ts | 12 ++++++------ packages/schema-analyzer/src/ValueFormatters.ts | 6 +++--- packages/schema-analyzer/src/getKnownFields.ts | 6 +++--- src/utils/json/data-api/autocomplete/future-work.md | 4 ++-- .../data-api/autocomplete/toTypeScriptDefinition.ts | 2 +- 8 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 78b2d1f06..6a7852567 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,6 +1,6 @@ # GitHub Copilot Instructions for vscode-documentdb -VS Code Extension for Azure Cosmos DB and MongoDB. TypeScript (strict mode), React webviews, Jest testing. +VS Code Extension for Azure Cosmos DB and the MongoDB API. TypeScript (strict mode), React webviews, Jest testing. ## Critical Build Commands diff --git a/packages/schema-analyzer/README.md b/packages/schema-analyzer/README.md index 145611fe1..2010f2e7a 100644 --- a/packages/schema-analyzer/README.md +++ b/packages/schema-analyzer/README.md @@ -1,17 +1,17 @@ # @vscode-documentdb/schema-analyzer -Incremental JSON Schema analyzer for MongoDB and Azure Cosmos DB documents. Processes documents one at a time (or in batches) and produces an extended JSON Schema with statistical metadata — field occurrence counts, BSON type distributions, min/max values, and array length stats. +Incremental JSON Schema analyzer for DocumentDB API and MongoDB API documents. Processes documents one at a time (or in batches) and produces an extended JSON Schema with statistical metadata — field occurrence counts, BSON type distributions, min/max values, and array length stats. > **Note:** This package is not yet published to npm. We plan to publish it once the API stabilizes. For now, it is consumed internally via npm workspaces within the [vscode-documentdb](https://github.com/microsoft/vscode-documentdb) repository. ## Overview -The `SchemaAnalyzer` incrementally builds a JSON Schema by inspecting MongoDB/DocumentDB documents. It is designed for scenarios where documents arrive over time (streaming, pagination) and the schema needs to evolve as new documents are observed. +The `SchemaAnalyzer` incrementally builds a JSON Schema by inspecting DocumentDB API / MongoDB API documents. It is designed for scenarios where documents arrive over time (streaming, pagination) and the schema needs to evolve as new documents are observed. Key capabilities: - **Incremental analysis** — add documents one at a time or in batches; the schema updates in place. -- **BSON type awareness** — recognizes all MongoDB BSON types (`ObjectId`, `Decimal128`, `Binary`, `UUID`, etc.) and annotates them with `x-bsonType`. +- **BSON type awareness** — recognizes BSON types defined by the MongoDB API (`ObjectId`, `Decimal128`, `Binary`, `UUID`, etc.) and annotates them with `x-bsonType`. - **Statistical extensions** — tracks field occurrence (`x-occurrence`), type frequency (`x-typeOccurrence`), min/max values, string lengths, array sizes, and document counts (`x-documentsInspected`). - **Known fields extraction** — derives a flat list of known field paths with their types and occurrence probabilities, useful for autocomplete and UI rendering. - **Version tracking & caching** — a monotonic version counter enables efficient cache invalidation for derived data like `getKnownFields()`. diff --git a/packages/schema-analyzer/package.json b/packages/schema-analyzer/package.json index 78a54cc65..3751cdba2 100644 --- a/packages/schema-analyzer/package.json +++ b/packages/schema-analyzer/package.json @@ -1,7 +1,7 @@ { "name": "@vscode-documentdb/schema-analyzer", "version": "1.0.0", - "description": "Incremental JSON Schema analyzer for MongoDB/DocumentDB documents with statistical extensions", + "description": "Incremental JSON Schema analyzer for DocumentDB API / MongoDB API documents with statistical extensions", "main": "dist/index.js", "types": "dist/index.d.ts", "files": [ diff --git a/packages/schema-analyzer/src/BSONTypes.ts b/packages/schema-analyzer/src/BSONTypes.ts index 17c8ff45d..b8fb92f16 100644 --- a/packages/schema-analyzer/src/BSONTypes.ts +++ b/packages/schema-analyzer/src/BSONTypes.ts @@ -20,8 +20,8 @@ import { } from 'mongodb'; /** - * Represents the different data types that can be stored in a MongoDB document. - * The string representation is casesensitive and should match the MongoDB documentation. + * Represents the different data types that can be stored in a DocumentDB API / MongoDB API document. + * The string representation is case-sensitive and should match the MongoDB API documentation. * https://www.mongodb.com/docs/manual/reference/bson-types/ */ export enum BSONTypes { @@ -93,8 +93,8 @@ export namespace BSONTypes { } /** - * Converts a MongoDB data type to a case sensitive JSON data type - * @param type The MongoDB data type + * Converts a MongoDB API data type to a case-sensitive JSON data type + * @param type The MongoDB API data type * @returns A corresponding JSON data type (please note: it's case sensitive) */ export function toJSONType(type: BSONTypes): string { @@ -141,8 +141,8 @@ export namespace BSONTypes { } /** - * Accepts a value from a MongoDB 'Document' object and returns the inferred type. - * @param value The value of a field in a MongoDB 'Document' object + * Accepts a value from a MongoDB API `Document` object and returns the inferred type. + * @param value The value of a field in a MongoDB API `Document` object * @returns */ export function inferType(value: unknown): BSONTypes { diff --git a/packages/schema-analyzer/src/ValueFormatters.ts b/packages/schema-analyzer/src/ValueFormatters.ts index 8d770e318..7f9e8e5fa 100644 --- a/packages/schema-analyzer/src/ValueFormatters.ts +++ b/packages/schema-analyzer/src/ValueFormatters.ts @@ -7,13 +7,13 @@ import { type Binary, type BSONRegExp, type ObjectId } from 'mongodb'; import { BSONTypes } from './BSONTypes'; /** - * Converts a MongoDB value to its display string representation based on its type. + * Converts a MongoDB API value to its display string representation based on its type. * * @param value - The value to be converted to a display string. - * @param type - The MongoDB data type of the value. + * @param type - The MongoDB API data type of the value. * @returns The string representation of the value. * - * The function handles various MongoDB data types including: + * The function handles various MongoDB API data types including: * - String * - Number, Int32, Double, Decimal128, Long * - Boolean diff --git a/packages/schema-analyzer/src/getKnownFields.ts b/packages/schema-analyzer/src/getKnownFields.ts index 5c5f82bdc..f5da314b6 100644 --- a/packages/schema-analyzer/src/getKnownFields.ts +++ b/packages/schema-analyzer/src/getKnownFields.ts @@ -19,8 +19,8 @@ export interface FieldEntry { * True if this field was not present in every inspected document * (x-occurrence < parent x-documentsInspected). * - * This is a statistical observation, not a schema constraint — in the - * MongoDB API / DocumentDB API all fields are implicitly optional. + * This is a statistical observation, not a schema constraint — in the MongoDB API / DocumentDB API, + * all fields are implicitly optional. */ isSparse?: boolean; /** If the field is an array, the dominant element BSON type */ @@ -93,7 +93,7 @@ export function getKnownFields(schema: JSONSchema): FieldEntry[] { // itself contains a literal dot. For example, a root-level field named // "a.b" produces path "a.b", indistinguishable from a nested field // { a: { b: ... } }. Fields with literal dots in their names were - // prohibited before MongoDB 3.6 and remain rare in practice. + // prohibited before MongoDB API 3.6 and remain rare in practice. // // Future improvement: change `path` from `string` to `string[]` // (segment array) to preserve the distinction between nesting and diff --git a/src/utils/json/data-api/autocomplete/future-work.md b/src/utils/json/data-api/autocomplete/future-work.md index cac0d4ab0..660113c7d 100644 --- a/src/utils/json/data-api/autocomplete/future-work.md +++ b/src/utils/json/data-api/autocomplete/future-work.md @@ -55,7 +55,7 @@ referenceText: needsQuoting ## 3. `FieldEntry.path` dot-concatenation is ambiguous for literal dots -**Severity:** Low (rare in practice) — fields with literal dots were prohibited before MongoDB 3.6 +**Severity:** Low (rare in practice) — fields with literal dots were prohibited before MongoDB API 3.6 **File:** `getKnownFields.ts` — path concatenation at `path: \`${path}.${childName}\``**When to fix:** When we encounter real-world schemas with literal dots, or during the next`FieldEntry` interface revision ### Problem @@ -120,7 +120,7 @@ This is a **breaking change** to the `FieldEntry` interface. Affected consumers: ### Problem -The BSON-to-TypeScript type mapping emits non-built-in type names such as `ObjectId`, `Binary`, `Timestamp`, `MinKey`, `MaxKey`, `Code`, `DBRef`, and `UUID`. These are MongoDB BSON driver types, but the generated definition string doesn't include `import` statements or `declare` stubs for them. +The BSON-to-TypeScript type mapping emits non-built-in type names such as `ObjectId`, `Binary`, `Timestamp`, `MinKey`, `MaxKey`, `Code`, `DBRef`, and `UUID`. These are MongoDB API BSON driver types, but the generated definition string doesn't include `import` statements or `declare` stubs for them. If the output is ever fed to a TypeScript compiler or language service (e.g., Monaco with full TS checking), it will report "Cannot find name 'ObjectId'" etc. diff --git a/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.ts b/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.ts index 5f23cf652..17328dfeb 100644 --- a/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.ts +++ b/src/utils/json/data-api/autocomplete/toTypeScriptDefinition.ts @@ -100,7 +100,7 @@ function toInterfaceName(collectionName: string): string { * produced by the SchemaAnalyzer. * * @param schema - The JSON Schema with x- extensions from SchemaAnalyzer - * @param collectionName - The MongoDB collection name, used to derive the interface name + * @param collectionName - The MongoDB API collection name, used to derive the interface name * @returns A formatted TypeScript interface definition string */ export function toTypeScriptDefinition(schema: JSONSchema, collectionName: string): string {