fix(classes.collection): Fix closure issue in managed decorator so Class.collection/instance.collection resolve correctly

This commit is contained in:
2025-11-28 10:55:29 +00:00
parent 3013edb2eb
commit 181e9da151
3 changed files with 44 additions and 1 deletions

View File

@@ -1,5 +1,12 @@
# Changelog
## 2025-11-28 - 7.0.8 - fix(classes.collection)
Fix closure issue in managed decorator so Class.collection/instance.collection resolve correctly
- Resolve closure bug in the managed() decorator where class methods referencing Class.collection (or instance.collection) could receive the original constructor's captured value and thus the wrong collection/manager.
- Define dynamic getters on the original constructor and its prototype that compute the collection from the proper manager/db at access time (supports direct manager objects, delayed manager factory functions, and fallback to defaultManager).
- Getters are defined as non-enumerable and configurable to preserve compatibility with existing consumers.
## 2025-11-28 - 7.0.7 - fix(decorators)
Fix decorator metadata initialization and Lucene query transformation

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@push.rocks/smartdata',
version: '7.0.7',
version: '7.0.8',
description: 'An advanced library for NoSQL data organization and manipulation using TypeScript with support for MongoDB, data validation, collections, and custom data types.'
}

View File

@@ -188,6 +188,42 @@ export function managed<TManager extends IManager>(managerArg?: TManager | TDela
}
};
// Closure fix: When class methods reference the class name (e.g., `User.collection`),
// they get the original constructor via closure, not the decorated class.
// Define collection/manager getters on the original constructor.
const getCollectionStatic = function(this: any) {
let dbArg: SmartdataDb;
if (!managerArg) {
dbArg = this.prototype.defaultManager.db;
} else if (managerArg['db']) {
dbArg = (managerArg as TManager).db;
} else {
dbArg = (managerArg as TDelayed<TManager>)().db;
}
return collectionFactory.getCollection(constructor.name, dbArg);
};
const getCollectionInstance = function(this: any) {
let dbArg: SmartdataDb;
if (!managerArg) {
dbArg = this.defaultManager.db;
} else if (managerArg['db']) {
dbArg = (managerArg as TManager).db;
} else {
dbArg = (managerArg as TDelayed<TManager>)().db;
}
return collectionFactory.getCollection(constructor.name, dbArg);
};
Object.defineProperty(constructor, 'collection', {
get: getCollectionStatic,
enumerable: false,
configurable: true
});
Object.defineProperty(constructor.prototype, 'collection', {
get: getCollectionInstance,
enumerable: false,
configurable: true
});
// Initialize prototype properties from context.metadata (TC39 decorator metadata)
// This ensures prototype properties are available before any instance is created
const originalConstructor = value as any;