diff --git a/packages/2-mongo-family/5-query-builders/orm/src/compile.ts b/packages/2-mongo-family/5-query-builders/orm/src/compile.ts index d6c0eb9979e0..c08f34548ef5 100644 --- a/packages/2-mongo-family/5-query-builders/orm/src/compile.ts +++ b/packages/2-mongo-family/5-query-builders/orm/src/compile.ts @@ -73,6 +73,9 @@ export function compileMongoQuery( for (const field of state.selectedFields) { projection[field] = 1; } + for (const inc of state.includes) { + projection[inc.relationName] = 1; + } if (!Object.hasOwn(projection, '_id')) { projection['_id'] = 0; } diff --git a/packages/2-mongo-family/5-query-builders/orm/test/compile.test.ts b/packages/2-mongo-family/5-query-builders/orm/test/compile.test.ts index 536e68773f8a..71c6b2848241 100644 --- a/packages/2-mongo-family/5-query-builders/orm/test/compile.test.ts +++ b/packages/2-mongo-family/5-query-builders/orm/test/compile.test.ts @@ -300,4 +300,51 @@ describe('compileMongoQuery', () => { expect(plan.resultShape.fields['author']?.kind).toBe('unknown'); }); }); + + describe('select combined with include', () => { + it('$project retains to-many relation alias from includes', () => { + const state: MongoCollectionState = { + ...emptyCollectionState(), + selectedFields: ['name'], + includes: [ + { + relationName: 'posts', + from: 'posts', + localField: '_id', + foreignField: 'authorId', + cardinality: '1:N', + }, + ], + }; + const plan = compileMongoQuery('users', state, testHash, testUserModel); + const projectStage = stages(plan).find((s) => s.kind === 'project') as + | MongoProjectStage + | undefined; + expect(projectStage).toBeDefined(); + expect(projectStage!.projection).toEqual({ name: 1, posts: 1, _id: 0 }); + }); + + it('$project retains to-one relation alias from includes', () => { + const state: MongoCollectionState = { + ...emptyCollectionState(), + selectedFields: ['title'], + includes: [ + { + relationName: 'author', + from: 'users', + localField: 'authorId', + foreignField: '_id', + cardinality: 'N:1', + }, + ], + }; + const plan = compileMongoQuery('posts', state, testHash, testPostModel); + expect(stages(plan).map((s) => s.kind)).toEqual(['lookup', 'unwind', 'project']); + const projectStage = stages(plan).find((s) => s.kind === 'project') as + | MongoProjectStage + | undefined; + expect(projectStage).toBeDefined(); + expect(projectStage!.projection).toEqual({ title: 1, author: 1, _id: 0 }); + }); + }); }); diff --git a/packages/2-mongo-family/5-query-builders/orm/test/integration/orm-ergonomics.test.ts b/packages/2-mongo-family/5-query-builders/orm/test/integration/orm-ergonomics.test.ts index f6e186ce1a35..01fe4a792dd7 100644 --- a/packages/2-mongo-family/5-query-builders/orm/test/integration/orm-ergonomics.test.ts +++ b/packages/2-mongo-family/5-query-builders/orm/test/integration/orm-ergonomics.test.ts @@ -266,5 +266,52 @@ describe('ORM ergonomics integration (FL-04, FL-06, FL-08)', { const tasks = (result as Record)['tasks'] as unknown[]; expect(tasks).toEqual([]); }); + + it('select() keeps a 1:N include whose join key is not selected', async () => { + const orm = mongoOrm({ contract, executor: runtime }); + const user = await orm.users.create(defaultUserData); + await orm.tasks.create({ + title: 'Task 1', + type: 'bug', + assigneeId: user._id as string, + } as never); + + const result = await orm.users + .select('name') + .include('tasks') + .where({ _id: user._id as string }) + .first(); + + expect(result).toEqual({ + name: 'Alice', + tasks: [ + { _id: expect.anything(), title: 'Task 1', type: 'bug', assigneeId: expect.anything() }, + ], + }); + }); + + it('select() keeps an N:1 include whose join key is not selected', async () => { + const orm = mongoOrm({ contract, executor: runtime }); + const user = await orm.users.create(defaultUserData); + await orm.tasks.create({ + title: 'Task 1', + type: 'bug', + assigneeId: user._id as string, + } as never); + + const result = await orm.tasks.select('title').include('assignee').first(); + + expect(result).toEqual({ + title: 'Task 1', + assignee: { + _id: expect.anything(), + name: 'Alice', + email: 'alice@test.com', + loginCount: 0, + tags: [], + homeAddress: null, + }, + }); + }); }); });