diff --git a/changelog.md b/changelog.md index 2f335dc..cfdbc87 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,13 @@ # Changelog +## 2025-11-28 - 7.0.14 - fix(classes.collection) +Centralize TC39 decorator metadata initialization and use context.metadata in class decorators + +- Add initializeDecoratorMetadata helper to initialize prototype and constructor properties from TC39 decorator metadata +- Refactor Collection and managed decorators to call initializeDecoratorMetadata with context.metadata +- Remove direct reliance on constructor[Symbol.metadata] in class decorators to avoid read-only assignment issues +- Ensure consistent initialization of saveableProperties, globalSaveableProperties, uniqueIndexes, regularIndexes, searchableFields and _svDbOptions + ## 2025-11-28 - 7.0.13 - fix(classes.doc) Remove noisy debug logging from decorators and serialization logic diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index d77260d..74d50cd 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@push.rocks/smartdata', - version: '7.0.13', + version: '7.0.14', description: 'An advanced library for NoSQL data organization and manipulation using TypeScript with support for MongoDB, data validation, collections, and custom data types.' } diff --git a/ts/classes.collection.ts b/ts/classes.collection.ts index edb20d5..02c7968 100644 --- a/ts/classes.collection.ts +++ b/ts/classes.collection.ts @@ -21,6 +21,42 @@ export type TDelayed = () => TDelayedArg; const collectionFactory = new CollectionFactory(); +/** + * Initialize prototype and constructor properties from TC39 decorator metadata. + * Shared by both Collection and managed decorators. + */ +function initializeDecoratorMetadata( + constructor: { new (...args: any[]): any; prototype: any }, + metadata: any +): void { + if (!metadata) return; + + const proto = constructor.prototype; + const ctor = constructor as any; + + // Prototype properties (instance-level) + if (metadata.globalSaveableProperties && !proto.globalSaveableProperties) { + proto.globalSaveableProperties = [...metadata.globalSaveableProperties]; + } + if (metadata.saveableProperties && !proto.saveableProperties) { + proto.saveableProperties = [...metadata.saveableProperties]; + } + if (metadata.uniqueIndexes && !proto.uniqueIndexes) { + proto.uniqueIndexes = [...metadata.uniqueIndexes]; + } + if (metadata.regularIndexes && !proto.regularIndexes) { + proto.regularIndexes = [...metadata.regularIndexes]; + } + + // Constructor properties (static-level) + if (metadata.searchableFields && !Array.isArray(ctor.searchableFields)) { + ctor.searchableFields = [...metadata.searchableFields]; + } + if (metadata._svDbOptions && !ctor._svDbOptions) { + ctor._svDbOptions = { ...metadata._svDbOptions }; + } +} + /** * This is a decorator that will tell the decorated class what dbTable to use * @param dbArg @@ -62,38 +98,7 @@ export function Collection(dbArg: SmartdataDb | TDelayed) { configurable: true }); - // Initialize prototype properties from context.metadata (TC39 decorator metadata) - // This ensures prototype properties are available before any instance is created - const metadata = context.metadata as any; - if (metadata) { - const proto = constructor.prototype; - - if (metadata.globalSaveableProperties && !proto.globalSaveableProperties) { - proto.globalSaveableProperties = [...metadata.globalSaveableProperties]; - } - - if (metadata.saveableProperties && !proto.saveableProperties) { - proto.saveableProperties = [...metadata.saveableProperties]; - } - - if (metadata.uniqueIndexes && !proto.uniqueIndexes) { - proto.uniqueIndexes = [...metadata.uniqueIndexes]; - } - - if (metadata.regularIndexes && !proto.regularIndexes) { - proto.regularIndexes = [...metadata.regularIndexes]; - } - - if (metadata.searchableFields && !Array.isArray((constructor as any).searchableFields)) { - (constructor as any).searchableFields = [...metadata.searchableFields]; - } - - if (metadata._svDbOptions && !(constructor as any)._svDbOptions) { - (constructor as any)._svDbOptions = { ...metadata._svDbOptions }; - } - } - - // Return the ORIGINAL constructor (no class replacement) + initializeDecoratorMetadata(constructor, context.metadata); return constructor as any; }; } @@ -118,106 +123,44 @@ export function managed(managerArg?: TManager | TDela } const constructor = value as { new (...args: any[]): any } & { className?: string }; - - // Add static className property directly on the constructor (constructor as any).className = constructor.name; - // Define collection getter (static) + // Resolution helpers (capture managerArg via closure) + const getManager = (defaultManagerFn: () => TManager): TManager => { + if (!managerArg) return defaultManagerFn(); + if (managerArg['db']) return managerArg as TManager; + return (managerArg as TDelayed)(); + }; + + const getDb = (defaultManagerFn: () => TManager): SmartdataDb => { + return getManager(defaultManagerFn).db; + }; + + // Static getters Object.defineProperty(constructor, 'collection', { - get: 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)().db; - } - return collectionFactory.getCollection(constructor.name, dbArg); - }, + get(this: any) { return collectionFactory.getCollection(constructor.name, getDb(() => this.prototype.defaultManager)); }, enumerable: false, configurable: true }); - - // Define collection getter (instance) - Object.defineProperty(constructor.prototype, 'collection', { - get: 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)().db; - } - return collectionFactory.getCollection(constructor.name, dbArg); - }, - enumerable: false, - configurable: true - }); - - // Define manager getter (static) Object.defineProperty(constructor, 'manager', { - get: function(this: any) { - if (!managerArg) { - return this.prototype.defaultManager; - } else if (managerArg['db']) { - return managerArg as TManager; - } else { - return (managerArg as TDelayed)(); - } - }, + get(this: any) { return getManager(() => this.prototype.defaultManager); }, enumerable: false, configurable: true }); - // Define manager getter (instance) + // Instance getters + Object.defineProperty(constructor.prototype, 'collection', { + get(this: any) { return collectionFactory.getCollection(constructor.name, getDb(() => this.defaultManager)); }, + enumerable: false, + configurable: true + }); Object.defineProperty(constructor.prototype, 'manager', { - get: function(this: any) { - if (!managerArg) { - return this.defaultManager; - } else if (managerArg['db']) { - return managerArg as TManager; - } else { - return (managerArg as TDelayed)(); - } - }, + get(this: any) { return getManager(() => this.defaultManager); }, 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 metadata = context.metadata as any; - if (metadata) { - const proto = constructor.prototype; - - if (metadata.globalSaveableProperties && !proto.globalSaveableProperties) { - proto.globalSaveableProperties = [...metadata.globalSaveableProperties]; - } - - if (metadata.saveableProperties && !proto.saveableProperties) { - proto.saveableProperties = [...metadata.saveableProperties]; - } - - if (metadata.uniqueIndexes && !proto.uniqueIndexes) { - proto.uniqueIndexes = [...metadata.uniqueIndexes]; - } - - if (metadata.regularIndexes && !proto.regularIndexes) { - proto.regularIndexes = [...metadata.regularIndexes]; - } - - if (metadata.searchableFields && !Array.isArray((constructor as any).searchableFields)) { - (constructor as any).searchableFields = [...metadata.searchableFields]; - } - - if (metadata._svDbOptions && !(constructor as any)._svDbOptions) { - (constructor as any)._svDbOptions = { ...metadata._svDbOptions }; - } - } - - // Return the ORIGINAL constructor (no class replacement) + initializeDecoratorMetadata(constructor, context.metadata); return constructor as any; }; }