BREAKING CHANGE(decorators): Migrate to TC39 Stage 3 decorators and refactor decorator metadata handling; update class initialization, lucene adapter fixes and docs
This commit is contained in:
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/smartdata',
|
||||
version: '5.16.7',
|
||||
version: '6.0.0',
|
||||
description: 'An advanced library for NoSQL data organization and manipulation using TypeScript with support for MongoDB, data validation, collections, and custom data types.'
|
||||
}
|
||||
|
||||
@@ -26,9 +26,14 @@ const collectionFactory = new CollectionFactory();
|
||||
* @param dbArg
|
||||
*/
|
||||
export function Collection(dbArg: SmartdataDb | TDelayed<SmartdataDb>) {
|
||||
return function classDecorator<T extends { new (...args: any[]): {} }>(constructor: T) {
|
||||
return function classDecorator(value: Function, context: ClassDecoratorContext) {
|
||||
if (context.kind !== 'class') {
|
||||
throw new Error('Collection can only decorate classes');
|
||||
}
|
||||
|
||||
// Capture original constructor for _svDbOptions forwarding
|
||||
const originalConstructor = constructor as any;
|
||||
const originalConstructor = value as any;
|
||||
const constructor = value as { new (...args: any[]): any };
|
||||
|
||||
const getCollection = () => {
|
||||
if (!(dbArg instanceof SmartdataDb)) {
|
||||
@@ -71,7 +76,44 @@ export function Collection(dbArg: SmartdataDb | TDelayed<SmartdataDb>) {
|
||||
configurable: true
|
||||
});
|
||||
|
||||
return decoratedClass;
|
||||
// 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 = decoratedClass.prototype;
|
||||
|
||||
// Initialize globalSaveableProperties
|
||||
if (metadata.globalSaveableProperties && !proto.globalSaveableProperties) {
|
||||
proto.globalSaveableProperties = [...metadata.globalSaveableProperties];
|
||||
}
|
||||
|
||||
// Initialize saveableProperties
|
||||
if (metadata.saveableProperties && !proto.saveableProperties) {
|
||||
proto.saveableProperties = [...metadata.saveableProperties];
|
||||
}
|
||||
|
||||
// Initialize uniqueIndexes
|
||||
if (metadata.uniqueIndexes && !proto.uniqueIndexes) {
|
||||
proto.uniqueIndexes = [...metadata.uniqueIndexes];
|
||||
}
|
||||
|
||||
// Initialize regularIndexes
|
||||
if (metadata.regularIndexes && !proto.regularIndexes) {
|
||||
proto.regularIndexes = [...metadata.regularIndexes];
|
||||
}
|
||||
|
||||
// Initialize searchableFields on constructor (not prototype)
|
||||
if (metadata.searchableFields && !Array.isArray((decoratedClass as any).searchableFields)) {
|
||||
(decoratedClass as any).searchableFields = [...metadata.searchableFields];
|
||||
}
|
||||
|
||||
// Initialize _svDbOptions from metadata
|
||||
if (metadata._svDbOptions && !originalConstructor._svDbOptions) {
|
||||
originalConstructor._svDbOptions = { ...metadata._svDbOptions };
|
||||
}
|
||||
}
|
||||
|
||||
return decoratedClass as any;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -89,7 +131,13 @@ export const setDefaultManagerForDoc = <T,>(managerArg: IManager, dbDocArg: T):
|
||||
* @param dbArg
|
||||
*/
|
||||
export function managed<TManager extends IManager>(managerArg?: TManager | TDelayed<TManager>) {
|
||||
return function classDecorator<T extends { new (...args: any[]): any }>(constructor: T) {
|
||||
return function classDecorator(value: Function, context: ClassDecoratorContext) {
|
||||
if (context.kind !== 'class') {
|
||||
throw new Error('managed can only decorate classes');
|
||||
}
|
||||
|
||||
const constructor = value as { new (...args: any[]): any };
|
||||
|
||||
const decoratedClass = class extends constructor {
|
||||
public static className = constructor.name;
|
||||
public static get collection() {
|
||||
@@ -139,7 +187,46 @@ export function managed<TManager extends IManager>(managerArg?: TManager | TDela
|
||||
return manager;
|
||||
}
|
||||
};
|
||||
return decoratedClass;
|
||||
|
||||
// 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;
|
||||
const metadata = context.metadata as any;
|
||||
if (metadata) {
|
||||
const proto = decoratedClass.prototype;
|
||||
|
||||
// Initialize globalSaveableProperties
|
||||
if (metadata.globalSaveableProperties && !proto.globalSaveableProperties) {
|
||||
proto.globalSaveableProperties = [...metadata.globalSaveableProperties];
|
||||
}
|
||||
|
||||
// Initialize saveableProperties
|
||||
if (metadata.saveableProperties && !proto.saveableProperties) {
|
||||
proto.saveableProperties = [...metadata.saveableProperties];
|
||||
}
|
||||
|
||||
// Initialize uniqueIndexes
|
||||
if (metadata.uniqueIndexes && !proto.uniqueIndexes) {
|
||||
proto.uniqueIndexes = [...metadata.uniqueIndexes];
|
||||
}
|
||||
|
||||
// Initialize regularIndexes
|
||||
if (metadata.regularIndexes && !proto.regularIndexes) {
|
||||
proto.regularIndexes = [...metadata.regularIndexes];
|
||||
}
|
||||
|
||||
// Initialize searchableFields on constructor (not prototype)
|
||||
if (metadata.searchableFields && !Array.isArray((decoratedClass as any).searchableFields)) {
|
||||
(decoratedClass as any).searchableFields = [...metadata.searchableFields];
|
||||
}
|
||||
|
||||
// Initialize _svDbOptions from metadata
|
||||
if (metadata._svDbOptions && !originalConstructor._svDbOptions) {
|
||||
originalConstructor._svDbOptions = { ...metadata._svDbOptions };
|
||||
}
|
||||
}
|
||||
|
||||
return decoratedClass as any;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -28,15 +28,42 @@ export interface SearchOptions<T> {
|
||||
|
||||
export type TDocCreation = 'db' | 'new' | 'mixed';
|
||||
|
||||
|
||||
// Type for decorator metadata - extends TypeScript's built-in DecoratorMetadataObject
|
||||
interface ISmartdataDecoratorMetadata extends DecoratorMetadataObject {
|
||||
globalSaveableProperties?: string[];
|
||||
saveableProperties?: string[];
|
||||
uniqueIndexes?: string[];
|
||||
regularIndexes?: Array<{field: string, options: IIndexOptions}>;
|
||||
searchableFields?: string[];
|
||||
_svDbOptions?: Record<string, SvDbOptions>;
|
||||
}
|
||||
|
||||
export function globalSvDb() {
|
||||
return (target: SmartDataDbDoc<unknown, unknown>, key: string) => {
|
||||
logger.log('debug', `called svDb() on >${target.constructor.name}.${key}<`);
|
||||
if (!target.globalSaveableProperties) {
|
||||
target.globalSaveableProperties = [];
|
||||
return (value: undefined, context: ClassFieldDecoratorContext) => {
|
||||
if (context.kind !== 'field') {
|
||||
throw new Error('globalSvDb can only decorate fields');
|
||||
}
|
||||
target.globalSaveableProperties.push(key);
|
||||
|
||||
// Store metadata at class level using Symbol.metadata
|
||||
const metadata = context.metadata as ISmartdataDecoratorMetadata;
|
||||
if (!metadata.globalSaveableProperties) {
|
||||
metadata.globalSaveableProperties = [];
|
||||
}
|
||||
metadata.globalSaveableProperties.push(String(context.name));
|
||||
|
||||
logger.log('debug', `called globalSvDb() on metadata for property ${String(context.name)}`);
|
||||
|
||||
// Use addInitializer to ensure prototype arrays are set up once
|
||||
context.addInitializer(function(this: any) {
|
||||
const proto = this.constructor.prototype;
|
||||
const metadata = this.constructor[Symbol.metadata];
|
||||
|
||||
if (metadata && metadata.globalSaveableProperties && !proto.globalSaveableProperties) {
|
||||
// Initialize prototype array from metadata (runs once per class)
|
||||
proto.globalSaveableProperties = [...metadata.globalSaveableProperties];
|
||||
logger.log('debug', `initialized globalSaveableProperties with ${proto.globalSaveableProperties.length} properties`);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -54,20 +81,47 @@ export interface SvDbOptions {
|
||||
* saveable - saveable decorator to be used on class properties
|
||||
*/
|
||||
export function svDb(options?: SvDbOptions) {
|
||||
return (target: SmartDataDbDoc<unknown, unknown>, key: string) => {
|
||||
logger.log('debug', `called svDb() on >${target.constructor.name}.${key}<`);
|
||||
if (!target.saveableProperties) {
|
||||
target.saveableProperties = [];
|
||||
return (value: undefined, context: ClassFieldDecoratorContext) => {
|
||||
if (context.kind !== 'field') {
|
||||
throw new Error('svDb can only decorate fields');
|
||||
}
|
||||
target.saveableProperties.push(key);
|
||||
// attach custom serializer/deserializer options to the class constructor
|
||||
const ctor = target.constructor as any;
|
||||
if (!ctor._svDbOptions) {
|
||||
ctor._svDbOptions = {};
|
||||
|
||||
const propName = String(context.name);
|
||||
|
||||
// Store metadata at class level using Symbol.metadata
|
||||
const metadata = context.metadata as ISmartdataDecoratorMetadata;
|
||||
if (!metadata.saveableProperties) {
|
||||
metadata.saveableProperties = [];
|
||||
}
|
||||
metadata.saveableProperties.push(propName);
|
||||
|
||||
// Store options in metadata
|
||||
if (options) {
|
||||
ctor._svDbOptions[key] = options;
|
||||
if (!metadata._svDbOptions) {
|
||||
metadata._svDbOptions = {};
|
||||
}
|
||||
metadata._svDbOptions[propName] = options;
|
||||
}
|
||||
|
||||
logger.log('debug', `called svDb() on metadata for property ${propName}`);
|
||||
|
||||
// Use addInitializer to ensure prototype arrays are set up once
|
||||
context.addInitializer(function(this: any) {
|
||||
const proto = this.constructor.prototype;
|
||||
const ctor = this.constructor;
|
||||
const metadata = ctor[Symbol.metadata];
|
||||
|
||||
if (metadata && metadata.saveableProperties && !proto.saveableProperties) {
|
||||
// Initialize prototype array from metadata (runs once per class)
|
||||
proto.saveableProperties = [...metadata.saveableProperties];
|
||||
logger.log('debug', `initialized saveableProperties with ${proto.saveableProperties.length} properties`);
|
||||
}
|
||||
|
||||
// Initialize svDbOptions from metadata
|
||||
if (metadata && metadata._svDbOptions && !ctor._svDbOptions) {
|
||||
ctor._svDbOptions = { ...metadata._svDbOptions };
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -75,13 +129,30 @@ export function svDb(options?: SvDbOptions) {
|
||||
* searchable - marks a property as searchable with Lucene query syntax
|
||||
*/
|
||||
export function searchable() {
|
||||
return (target: SmartDataDbDoc<unknown, unknown>, key: string) => {
|
||||
// Attach to class constructor for direct access
|
||||
const ctor = target.constructor as any;
|
||||
if (!Array.isArray(ctor.searchableFields)) {
|
||||
ctor.searchableFields = [];
|
||||
return (value: undefined, context: ClassFieldDecoratorContext) => {
|
||||
if (context.kind !== 'field') {
|
||||
throw new Error('searchable can only decorate fields');
|
||||
}
|
||||
ctor.searchableFields.push(key);
|
||||
|
||||
const propName = String(context.name);
|
||||
|
||||
// Store metadata at class level
|
||||
const metadata = context.metadata as ISmartdataDecoratorMetadata;
|
||||
if (!metadata.searchableFields) {
|
||||
metadata.searchableFields = [];
|
||||
}
|
||||
metadata.searchableFields.push(propName);
|
||||
|
||||
// Use addInitializer to set up constructor property once
|
||||
context.addInitializer(function(this: any) {
|
||||
const ctor = this.constructor as any;
|
||||
const metadata = ctor[Symbol.metadata];
|
||||
|
||||
if (metadata && metadata.searchableFields && !Array.isArray(ctor.searchableFields)) {
|
||||
// Initialize from metadata (runs once per class)
|
||||
ctor.searchableFields = [...metadata.searchableFields];
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -94,20 +165,44 @@ function escapeForRegex(input: string): string {
|
||||
* unique index - decorator to mark a unique index
|
||||
*/
|
||||
export function unI() {
|
||||
return (target: SmartDataDbDoc<unknown, unknown>, key: string) => {
|
||||
logger.log('debug', `called unI on >>${target.constructor.name}.${key}<<`);
|
||||
|
||||
// mark the index as unique
|
||||
if (!target.uniqueIndexes) {
|
||||
target.uniqueIndexes = [];
|
||||
return (value: undefined, context: ClassFieldDecoratorContext) => {
|
||||
if (context.kind !== 'field') {
|
||||
throw new Error('unI can only decorate fields');
|
||||
}
|
||||
target.uniqueIndexes.push(key);
|
||||
|
||||
// and also save it
|
||||
if (!target.saveableProperties) {
|
||||
target.saveableProperties = [];
|
||||
const propName = String(context.name);
|
||||
|
||||
// Store metadata at class level
|
||||
const metadata = context.metadata as ISmartdataDecoratorMetadata;
|
||||
if (!metadata.uniqueIndexes) {
|
||||
metadata.uniqueIndexes = [];
|
||||
}
|
||||
target.saveableProperties.push(key);
|
||||
metadata.uniqueIndexes.push(propName);
|
||||
|
||||
// Also mark as saveable
|
||||
if (!metadata.saveableProperties) {
|
||||
metadata.saveableProperties = [];
|
||||
}
|
||||
if (!metadata.saveableProperties.includes(propName)) {
|
||||
metadata.saveableProperties.push(propName);
|
||||
}
|
||||
|
||||
logger.log('debug', `called unI on metadata for property ${propName}`);
|
||||
|
||||
// Use addInitializer to ensure prototype arrays are set up once
|
||||
context.addInitializer(function(this: any) {
|
||||
const proto = this.constructor.prototype;
|
||||
const metadata = this.constructor[Symbol.metadata];
|
||||
|
||||
if (metadata && metadata.uniqueIndexes && !proto.uniqueIndexes) {
|
||||
proto.uniqueIndexes = [...metadata.uniqueIndexes];
|
||||
logger.log('debug', `initialized uniqueIndexes with ${proto.uniqueIndexes.length} properties`);
|
||||
}
|
||||
|
||||
if (metadata && metadata.saveableProperties && !proto.saveableProperties) {
|
||||
proto.saveableProperties = [...metadata.saveableProperties];
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -126,28 +221,47 @@ export interface IIndexOptions {
|
||||
* index - decorator to mark a field for regular indexing
|
||||
*/
|
||||
export function index(options?: IIndexOptions) {
|
||||
return (target: SmartDataDbDoc<unknown, unknown>, key: string) => {
|
||||
logger.log('debug', `called index() on >${target.constructor.name}.${key}<`);
|
||||
|
||||
// Initialize regular indexes array if it doesn't exist
|
||||
if (!target.regularIndexes) {
|
||||
target.regularIndexes = [];
|
||||
return (value: undefined, context: ClassFieldDecoratorContext) => {
|
||||
if (context.kind !== 'field') {
|
||||
throw new Error('index can only decorate fields');
|
||||
}
|
||||
|
||||
// Add this field to regularIndexes with its options
|
||||
target.regularIndexes.push({
|
||||
field: key,
|
||||
|
||||
const propName = String(context.name);
|
||||
|
||||
// Store metadata at class level
|
||||
const metadata = context.metadata as ISmartdataDecoratorMetadata;
|
||||
if (!metadata.regularIndexes) {
|
||||
metadata.regularIndexes = [];
|
||||
}
|
||||
metadata.regularIndexes.push({
|
||||
field: propName,
|
||||
options: options || {}
|
||||
});
|
||||
|
||||
// Also ensure it's marked as saveable
|
||||
if (!target.saveableProperties) {
|
||||
target.saveableProperties = [];
|
||||
|
||||
// Also mark as saveable
|
||||
if (!metadata.saveableProperties) {
|
||||
metadata.saveableProperties = [];
|
||||
}
|
||||
|
||||
if (!target.saveableProperties.includes(key)) {
|
||||
target.saveableProperties.push(key);
|
||||
if (!metadata.saveableProperties.includes(propName)) {
|
||||
metadata.saveableProperties.push(propName);
|
||||
}
|
||||
|
||||
logger.log('debug', `called index() on metadata for property ${propName}`);
|
||||
|
||||
// Use addInitializer to ensure prototype arrays are set up once
|
||||
context.addInitializer(function(this: any) {
|
||||
const proto = this.constructor.prototype;
|
||||
const metadata = this.constructor[Symbol.metadata];
|
||||
|
||||
if (metadata && metadata.regularIndexes && !proto.regularIndexes) {
|
||||
proto.regularIndexes = [...metadata.regularIndexes];
|
||||
logger.log('debug', `initialized regularIndexes with ${proto.regularIndexes.length} indexes`);
|
||||
}
|
||||
|
||||
if (metadata && metadata.saveableProperties && !proto.saveableProperties) {
|
||||
proto.saveableProperties = [...metadata.saveableProperties];
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user