Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 27c1500db5 | |||
| 3bbb78add8 | |||
| 9d779329e1 | |||
| cdc6b029af | |||
| 39c0ba7bea | |||
| e4faca88ba | |||
| 40bc408d8f | |||
| 3c8308561e |
30
changelog.md
30
changelog.md
@@ -1,5 +1,35 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-02-26 - 7.1.0 - feat(config)
|
||||||
|
normalize npmextra.json to namespaced keys and add CI/release configuration
|
||||||
|
|
||||||
|
- Replaced legacy keys (npmdocker, npmci, gitzone, tsdoc) with namespaced package keys (@git.zone/cli, @git.zone/tsdoc, @git.zone/tsdocker, @ship.zone/szci).
|
||||||
|
- Moved tsdoc legal text under @git.zone/tsdoc.
|
||||||
|
- Added release configuration with registries (https://verdaccio.lossless.digital and https://registry.npmjs.org) and accessLevel public under @git.zone/cli.
|
||||||
|
- Added @git.zone/tsdocker CI/docker settings and @ship.zone/szci npm registry/tooling settings.
|
||||||
|
- Removed old top-level entries to consolidate tooling configuration under scoped keys.
|
||||||
|
|
||||||
|
## 2026-02-26 - 7.0.16 - fix(mongodb)
|
||||||
|
set default socketTimeoutMS to 30000ms in MongoClient options to prevent hung operations from holding connections
|
||||||
|
|
||||||
|
- Adds socketTimeoutMS: 30000 to MongoClient clientOptions in ts/classes.db.ts
|
||||||
|
- Helps prevent hung operations from indefinitely holding connections by enforcing a 30s socket timeout
|
||||||
|
- Non-breaking change (defaults only)
|
||||||
|
|
||||||
|
## 2025-12-01 - 7.0.15 - fix(classes.doc)
|
||||||
|
Avoid emitting instance fields for collection and manager to preserve decorator-defined prototype getters
|
||||||
|
|
||||||
|
- ts/classes.doc.ts: changed instance properties `collection` and `manager` to `declare` so TypeScript does not emit them as own properties — prevents ES2022 class fields from shadowing prototype getters created by @Collection and @managed decorators.
|
||||||
|
- readme.hints.md: added documentation explaining the ES2022 class fields issue and recommending use of `declare` for type-only instance properties; marks the fix as v7.0.15.
|
||||||
|
|
||||||
|
## 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)
|
## 2025-11-28 - 7.0.13 - fix(classes.doc)
|
||||||
Remove noisy debug logging from decorators and serialization logic
|
Remove noisy debug logging from decorators and serialization logic
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,5 @@
|
|||||||
{
|
{
|
||||||
"npmdocker": {
|
"@git.zone/cli": {
|
||||||
"baseImage": "hosttoday/ht-docker-node:mongo",
|
|
||||||
"command": "npmci test stable",
|
|
||||||
"dockerSock": false
|
|
||||||
},
|
|
||||||
"npmci": {
|
|
||||||
"npmGlobalTools": [],
|
|
||||||
"npmAccessLevel": "public",
|
|
||||||
"npmRegistryUrl": "registry.npmjs.org"
|
|
||||||
},
|
|
||||||
"gitzone": {
|
|
||||||
"projectType": "npm",
|
"projectType": "npm",
|
||||||
"module": {
|
"module": {
|
||||||
"githost": "code.foss.global",
|
"githost": "code.foss.global",
|
||||||
@@ -28,9 +18,25 @@
|
|||||||
"custom data types",
|
"custom data types",
|
||||||
"ODM"
|
"ODM"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"release": {
|
||||||
|
"registries": [
|
||||||
|
"https://verdaccio.lossless.digital",
|
||||||
|
"https://registry.npmjs.org"
|
||||||
|
],
|
||||||
|
"accessLevel": "public"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tsdoc": {
|
"@git.zone/tsdoc": {
|
||||||
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
|
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
|
||||||
|
},
|
||||||
|
"@git.zone/tsdocker": {
|
||||||
|
"baseImage": "hosttoday/ht-docker-node:mongo",
|
||||||
|
"command": "npmci test stable",
|
||||||
|
"dockerSock": false
|
||||||
|
},
|
||||||
|
"@ship.zone/szci": {
|
||||||
|
"npmGlobalTools": [],
|
||||||
|
"npmRegistryUrl": "registry.npmjs.org"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@push.rocks/smartdata",
|
"name": "@push.rocks/smartdata",
|
||||||
"version": "7.0.13",
|
"version": "7.1.0",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "An advanced library for NoSQL data organization and manipulation using TypeScript with support for MongoDB, data validation, collections, and custom data types.",
|
"description": "An advanced library for NoSQL data organization and manipulation using TypeScript with support for MongoDB, data validation, collections, and custom data types.",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -78,3 +78,21 @@ All 157 tests passing across 10 test files:
|
|||||||
3. `Symbol.metadata` on constructors is read-only (managed by runtime)
|
3. `Symbol.metadata` on constructors is read-only (managed by runtime)
|
||||||
4. Field decorators run before class decorators (guaranteed order)
|
4. Field decorators run before class decorators (guaranteed order)
|
||||||
5. TypeScript 5.2+ has built-in TC39 decorator support
|
5. TypeScript 5.2+ has built-in TC39 decorator support
|
||||||
|
|
||||||
|
## ES2022 Class Fields & Prototype Getters - Fixed in v7.0.15
|
||||||
|
|
||||||
|
### Issue
|
||||||
|
ES2022 class fields (`useDefineForClassFields: true`) create own properties during construction that shadow prototype getters defined by decorators.
|
||||||
|
|
||||||
|
### Solution
|
||||||
|
Use `declare` keyword for instance properties that are accessed via prototype getters:
|
||||||
|
```typescript
|
||||||
|
// In SmartDataDbDoc (ts/classes.doc.ts):
|
||||||
|
declare public collection: SmartdataCollection<any>; // Type-only, no JS emitted
|
||||||
|
declare public manager: TManager; // Type-only, no JS emitted
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Insight
|
||||||
|
- `declare` tells TypeScript this is a type-only declaration
|
||||||
|
- No JavaScript code is emitted for `declare` properties
|
||||||
|
- Prototype getters defined by `@Collection` and `@managed` decorators are no longer shadowed
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartdata',
|
name: '@push.rocks/smartdata',
|
||||||
version: '7.0.13',
|
version: '7.1.0',
|
||||||
description: 'An advanced library for NoSQL data organization and manipulation using TypeScript with support for MongoDB, data validation, collections, and custom data types.'
|
description: 'An advanced library for NoSQL data organization and manipulation using TypeScript with support for MongoDB, data validation, collections, and custom data types.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,42 @@ export type TDelayed<TDelayedArg> = () => TDelayedArg;
|
|||||||
|
|
||||||
const collectionFactory = new CollectionFactory();
|
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
|
* This is a decorator that will tell the decorated class what dbTable to use
|
||||||
* @param dbArg
|
* @param dbArg
|
||||||
@@ -62,38 +98,7 @@ export function Collection(dbArg: SmartdataDb | TDelayed<SmartdataDb>) {
|
|||||||
configurable: true
|
configurable: true
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initialize prototype properties from context.metadata (TC39 decorator metadata)
|
initializeDecoratorMetadata(constructor, context.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)
|
|
||||||
return constructor as any;
|
return constructor as any;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -118,106 +123,44 @@ export function managed<TManager extends IManager>(managerArg?: TManager | TDela
|
|||||||
}
|
}
|
||||||
|
|
||||||
const constructor = value as { new (...args: any[]): any } & { className?: string };
|
const constructor = value as { new (...args: any[]): any } & { className?: string };
|
||||||
|
|
||||||
// Add static className property directly on the constructor
|
|
||||||
(constructor as any).className = constructor.name;
|
(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<TManager>)();
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDb = (defaultManagerFn: () => TManager): SmartdataDb => {
|
||||||
|
return getManager(defaultManagerFn).db;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Static getters
|
||||||
Object.defineProperty(constructor, 'collection', {
|
Object.defineProperty(constructor, 'collection', {
|
||||||
get: function(this: any) {
|
get(this: any) { return collectionFactory.getCollection(constructor.name, getDb(() => this.prototype.defaultManager)); },
|
||||||
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);
|
|
||||||
},
|
|
||||||
enumerable: false,
|
enumerable: false,
|
||||||
configurable: true
|
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<TManager>)().db;
|
|
||||||
}
|
|
||||||
return collectionFactory.getCollection(constructor.name, dbArg);
|
|
||||||
},
|
|
||||||
enumerable: false,
|
|
||||||
configurable: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// Define manager getter (static)
|
|
||||||
Object.defineProperty(constructor, 'manager', {
|
Object.defineProperty(constructor, 'manager', {
|
||||||
get: function(this: any) {
|
get(this: any) { return getManager(() => this.prototype.defaultManager); },
|
||||||
if (!managerArg) {
|
|
||||||
return this.prototype.defaultManager;
|
|
||||||
} else if (managerArg['db']) {
|
|
||||||
return managerArg as TManager;
|
|
||||||
} else {
|
|
||||||
return (managerArg as TDelayed<TManager>)();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
enumerable: false,
|
enumerable: false,
|
||||||
configurable: true
|
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', {
|
Object.defineProperty(constructor.prototype, 'manager', {
|
||||||
get: function(this: any) {
|
get(this: any) { return getManager(() => this.defaultManager); },
|
||||||
if (!managerArg) {
|
|
||||||
return this.defaultManager;
|
|
||||||
} else if (managerArg['db']) {
|
|
||||||
return managerArg as TManager;
|
|
||||||
} else {
|
|
||||||
return (managerArg as TDelayed<TManager>)();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
enumerable: false,
|
enumerable: false,
|
||||||
configurable: true
|
configurable: true
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initialize prototype properties from context.metadata (TC39 decorator metadata)
|
initializeDecoratorMetadata(constructor, context.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)
|
|
||||||
return constructor as any;
|
return constructor as any;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ export class SmartdataDb {
|
|||||||
maxPoolSize: (this.smartdataOptions as any).maxPoolSize ?? 100,
|
maxPoolSize: (this.smartdataOptions as any).maxPoolSize ?? 100,
|
||||||
maxIdleTimeMS: (this.smartdataOptions as any).maxIdleTimeMS ?? 300000, // 5 minutes default
|
maxIdleTimeMS: (this.smartdataOptions as any).maxIdleTimeMS ?? 300000, // 5 minutes default
|
||||||
serverSelectionTimeoutMS: (this.smartdataOptions as any).serverSelectionTimeoutMS ?? 30000,
|
serverSelectionTimeoutMS: (this.smartdataOptions as any).serverSelectionTimeoutMS ?? 30000,
|
||||||
|
socketTimeoutMS: (this.smartdataOptions as any).socketTimeoutMS ?? 30000, // 30 seconds default — prevents hung operations from holding connections
|
||||||
retryWrites: true,
|
retryWrites: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -436,10 +436,10 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|||||||
* the collection object an Doc belongs to
|
* the collection object an Doc belongs to
|
||||||
*/
|
*/
|
||||||
public static collection: SmartdataCollection<any>;
|
public static collection: SmartdataCollection<any>;
|
||||||
public collection: SmartdataCollection<any>;
|
declare public collection: SmartdataCollection<any>;
|
||||||
public static defaultManager;
|
public static defaultManager;
|
||||||
public static manager;
|
public static manager;
|
||||||
public manager: TManager;
|
declare public manager: TManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to get collection with fallback to static for Deno compatibility
|
* Helper to get collection with fallback to static for Deno compatibility
|
||||||
|
|||||||
Reference in New Issue
Block a user