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:
105
readme.md
105
readme.md
@@ -8,7 +8,7 @@
|
||||
|
||||
SmartData isn't just another MongoDB wrapper - it's a complete paradigm shift in how you work with databases:
|
||||
|
||||
- 🔒 **100% Type-Safe**: Full TypeScript with decorators, generics, and compile-time query validation
|
||||
- 🔒 **100% Type-Safe**: Full TypeScript with TC39 Stage 3 decorators, generics, and compile-time query validation
|
||||
- ⚡ **Lightning Fast**: Connection pooling, cursor streaming, and intelligent indexing
|
||||
- 🔄 **Real-time Ready**: MongoDB Change Streams with RxJS for reactive applications
|
||||
- 🌍 **Distributed Systems**: Built-in leader election and task coordination
|
||||
@@ -31,8 +31,11 @@ yarn add @push.rocks/smartdata
|
||||
## 🚦 Requirements
|
||||
|
||||
- **Node.js** >= 16.x
|
||||
- **Deno** >= 1.40 (for Deno projects)
|
||||
- **MongoDB** >= 4.4
|
||||
- **TypeScript** >= 4.x (for development)
|
||||
- **TypeScript** >= 5.2 (for TC39 decorator support)
|
||||
|
||||
> **Note**: SmartData v6.0+ uses TC39 Stage 3 decorators (the new standard). If you're migrating from v5.x, you'll need to remove `experimentalDecorators` from your tsconfig.json. Bun is not currently supported as it doesn't implement TC39 decorators yet.
|
||||
|
||||
## 🎯 Quick Start
|
||||
|
||||
@@ -47,7 +50,7 @@ const db = new SmartdataDb({
|
||||
mongoDbName: 'myapp',
|
||||
mongoDbUser: 'username',
|
||||
mongoDbPass: 'password',
|
||||
|
||||
|
||||
// Optional: Advanced connection pooling
|
||||
maxPoolSize: 100, // Max connections in pool
|
||||
maxIdleTimeMS: 300000, // Max idle time before connection close
|
||||
@@ -75,38 +78,38 @@ import { ObjectId } from 'mongodb';
|
||||
class User extends SmartDataDbDoc<User, User> {
|
||||
@unI()
|
||||
public id: string; // Unique index with automatic ID generation
|
||||
|
||||
|
||||
@svDb()
|
||||
@searchable() // Enable Lucene-style searching
|
||||
public username: string;
|
||||
|
||||
|
||||
@svDb()
|
||||
@searchable()
|
||||
@index({ unique: false }) // Performance index
|
||||
public email: string;
|
||||
|
||||
|
||||
@svDb()
|
||||
public status: 'active' | 'inactive' | 'pending'; // Full union type support
|
||||
|
||||
|
||||
@svDb()
|
||||
public organizationId: ObjectId; // Native MongoDB types
|
||||
|
||||
|
||||
@svDb()
|
||||
public profilePicture: Buffer; // Binary data support
|
||||
|
||||
|
||||
@svDb({
|
||||
// Custom serialization for complex objects
|
||||
serialize: (data) => JSON.stringify(data),
|
||||
deserialize: (data) => JSON.parse(data),
|
||||
})
|
||||
public preferences: Record<string, any>;
|
||||
|
||||
|
||||
@svDb()
|
||||
public tags: string[]; // Array support with operators
|
||||
|
||||
|
||||
@svDb()
|
||||
public createdAt: Date = new Date();
|
||||
|
||||
|
||||
constructor(username: string, email: string) {
|
||||
super();
|
||||
this.username = username;
|
||||
@@ -241,7 +244,7 @@ const experts = await User.getInstances({
|
||||
|
||||
// Array element matching
|
||||
const results = await Order.getInstances({
|
||||
items: {
|
||||
items: {
|
||||
$elemMatch: { // Match array elements
|
||||
product: 'laptop',
|
||||
quantity: { $gte: 2 }
|
||||
@@ -342,21 +345,21 @@ const johnUsers = await User.getInstances({
|
||||
const advancedQuery = await User.getInstances({
|
||||
// Direct field matching
|
||||
status: 'active',
|
||||
|
||||
|
||||
// Nested object with operators
|
||||
profile: {
|
||||
age: { $gte: 18, $lte: 65 },
|
||||
verified: true
|
||||
},
|
||||
|
||||
|
||||
// Dot notation for deep paths
|
||||
'settings.notifications.email': true,
|
||||
'metadata.lastLogin': { $gte: new Date(Date.now() - 30*24*60*60*1000) },
|
||||
|
||||
|
||||
// Array operations
|
||||
roles: { $in: ['admin', 'moderator'] },
|
||||
tags: { $all: ['verified', 'premium'] },
|
||||
|
||||
|
||||
// Logical grouping
|
||||
$or: [
|
||||
{ 'subscription.plan': 'premium' },
|
||||
@@ -452,7 +455,7 @@ React to database changes instantly with RxJS integration:
|
||||
// Watch for changes with automatic reconnection
|
||||
const watcher = await User.watch(
|
||||
{ status: 'active' }, // Filter which documents to watch
|
||||
{
|
||||
{
|
||||
fullDocument: 'updateLookup', // Get full document on updates
|
||||
bufferTimeMs: 100 // Buffer changes for efficiency
|
||||
}
|
||||
@@ -462,7 +465,7 @@ const watcher = await User.watch(
|
||||
watcher.changeSubject.subscribe({
|
||||
next: (change) => {
|
||||
console.log('User changed:', change.fullDocument);
|
||||
|
||||
|
||||
switch(change.operationType) {
|
||||
case 'insert':
|
||||
console.log('New user created');
|
||||
@@ -570,27 +573,27 @@ try {
|
||||
await session.withTransaction(async () => {
|
||||
// All operations in this block are atomic
|
||||
const sender = await User.getInstance(
|
||||
{ id: 'user-1' },
|
||||
{ id: 'user-1' },
|
||||
{ session } // Pass session to all operations
|
||||
);
|
||||
|
||||
|
||||
sender.balance -= 100;
|
||||
await sender.save({ session });
|
||||
|
||||
|
||||
const receiver = await User.getInstance(
|
||||
{ id: 'user-2' },
|
||||
{ id: 'user-2' },
|
||||
{ session }
|
||||
);
|
||||
|
||||
|
||||
receiver.balance += 100;
|
||||
await receiver.save({ session });
|
||||
|
||||
|
||||
// If anything fails, everything rolls back
|
||||
if (sender.balance < 0) {
|
||||
throw new Error('Insufficient funds!');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
console.log('✅ Transaction completed');
|
||||
} finally {
|
||||
await session.endSession();
|
||||
@@ -609,21 +612,21 @@ class Document extends SmartDataDbDoc<Document, Document> {
|
||||
deserialize: async (value) => await decrypt(value)
|
||||
})
|
||||
public sensitiveData: string;
|
||||
|
||||
|
||||
@svDb({
|
||||
// Compress large JSON objects
|
||||
serialize: (value) => compress(JSON.stringify(value)),
|
||||
deserialize: (value) => JSON.parse(decompress(value))
|
||||
})
|
||||
public largePayload: any;
|
||||
|
||||
|
||||
@svDb({
|
||||
// Store Sets as arrays
|
||||
serialize: (set) => Array.from(set),
|
||||
deserialize: (arr) => new Set(arr)
|
||||
})
|
||||
public tags: Set<string>;
|
||||
|
||||
|
||||
@svDb({
|
||||
// Handle custom date formats
|
||||
serialize: (date) => date?.toISOString(),
|
||||
@@ -644,45 +647,45 @@ class Order extends SmartDataDbDoc<Order, Order> {
|
||||
@svDb() public items: Array<{ product: string; quantity: number }>;
|
||||
@svDb() public totalAmount: number;
|
||||
@svDb() public status: string;
|
||||
|
||||
|
||||
// Called before saving (create or update)
|
||||
async beforeSave() {
|
||||
// Recalculate total
|
||||
this.totalAmount = this.items.reduce((sum, item) =>
|
||||
this.totalAmount = this.items.reduce((sum, item) =>
|
||||
sum + (item.price * item.quantity), 0
|
||||
);
|
||||
|
||||
|
||||
// Validate
|
||||
if (this.totalAmount < 0) {
|
||||
throw new Error('Invalid order total');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Called after successful save
|
||||
async afterSave() {
|
||||
// Send notifications
|
||||
await notificationService.orderUpdated(this.id);
|
||||
|
||||
|
||||
// Update cache
|
||||
await cache.set(`order:${this.id}`, this);
|
||||
}
|
||||
|
||||
|
||||
// Called before deletion
|
||||
async beforeDelete() {
|
||||
// Archive order
|
||||
await archive.store(this);
|
||||
|
||||
|
||||
// Check if deletion is allowed
|
||||
if (this.status === 'completed') {
|
||||
throw new Error('Cannot delete completed orders');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Called after successful deletion
|
||||
async afterDelete() {
|
||||
// Clean up related data
|
||||
await cache.delete(`order:${this.id}`);
|
||||
|
||||
|
||||
// Log deletion
|
||||
console.log(`Order ${this.id} deleted at ${new Date()}`);
|
||||
}
|
||||
@@ -698,27 +701,27 @@ class Order extends SmartDataDbDoc<Order, Order> {
|
||||
class HighPerformanceDoc extends SmartDataDbDoc<HighPerformanceDoc, HighPerformanceDoc> {
|
||||
@unI() // Unique index
|
||||
public id: string;
|
||||
|
||||
|
||||
@index() // Single field index
|
||||
public userId: string;
|
||||
|
||||
|
||||
@index({ sparse: true }) // Sparse index for optional fields
|
||||
public deletedAt?: Date;
|
||||
|
||||
@index({
|
||||
|
||||
@index({
|
||||
unique: false,
|
||||
background: true, // Non-blocking index creation
|
||||
expireAfterSeconds: 86400 // TTL index
|
||||
})
|
||||
public sessionToken: string;
|
||||
|
||||
|
||||
// Compound indexes for complex queries
|
||||
static async createIndexes() {
|
||||
await this.collection.createIndex(
|
||||
{ userId: 1, createdAt: -1 }, // Compound index
|
||||
{ name: 'user_activity_idx' }
|
||||
);
|
||||
|
||||
|
||||
// Text index for search
|
||||
await this.collection.createIndex(
|
||||
{ title: 'text', content: 'text' },
|
||||
@@ -734,17 +737,17 @@ class HighPerformanceDoc extends SmartDataDbDoc<HighPerformanceDoc, HighPerforma
|
||||
const db = new SmartdataDb({
|
||||
mongoDbUrl: 'mongodb://localhost:27017',
|
||||
mongoDbName: 'myapp',
|
||||
|
||||
|
||||
// Connection pool optimization
|
||||
maxPoolSize: 100, // Maximum connections
|
||||
minPoolSize: 10, // Minimum connections to maintain
|
||||
maxIdleTimeMS: 300000, // Close idle connections after 5 minutes
|
||||
waitQueueTimeoutMS: 5000, // Max time to wait for available connection
|
||||
|
||||
|
||||
// Server selection
|
||||
serverSelectionTimeoutMS: 30000, // Timeout for selecting a server
|
||||
heartbeatFrequencyMS: 10000, // How often to check server status
|
||||
|
||||
|
||||
// Socket settings
|
||||
socketTimeoutMS: 360000, // Socket timeout (6 minutes)
|
||||
family: 4, // Force IPv4
|
||||
@@ -874,7 +877,7 @@ await db.mongoDb.setProfilingLevel('all');
|
||||
const stats = await User.collection.mongoDbCollection
|
||||
.find({ complex: 'query' })
|
||||
.explain('executionStats');
|
||||
|
||||
|
||||
console.log('Query execution time:', stats.executionTimeMillis);
|
||||
console.log('Documents examined:', stats.totalDocsExamined);
|
||||
console.log('Index used:', stats.executionStats.executionStages.indexName);
|
||||
@@ -918,7 +921,7 @@ console.log('Index used:', stats.executionStats.executionStages.indexName);
|
||||
|
||||
## License and Legal Information
|
||||
|
||||
This 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.
|
||||
This 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.
|
||||
|
||||
**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.
|
||||
|
||||
@@ -928,9 +931,9 @@ This project is owned and maintained by Task Venture Capital GmbH. The names and
|
||||
|
||||
### Company Information
|
||||
|
||||
Task Venture Capital GmbH
|
||||
Task Venture Capital GmbH
|
||||
Registered at District court Bremen HRB 35230 HB, Germany
|
||||
|
||||
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
|
||||
|
||||
By 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.
|
||||
By 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.
|
||||
|
||||
Reference in New Issue
Block a user