From 967c4796ebff778c47debc7da52a53d1271bfd9f Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Fri, 20 Jun 2025 11:33:36 +0100 Subject: [PATCH 1/4] recreate the tsc crash we're seeing in mongosh --- packages/mql-typescript/tests/real.ts | 156 ++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 packages/mql-typescript/tests/real.ts diff --git a/packages/mql-typescript/tests/real.ts b/packages/mql-typescript/tests/real.ts new file mode 100644 index 00000000..af896b6e --- /dev/null +++ b/packages/mql-typescript/tests/real.ts @@ -0,0 +1,156 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import type * as schema from '../out/schema'; +import type { Document } from 'bson'; + +interface GenericCollectionSchema { + schema: Document; +} +interface GenericDatabaseSchema { + [key: string]: GenericCollectionSchema; +} +interface GenericServerSideSchema { + [key: string]: GenericDatabaseSchema; +} +type StringKey = keyof T & string; + +class Mongo {} + +type CollectionWithSchema< + M extends GenericServerSideSchema = GenericServerSideSchema, + D extends GenericDatabaseSchema = M[keyof M], + C extends GenericCollectionSchema = D[keyof D], + N extends StringKey = StringKey, +> = Collection & { + [k in StringKey as k extends `${N}.${infer S}` ? S : never]: Collection< + M, + D, + D[k], + k + >; +}; + +class Collection< + M extends GenericServerSideSchema = GenericServerSideSchema, + D extends GenericDatabaseSchema = M[keyof M], + C extends GenericCollectionSchema = D[keyof D], + N extends StringKey = StringKey, +> { + _mongo: Mongo; + _database: DatabaseWithSchema; + _name: N; + constructor( + mongo: Mongo, + database: DatabaseWithSchema | Database, + name: N, + ) { + this._mongo = mongo; + this._database = database as DatabaseWithSchema; + this._name = name; + } + getName(): N { + return this._name; + } + async find( + query?: schema.Query, + projection?: Document, + options: Document = {}, + ): Promise | undefined> { + //): Promise { + return Promise.resolve(query); + } +} + +type DatabaseWithSchema< + M extends GenericServerSideSchema = GenericServerSideSchema, + D extends GenericDatabaseSchema = GenericDatabaseSchema, +> = Database & { + [k in StringKey]: Collection; +}; + +function isValidCollectionName(name: string): boolean { + return !!name && !/[$\0]/.test(name); +} + +export class Database< + M extends GenericServerSideSchema = GenericServerSideSchema, + D extends GenericDatabaseSchema = GenericDatabaseSchema, +> { + _mongo: Mongo; + _name: StringKey; + _collections: Record, CollectionWithSchema>; + + constructor(mongo: Mongo, name: StringKey) { + this._mongo = mongo; + this._name = name; + const collections: Record< + string, + CollectionWithSchema + > = Object.create(null); + this._collections = collections; + + const proxy = new Proxy(this, { + get: (target, prop): any => { + if (prop in target) { + return (target as any)[prop]; + } + + if ( + typeof prop !== 'string' || + prop.startsWith('_') || + !isValidCollectionName(prop) + ) { + return; + } + + if (!collections[prop]) { + collections[prop] = new Collection( + mongo, + proxy, + prop, + ) as CollectionWithSchema; + } + + return collections[prop]; + }, + }); + return proxy; + } + + getCollection>( + coll: K, + ): CollectionWithSchema { + const collection = new Collection( + this._mongo, + this, + 'myCollection', + ); + + return collection as CollectionWithSchema; + } +} + +async function run() { + const serverSchema = { + myDatabase: { + myCollection: { + schema: { + _id: 'ObjectId', + name: 'string', + age: 'number', + }, + }, + }, + }; + const mongo = new Mongo(); + const db = new Database< + typeof serverSchema, + (typeof serverSchema)['myDatabase'] + >(mongo, 'myDatabase') as DatabaseWithSchema< + typeof serverSchema, + (typeof serverSchema)['myDatabase'] + >; + const query = await db.myCollection.find({ name: 'foo' }); + console.log(query); +} + +run().catch(console.error); From 82aa8bf487c95d973017791ea65bf59c51b7701e Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Fri, 20 Jun 2025 11:40:11 +0100 Subject: [PATCH 2/4] and with the actual crash --- packages/mql-typescript/tests/real.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/mql-typescript/tests/real.ts b/packages/mql-typescript/tests/real.ts index af896b6e..36d4e18e 100644 --- a/packages/mql-typescript/tests/real.ts +++ b/packages/mql-typescript/tests/real.ts @@ -51,11 +51,10 @@ class Collection< return this._name; } async find( - query?: schema.Query, + query?: schema.Query, projection?: Document, options: Document = {}, - ): Promise | undefined> { - //): Promise { + ): Promise | undefined> { return Promise.resolve(query); } } From 03eca415e622c64f4bb93fc21302e733ad192ac4 Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Mon, 23 Jun 2025 17:17:44 +0100 Subject: [PATCH 3/4] simpler reproduction --- packages/mql-typescript/tests/real.ts | 150 ++++++-------------------- 1 file changed, 32 insertions(+), 118 deletions(-) diff --git a/packages/mql-typescript/tests/real.ts b/packages/mql-typescript/tests/real.ts index 36d4e18e..a215e1b2 100644 --- a/packages/mql-typescript/tests/real.ts +++ b/packages/mql-typescript/tests/real.ts @@ -2,111 +2,38 @@ import type * as schema from '../out/schema'; import type { Document } from 'bson'; -interface GenericCollectionSchema { - schema: Document; -} +type StringKey = keyof T & string; interface GenericDatabaseSchema { [key: string]: GenericCollectionSchema; } -interface GenericServerSideSchema { - [key: string]: GenericDatabaseSchema; -} -type StringKey = keyof T & string; -class Mongo {} - -type CollectionWithSchema< - M extends GenericServerSideSchema = GenericServerSideSchema, - D extends GenericDatabaseSchema = M[keyof M], - C extends GenericCollectionSchema = D[keyof D], - N extends StringKey = StringKey, -> = Collection & { - [k in StringKey as k extends `${N}.${infer S}` ? S : never]: Collection< - M, - D, - D[k], - k - >; -}; - -class Collection< - M extends GenericServerSideSchema = GenericServerSideSchema, - D extends GenericDatabaseSchema = M[keyof M], - C extends GenericCollectionSchema = D[keyof D], - N extends StringKey = StringKey, -> { - _mongo: Mongo; - _database: DatabaseWithSchema; - _name: N; - constructor( - mongo: Mongo, - database: DatabaseWithSchema | Database, - name: N, - ) { - this._mongo = mongo; - this._database = database as DatabaseWithSchema; - this._name = name; - } - getName(): N { - return this._name; - } - async find( - query?: schema.Query, - projection?: Document, - options: Document = {}, - ): Promise | undefined> { - return Promise.resolve(query); - } -} - -type DatabaseWithSchema< - M extends GenericServerSideSchema = GenericServerSideSchema, - D extends GenericDatabaseSchema = GenericDatabaseSchema, -> = Database & { - [k in StringKey]: Collection; -}; - -function isValidCollectionName(name: string): boolean { - return !!name && !/[$\0]/.test(name); +interface GenericCollectionSchema { + schema: Document; } -export class Database< - M extends GenericServerSideSchema = GenericServerSideSchema, - D extends GenericDatabaseSchema = GenericDatabaseSchema, -> { - _mongo: Mongo; - _name: StringKey; - _collections: Record, CollectionWithSchema>; +class Database { + _collections: Record, CollectionWithSchema>; - constructor(mongo: Mongo, name: StringKey) { - this._mongo = mongo; - this._name = name; - const collections: Record< - string, - CollectionWithSchema - > = Object.create(null); + constructor() { + const collections: Record> = Object.create( + null, + ); this._collections = collections; - const proxy = new Proxy(this, { get: (target, prop): any => { if (prop in target) { return (target as any)[prop]; } - if ( - typeof prop !== 'string' || - prop.startsWith('_') || - !isValidCollectionName(prop) - ) { + if (typeof prop !== 'string' || prop.startsWith('_')) { return; } if (!collections[prop]) { - collections[prop] = new Collection( - mongo, - proxy, - prop, - ) as CollectionWithSchema; + collections[prop] = new Collection< + D, + D[typeof prop] + >() as CollectionWithSchema; } return collections[prop]; @@ -114,42 +41,29 @@ export class Database< }); return proxy; } +} - getCollection>( - coll: K, - ): CollectionWithSchema { - const collection = new Collection( - this._mongo, - this, - 'myCollection', - ); - - return collection as CollectionWithSchema; +type DatabaseWithSchema< + D extends GenericDatabaseSchema = GenericDatabaseSchema, +> = Database; +class Collection< + D extends GenericDatabaseSchema = GenericDatabaseSchema, + C extends GenericCollectionSchema = GenericCollectionSchema, +> { + find(query: schema.Query): Promise> { + return Promise.resolve(query); } } +type CollectionWithSchema< + D extends GenericDatabaseSchema = GenericDatabaseSchema, + C extends GenericCollectionSchema = D[keyof D], +> = Collection; async function run() { - const serverSchema = { - myDatabase: { - myCollection: { - schema: { - _id: 'ObjectId', - name: 'string', - age: 'number', - }, - }, - }, - }; - const mongo = new Mongo(); - const db = new Database< - typeof serverSchema, - (typeof serverSchema)['myDatabase'] - >(mongo, 'myDatabase') as DatabaseWithSchema< - typeof serverSchema, - (typeof serverSchema)['myDatabase'] - >; - const query = await db.myCollection.find({ name: 'foo' }); - console.log(query); + const database = new Database<{ + myCollection: { schema: { name: string } }; + }>(); + console.log(await database.myCollection.find({ name: 'foo' })); } run().catch(console.error); From 84255b09849b7a87e825830e7a5d49b16d59fef2 Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Mon, 23 Jun 2025 17:38:41 +0100 Subject: [PATCH 4/4] kinda working --- packages/mql-typescript/tests/real.js | 186 ++++++++++++++++++++++++++ packages/mql-typescript/tests/real.ts | 23 ++-- 2 files changed, 199 insertions(+), 10 deletions(-) create mode 100644 packages/mql-typescript/tests/real.js diff --git a/packages/mql-typescript/tests/real.js b/packages/mql-typescript/tests/real.js new file mode 100644 index 00000000..8401f894 --- /dev/null +++ b/packages/mql-typescript/tests/real.js @@ -0,0 +1,186 @@ +'use strict'; +var __awaiter = + (this && this.__awaiter) || + function (thisArg, _arguments, P, generator) { + function adopt(value) { + return value instanceof P + ? value + : new P(function (resolve) { + resolve(value); + }); + } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { + try { + step(generator.next(value)); + } catch (e) { + reject(e); + } + } + function rejected(value) { + try { + step(generator['throw'](value)); + } catch (e) { + reject(e); + } + } + function step(result) { + result.done + ? resolve(result.value) + : adopt(result.value).then(fulfilled, rejected); + } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); + }; +var __generator = + (this && this.__generator) || + function (thisArg, body) { + var _ = { + label: 0, + sent: function () { + if (t[0] & 1) throw t[1]; + return t[1]; + }, + trys: [], + ops: [], + }, + f, + y, + t, + g = Object.create( + (typeof Iterator === 'function' ? Iterator : Object).prototype, + ); + return ( + (g.next = verb(0)), + (g['throw'] = verb(1)), + (g['return'] = verb(2)), + typeof Symbol === 'function' && + (g[Symbol.iterator] = function () { + return this; + }), + g + ); + function verb(n) { + return function (v) { + return step([n, v]); + }; + } + function step(op) { + if (f) throw new TypeError('Generator is already executing.'); + while ((g && ((g = 0), op[0] && (_ = 0)), _)) + try { + if ( + ((f = 1), + y && + (t = + op[0] & 2 + ? y['return'] + : op[0] + ? y['throw'] || ((t = y['return']) && t.call(y), 0) + : y.next) && + !(t = t.call(y, op[1])).done) + ) + return t; + if (((y = 0), t)) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: + case 1: + t = op; + break; + case 4: + _.label++; + return { value: op[1], done: false }; + case 5: + _.label++; + y = op[1]; + op = [0]; + continue; + case 7: + op = _.ops.pop(); + _.trys.pop(); + continue; + default: + if ( + !((t = _.trys), (t = t.length > 0 && t[t.length - 1])) && + (op[0] === 6 || op[0] === 2) + ) { + _ = 0; + continue; + } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { + _.label = op[1]; + break; + } + if (op[0] === 6 && _.label < t[1]) { + _.label = t[1]; + t = op; + break; + } + if (t && _.label < t[2]) { + _.label = t[2]; + _.ops.push(op); + break; + } + if (t[2]) _.ops.pop(); + _.trys.pop(); + continue; + } + op = body.call(thisArg, _); + } catch (e) { + op = [6, e]; + y = 0; + } finally { + f = t = 0; + } + if (op[0] & 5) throw op[1]; + return { value: op[0] ? op[1] : void 0, done: true }; + } + }; +Object.defineProperty(exports, '__esModule', { value: true }); +var Database = /** @class */ (function () { + function Database() { + var collections = Object.create(null); + this._collections = collections; + var proxy = new Proxy(this, { + get: function (target, prop) { + if (prop in target) { + return target[prop]; + } + if (typeof prop !== 'string' || prop.startsWith('_')) { + return; + } + if (!collections[prop]) { + collections[prop] = new Collection(); + } + return collections[prop]; + }, + }); + return proxy; + } + return Database; +})(); +var Collection = /** @class */ (function () { + function Collection() {} + Collection.prototype.find = function (query) { + return Promise.resolve(query); + }; + return Collection; +})(); +function run() { + return __awaiter(this, void 0, void 0, function () { + var database, coll, _a, _b; + return __generator(this, function (_c) { + switch (_c.label) { + case 0: + database = new Database(); + coll = database.myCollection; + _b = (_a = console).log; + return [4 /*yield*/, database.myCollection.find({ name: 'foo' })]; + case 1: + _b.apply(_a, [_c.sent()]); + return [2 /*return*/]; + } + }); + }); +} +run().catch(console.error); diff --git a/packages/mql-typescript/tests/real.ts b/packages/mql-typescript/tests/real.ts index a215e1b2..9018bf1f 100644 --- a/packages/mql-typescript/tests/real.ts +++ b/packages/mql-typescript/tests/real.ts @@ -20,9 +20,9 @@ class Database { ); this._collections = collections; const proxy = new Proxy(this, { - get: (target, prop): any => { + get(target: any, prop: string): any { if (prop in target) { - return (target as any)[prop]; + return target[prop]; } if (typeof prop !== 'string' || prop.startsWith('_')) { @@ -30,10 +30,8 @@ class Database { } if (!collections[prop]) { - collections[prop] = new Collection< - D, - D[typeof prop] - >() as CollectionWithSchema; + collections[prop] = + new Collection() as unknown as CollectionWithSchema; } return collections[prop]; @@ -45,7 +43,9 @@ class Database { type DatabaseWithSchema< D extends GenericDatabaseSchema = GenericDatabaseSchema, -> = Database; +> = Database & { + [k in StringKey]: Collection; +}; class Collection< D extends GenericDatabaseSchema = GenericDatabaseSchema, C extends GenericCollectionSchema = GenericCollectionSchema, @@ -59,10 +59,13 @@ type CollectionWithSchema< C extends GenericCollectionSchema = D[keyof D], > = Collection; +type dbSchema = { + myCollection: { schema: { name: string } }; +}; + async function run() { - const database = new Database<{ - myCollection: { schema: { name: string } }; - }>(); + const database = new Database() as DatabaseWithSchema; + const coll = database.myCollection; console.log(await database.myCollection.find({ name: 'foo' })); }