From 4f604fdc581bd2d4ccd04204e2e67c4468dd1c70 Mon Sep 17 00:00:00 2001 From: Gregor Becker Date: Thu, 20 Nov 2025 14:41:13 +0100 Subject: [PATCH 1/2] fix(pinia-orm): Querying same Model without and with relations gives back always relations --- packages/pinia-orm/src/query/Query.ts | 51 +++++++++++-------- .../tests/feature/hooks/creating.spec.ts | 2 +- .../relations/constraints/constraints.spec.ts | 33 ++++++++++++ .../tests/unit/model/Model_Meta_Field.spec.ts | 2 +- .../tests/unit/repository/Repository.spec.ts | 2 +- 5 files changed, 66 insertions(+), 24 deletions(-) diff --git a/packages/pinia-orm/src/query/Query.ts b/packages/pinia-orm/src/query/Query.ts index 954e49ef8..33a36aff4 100644 --- a/packages/pinia-orm/src/query/Query.ts +++ b/packages/pinia-orm/src/query/Query.ts @@ -106,6 +106,8 @@ export class Query { protected getNewHydrated = false + protected hydrationKey?: string + /** * Hydrated models. They are stored to prevent rerendering of child components. */ @@ -114,13 +116,14 @@ export class Query { /** * Create a new query instance. */ - constructor (database: Database, model: M, cache: WeakCache | GroupedCollection> | undefined, hydratedData: Map, pinia?: Pinia) { + constructor (database: Database, model: M, cache: WeakCache | GroupedCollection> | undefined, hydratedData: Map, pinia?: Pinia, hydrationKey?: string) { this.database = database this.model = model this.pinia = pinia this.cache = cache this.hydratedDataCache = hydratedData this.getNewHydrated = false + this.hydrationKey = hydrationKey } /** @@ -128,14 +131,14 @@ export class Query { */ newQuery (model: string): Query { this.getNewHydrated = true - return new Query(this.database, this.database.getModel(model), this.cache, this.hydratedDataCache, this.pinia) + return new Query(this.database, this.database.getModel(model), this.cache, this.hydratedDataCache, this.pinia, this.hydrationKey) } /** * Create a new query instance with constraints for the given model. */ newQueryWithConstraints (model: string): Query { - const newQuery = new Query(this.database, this.database.getModel(model), this.cache, this.hydratedDataCache, this.pinia) + const newQuery = new Query(this.database, this.database.getModel(model), this.cache, this.hydratedDataCache, this.pinia, this.hydrationKey) // Copy query constraints newQuery.eagerLoad = { ...this.eagerLoad } @@ -153,7 +156,7 @@ export class Query { * Create a new query instance from the given relation. */ newQueryForRelation (relation: Relation): Query { - return new Query(this.database, relation.getRelated() as M, this.cache, new Map(), this.pinia) + return new Query(this.database, relation.getRelated() as M, this.cache, new Map(), this.pinia, this.hydrationKey) } /** @@ -482,20 +485,12 @@ export class Query { */ get(triggerHook?: boolean): T extends 'group' ? GroupedCollection : Collection get (triggerHook = true): Collection | GroupedCollection { + this.hydrationKey = this.hydrationKey ?? this.generateHydrationKey() if (!this.fromCache || !this.cache) { return this.internalGet(triggerHook) } const key = this.cacheConfig.key ? this.cacheConfig.key + JSON.stringify(this.cacheConfig.params) - : generateKey(this.model.$entity(), { - where: this.wheres, - groups: this.groups, - orders: this.orders, - eagerLoads: this.eagerLoad, - skip: this.skip, - take: this.take, - hidden: this.hidden, - visible: this.visible, - }) + : this.hydrationKey const result = this.cache.get(key) if (result) { return result } @@ -582,6 +577,19 @@ export class Query { return models.filter(model => comparator(model)) } + protected generateHydrationKey (): string { + return generateKey(this.model.$entity(), { + where: this.wheres, + groups: this.groups, + orders: this.orders, + eagerLoads: this.eagerLoad, + skip: this.skip, + take: this.take, + hidden: this.hidden, + visible: this.visible, + }) + } + /** * Get comparator for the where clause. */ @@ -1019,8 +1027,7 @@ export class Query { const isDeleting = currentModel.$self().deleting(currentModel) if (isDeleting === false) { notDeletableIds.push(currentModel.$getIndexId()) } else { - this.hydratedDataCache.delete('set' + this.model.$entity() + currentModel.$getIndexId()) - this.hydratedDataCache.delete('get' + this.model.$entity() + currentModel.$getIndexId()) + this.hydratedDataCache.delete(this.model.$entity() + currentModel.$getIndexId()) afterHooks.push(() => currentModel.$self().deleted(currentModel)) this.checkAndDeleteRelations(currentModel) } @@ -1066,11 +1073,11 @@ export class Query { */ protected getHydratedModel (record: Element, options?: ModelOptions): M { const id = this.model.$entity() + this.model.$getKey(record, true) - const operationId = options?.operation + id + const operationId = id let savedHydratedModel = this.hydratedDataCache.get(operationId) - if (options?.action === 'update') { - this.hydratedDataCache.delete('get' + id) + if (options?.action === 'update' || this.hydrationKey === undefined) { + this.hydratedDataCache.delete(id) savedHydratedModel = undefined } @@ -1081,10 +1088,12 @@ export class Query { const modelByType = this.model.$types()[record[this.model.$typeKey()]] const getNewInsance = (newOptions?: ModelOptions) => (modelByType ? modelByType.newRawInstance() as M : this.model) - .$newInstance(record, { relations: false, ...(options || {}), ...newOptions }) + .$newInstance(record, { ...(options || {}), ...newOptions, relations: false }) const hydratedModel = getNewInsance() - if (isEmpty(this.eagerLoad) && options?.operation !== 'set') { this.hydratedDataCache.set(operationId, hydratedModel) } + if (isEmpty(this.eagerLoad) && options?.operation !== 'set' && this.hydrationKey !== undefined) { + this.hydratedDataCache.set(operationId, hydratedModel) + } return hydratedModel } diff --git a/packages/pinia-orm/tests/feature/hooks/creating.spec.ts b/packages/pinia-orm/tests/feature/hooks/creating.spec.ts index 089c1d051..a67d28c8c 100644 --- a/packages/pinia-orm/tests/feature/hooks/creating.spec.ts +++ b/packages/pinia-orm/tests/feature/hooks/creating.spec.ts @@ -49,7 +49,7 @@ describe('feature/hooks/creating', () => { { id: 2, name: 'John Doe 2', age: 40 }, ]) - expect(useRepo(User).hydratedDataCache.size).toBe(2) + expect(useRepo(User).hydratedDataCache.size).toBe(0) expect(creatingMethod).toHaveBeenCalledTimes(2) expect(updatingMethod).toHaveBeenCalledTimes(0) expect(savingMethod).toHaveBeenCalledTimes(2) diff --git a/packages/pinia-orm/tests/feature/relations/constraints/constraints.spec.ts b/packages/pinia-orm/tests/feature/relations/constraints/constraints.spec.ts index 156abdfc0..023cb5cee 100644 --- a/packages/pinia-orm/tests/feature/relations/constraints/constraints.spec.ts +++ b/packages/pinia-orm/tests/feature/relations/constraints/constraints.spec.ts @@ -120,4 +120,37 @@ describe('feature/relations/constraints/constraints', () => { expect(users[1].phone!.type!.id).toBe(2) expect(users[2].phone!.type).toBe(null) }) + + it('loads with and without relations correctly', () => { + const usersRepo = useRepo(User) + const phonesRepo = useRepo(Phone) + const typesRepo = useRepo(Type) + usersRepo.cache()?.clear() + + usersRepo.save([ + { id: 1, name: 'John Doe', roles: [{ id: 1, pivot: { level: 1 }, phone: { id: 4, number: '999' } }, { id: 2 }, { id: 4 }] }, + { id: 2, name: 'John Doe', roles: [{ id: 1, pivot: { level: 2 } }] }, + { id: 3, name: 'Johnny Doe' }, + ]) + + phonesRepo.save([ + { id: 1, userId: 1, number: '123' }, + { id: 2, userId: 2, number: '345' }, + { id: 3, userId: 3, number: '789' }, + ]) + typesRepo.save([ + { id: 1, phoneId: 1, name: 'iPhone' }, + { id: 2, phoneId: 2, name: 'Android' }, + ]) + + const users2 = usersRepo.get() + const users = usersRepo + .with('roles', (query) => { + query.with('phone') + }) + .get() + + expect(users[0].roles.length).toBe(3) + expect(users2[0].roles).toBe(undefined) + }) }) diff --git a/packages/pinia-orm/tests/unit/model/Model_Meta_Field.spec.ts b/packages/pinia-orm/tests/unit/model/Model_Meta_Field.spec.ts index 9d9d9581c..9edb46bb6 100644 --- a/packages/pinia-orm/tests/unit/model/Model_Meta_Field.spec.ts +++ b/packages/pinia-orm/tests/unit/model/Model_Meta_Field.spec.ts @@ -44,7 +44,7 @@ describe('unit/model/Model_Meta_Field', () => { username: 'JD', }) - await new Promise(resolve => setTimeout(resolve, 1500)) + await new Promise(resolve => setTimeout(resolve, 2000)) userRepo.save({ id: 1, diff --git a/packages/pinia-orm/tests/unit/repository/Repository.spec.ts b/packages/pinia-orm/tests/unit/repository/Repository.spec.ts index 99a728219..0606f22ab 100644 --- a/packages/pinia-orm/tests/unit/repository/Repository.spec.ts +++ b/packages/pinia-orm/tests/unit/repository/Repository.spec.ts @@ -108,7 +108,7 @@ describe('unit/repository/Repository', () => { expect(userRepo.hydratedDataCache.size).toBe(0) userRepo.piniaStore().save({ 1: { id: 1, name: 'John' } }) - expect(userRepo.hydratedDataCache.size).toBe(1) + expect(userRepo.hydratedDataCache.size).toBe(0) userRepo.piniaStore().update({ 1: { id: 1, name: 'John 2' } }) expect(userRepo.hydratedDataCache.size).toBe(0) From 87a7dabfb4e9bade69e41d605518cefb3764dfe6 Mon Sep 17 00:00:00 2001 From: Gregor Becker Date: Thu, 20 Nov 2025 14:45:46 +0100 Subject: [PATCH 2/2] refactor(pinia-orm): revert small line --- packages/pinia-orm/src/query/Query.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pinia-orm/src/query/Query.ts b/packages/pinia-orm/src/query/Query.ts index 33a36aff4..1784529ea 100644 --- a/packages/pinia-orm/src/query/Query.ts +++ b/packages/pinia-orm/src/query/Query.ts @@ -1088,7 +1088,7 @@ export class Query { const modelByType = this.model.$types()[record[this.model.$typeKey()]] const getNewInsance = (newOptions?: ModelOptions) => (modelByType ? modelByType.newRawInstance() as M : this.model) - .$newInstance(record, { ...(options || {}), ...newOptions, relations: false }) + .$newInstance(record, { relations: false, ...(options || {}), ...newOptions }) const hydratedModel = getNewInsance() if (isEmpty(this.eagerLoad) && options?.operation !== 'set' && this.hydrationKey !== undefined) {