fix(build): update build and test tooling configuration, migrate project config to .smartconfig.json, and align TypeScript typings

This commit is contained in:
2026-03-24 19:44:49 +00:00
parent 27c1500db5
commit 03431535d7
29 changed files with 2210 additions and 1669 deletions

216
readme.md
View File

@@ -52,9 +52,9 @@ const db = new SmartdataDb({
mongoDbPass: 'password',
// Optional: Advanced connection pooling
maxPoolSize: 100, // Max connections in pool
maxIdleTimeMS: 300000, // Max idle time before connection close
serverSelectionTimeoutMS: 30000 // Connection timeout
maxPoolSize: 100, // Max connections in pool
maxIdleTimeMS: 300000, // Max idle time before connection close
serverSelectionTimeoutMS: 30000, // Connection timeout
});
// Initialize with automatic retry and health monitoring
@@ -77,25 +77,25 @@ import { ObjectId } from 'mongodb';
@Collection(() => db)
class User extends SmartDataDbDoc<User, User> {
@unI()
public id: string; // Unique index with automatic ID generation
public id: string; // Unique index with automatic ID generation
@svDb()
@searchable() // Enable Lucene-style searching
@searchable() // Enable Lucene-style searching
public username: string;
@svDb()
@searchable()
@index({ unique: false }) // Performance index
@index({ unique: false }) // Performance index
public email: string;
@svDb()
public status: 'active' | 'inactive' | 'pending'; // Full union type support
public status: 'active' | 'inactive' | 'pending'; // Full union type support
@svDb()
public organizationId: ObjectId; // Native MongoDB types
public organizationId: ObjectId; // Native MongoDB types
@svDb()
public profilePicture: Buffer; // Binary data support
public profilePicture: Buffer; // Binary data support
@svDb({
// Custom serialization for complex objects
@@ -105,7 +105,7 @@ class User extends SmartDataDbDoc<User, User> {
public preferences: Record<string, any>;
@svDb()
public tags: string[]; // Array support with operators
public tags: string[]; // Array support with operators
@svDb()
public createdAt: Date = new Date();
@@ -156,12 +156,12 @@ const john = await User.getInstances({ name: 'John Doe' });
// Multiple fields (implicit AND)
const activeAdults = await User.getInstances({
status: 'active',
age: { $gte: 18 }
age: { $gte: 18 },
});
// Union types work perfectly
const users = await User.getInstances({
status: { $in: ['active', 'pending'] } // TypeScript validates these values!
status: { $in: ['active', 'pending'] }, // TypeScript validates these values!
});
```
@@ -173,19 +173,19 @@ SmartData supports **both** nested object notation and dot notation for querying
// Nested object notation - natural TypeScript syntax
const users = await User.getInstances({
metadata: {
loginCount: { $gte: 5 }
}
loginCount: { $gte: 5 },
},
});
// Dot notation - MongoDB style
const sameUsers = await User.getInstances({
'metadata.loginCount': { $gte: 5 }
'metadata.loginCount': { $gte: 5 },
});
// POWERFUL: Combine both notations - operators are merged!
const filtered = await User.getInstances({
metadata: { loginCount: { $gte: 3 } }, // Object notation
'metadata.loginCount': { $lte: 10 } // Dot notation
metadata: { loginCount: { $gte: 3 } }, // Object notation
'metadata.loginCount': { $lte: 10 }, // Dot notation
// Result: metadata.loginCount between 3 and 10
});
@@ -195,10 +195,10 @@ const deepQuery = await User.getInstances({
settings: {
notifications: {
email: true,
frequency: { $in: ['daily', 'weekly'] }
}
}
}
frequency: { $in: ['daily', 'weekly'] },
},
},
},
});
// Mix styles for complex queries
@@ -206,11 +206,11 @@ const advanced = await User.getInstances({
// Object notation for structure
profile: {
age: { $gte: 21 },
verified: true
verified: true,
},
// Dot notation for specific overrides
'profile.settings.theme': 'dark',
'profile.lastSeen': { $gte: new Date('2024-01-01') }
'profile.lastSeen': { $gte: new Date('2024-01-01') },
});
```
@@ -219,17 +219,17 @@ const advanced = await User.getInstances({
```typescript
// Numeric comparisons with type checking
const adults = await User.getInstances({
age: { $gte: 18, $lt: 65 } // Type-safe numeric comparisons
age: { $gte: 18, $lt: 65 }, // Type-safe numeric comparisons
});
// Date comparisons
const recentUsers = await User.getInstances({
createdAt: { $gte: new Date('2024-01-01') }
createdAt: { $gte: new Date('2024-01-01') },
});
// Not equal
const nonAdmins = await User.getInstances({
role: { $ne: 'admin' }
role: { $ne: 'admin' },
});
```
@@ -238,23 +238,24 @@ const nonAdmins = await User.getInstances({
```typescript
// Array operations with full type safety
const experts = await User.getInstances({
tags: { $all: ['typescript', 'mongodb'] }, // Must have all tags
skills: { $size: 5 } // Exactly 5 skills
tags: { $all: ['typescript', 'mongodb'] }, // Must have all tags
skills: { $size: 5 }, // Exactly 5 skills
});
// Array element matching
const results = await Order.getInstances({
items: {
$elemMatch: { // Match array elements
$elemMatch: {
// Match array elements
product: 'laptop',
quantity: { $gte: 2 }
}
}
quantity: { $gte: 2 },
},
},
});
// Check if value exists in array field
const nodeUsers = await User.getInstances({
skills: { $in: ['nodejs'] } // Has nodejs in skills array
skills: { $in: ['nodejs'] }, // Has nodejs in skills array
});
```
@@ -266,24 +267,18 @@ const results = await Order.getInstances({
$and: [
{ status: { $in: ['pending', 'processing'] } },
{ 'items.price': { $gte: 100 } },
{ customer: { verified: true } }
]
{ customer: { verified: true } },
],
});
// $or operator
const urgentOrHighValue = await Order.getInstances({
$or: [
{ priority: 'urgent' },
{ totalAmount: { $gte: 1000 } }
]
$or: [{ priority: 'urgent' }, { totalAmount: { $gte: 1000 } }],
});
// $nor operator - none of the conditions
const excluded = await User.getInstances({
$nor: [
{ status: 'banned' },
{ role: 'guest' }
]
$nor: [{ status: 'banned' }, { role: 'guest' }],
});
// Combine logical operators
@@ -291,12 +286,9 @@ const complex = await Order.getInstances({
$and: [
{ status: 'active' },
{
$or: [
{ priority: 'high' },
{ value: { $gte: 1000 } }
]
}
]
$or: [{ priority: 'high' }, { value: { $gte: 1000 } }],
},
],
});
```
@@ -305,12 +297,12 @@ const complex = await Order.getInstances({
```typescript
// Check field existence
const withEmail = await User.getInstances({
email: { $exists: true }
email: { $exists: true },
});
// Check for null or missing nested fields
const noPreferences = await User.getInstances({
'profile.preferences': { $exists: false }
'profile.preferences': { $exists: false },
});
```
@@ -319,12 +311,12 @@ const noPreferences = await User.getInstances({
```typescript
// Regex patterns
const gmailUsers = await User.getInstances({
email: { $regex: '@gmail\\.com$', $options: 'i' }
email: { $regex: '@gmail\\.com$', $options: 'i' },
});
// Starts with pattern
const johnUsers = await User.getInstances({
name: { $regex: '^John' }
name: { $regex: '^John' },
});
```
@@ -349,22 +341,21 @@ const advancedQuery = await User.getInstances({
// Nested object with operators
profile: {
age: { $gte: 18, $lte: 65 },
verified: true
verified: true,
},
// Dot notation for deep paths
'settings.notifications.email': true,
'metadata.lastLogin': { $gte: new Date(Date.now() - 30*24*60*60*1000) },
'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' },
{ 'subscription.trial': true }
]
$or: [{ 'subscription.plan': 'premium' }, { 'subscription.trial': true }],
});
```
@@ -400,7 +391,7 @@ const exact = await Product.search('"MacBook Pro 16"');
// Combined with filters for powerful queries
const affordable = await Product.search('laptop', {
filter: { price: { $lte: 1500 } },
validate: async (p) => p.inStock === true
validate: async (p) => p.inStock === true,
});
```
@@ -427,7 +418,7 @@ const config = await db.createEasyStore<AppConfig>('app-config');
// Write with full type checking
await config.writeKey('features', {
darkMode: true,
notifications: false
notifications: false,
});
// Read with guaranteed types
@@ -437,7 +428,7 @@ const features = await config.readKey('features');
// Atomic updates
await config.updateKey('limits', (current) => ({
...current,
maxUsers: current.maxUsers + 100
maxUsers: current.maxUsers + 100,
}));
// Delete keys
@@ -454,11 +445,11 @@ React to database changes instantly with RxJS integration:
```typescript
// Watch for changes with automatic reconnection
const watcher = await User.watch(
{ status: 'active' }, // Filter which documents to watch
{ status: 'active' }, // Filter which documents to watch
{
fullDocument: 'updateLookup', // Get full document on updates
bufferTimeMs: 100 // Buffer changes for efficiency
}
fullDocument: 'updateLookup', // Get full document on updates
bufferTimeMs: 100, // Buffer changes for efficiency
},
);
// Subscribe to changes with RxJS
@@ -466,7 +457,7 @@ watcher.changeSubject.subscribe({
next: (change) => {
console.log('User changed:', change.fullDocument);
switch(change.operationType) {
switch (change.operationType) {
case 'insert':
console.log('New user created');
break;
@@ -478,7 +469,7 @@ watcher.changeSubject.subscribe({
break;
}
},
error: (err) => console.error('Watch error:', err)
error: (err) => console.error('Watch error:', err),
});
// Advanced: Watch with aggregation pipeline
@@ -487,9 +478,9 @@ const complexWatcher = await Order.watch(
{
pipeline: [
{ $match: { 'fullDocument.totalAmount': { $gte: 1000 } } },
{ $addFields: { isHighValue: true } }
]
}
{ $addFields: { isHighValue: true } },
],
},
);
// Clean up when done
@@ -522,7 +513,7 @@ const isLeader = coordinator.isLeader;
const result = await coordinator.fireDistributedTaskRequest({
taskName: 'process-payments',
taskExecutionTime: Date.now(),
requestResponseId: 'unique-id'
requestResponseId: 'unique-id',
});
// Graceful shutdown with leadership handoff
@@ -539,11 +530,12 @@ const cursor = await User.getCursor(
{ status: 'active' },
{
// Optional: Use MongoDB native cursor modifiers
modifier: (cursor) => cursor
.sort({ createdAt: -1 })
.limit(10000)
.project({ email: 1, username: 1 })
}
modifier: (cursor) =>
cursor
.sort({ createdAt: -1 })
.limit(10000)
.project({ email: 1, username: 1 }),
},
);
// Process one at a time
@@ -574,16 +566,13 @@ try {
// All operations in this block are atomic
const sender = await User.getInstance(
{ id: 'user-1' },
{ session } // Pass session to all operations
{ session }, // Pass session to all operations
);
sender.balance -= 100;
await sender.save({ session });
const receiver = await User.getInstance(
{ id: 'user-2' },
{ session }
);
const receiver = await User.getInstance({ id: 'user-2' }, { session });
receiver.balance += 100;
await receiver.save({ session });
@@ -609,28 +598,28 @@ class Document extends SmartDataDbDoc<Document, Document> {
@svDb({
// Encrypt sensitive data before storing
serialize: async (value) => await encrypt(value),
deserialize: async (value) => await decrypt(value)
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))
deserialize: (value) => JSON.parse(decompress(value)),
})
public largePayload: any;
@svDb({
// Store Sets as arrays
serialize: (set) => Array.from(set),
deserialize: (arr) => new Set(arr)
deserialize: (arr) => new Set(arr),
})
public tags: Set<string>;
@svDb({
// Handle custom date formats
serialize: (date) => date?.toISOString(),
deserialize: (str) => str ? new Date(str) : null
deserialize: (str) => (str ? new Date(str) : null),
})
public scheduledAt: Date | null;
}
@@ -651,8 +640,9 @@ class Order extends SmartDataDbDoc<Order, Order> {
// Called before saving (create or update)
async beforeSave() {
// Recalculate total
this.totalAmount = this.items.reduce((sum, item) =>
sum + (item.price * item.quantity), 0
this.totalAmount = this.items.reduce(
(sum, item) => sum + item.price * item.quantity,
0,
);
// Validate
@@ -698,34 +688,37 @@ class Order extends SmartDataDbDoc<Order, Order> {
```typescript
@Collection(() => db)
class HighPerformanceDoc extends SmartDataDbDoc<HighPerformanceDoc, HighPerformanceDoc> {
@unI() // Unique index
class HighPerformanceDoc extends SmartDataDbDoc<
HighPerformanceDoc,
HighPerformanceDoc
> {
@unI() // Unique index
public id: string;
@index() // Single field index
@index() // Single field index
public userId: string;
@index({ sparse: true }) // Sparse index for optional fields
@index({ sparse: true }) // Sparse index for optional fields
public deletedAt?: Date;
@index({
unique: false,
background: true, // Non-blocking index creation
expireAfterSeconds: 86400 // TTL index
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' }
{ userId: 1, createdAt: -1 }, // Compound index
{ name: 'user_activity_idx' },
);
// Text index for search
await this.collection.createIndex(
{ title: 'text', content: 'text' },
{ weights: { title: 10, content: 5 } }
{ weights: { title: 10, content: 5 } },
);
}
}
@@ -739,18 +732,18 @@ const db = new SmartdataDb({
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
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
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
socketTimeoutMS: 360000, // Socket timeout (6 minutes)
family: 4, // Force IPv4
});
```
@@ -759,6 +752,7 @@ const db = new SmartdataDb({
### 1. Always Use TypeScript
SmartData is built for TypeScript. Using JavaScript means missing out on:
- Compile-time query validation
- IntelliSense for MongoDB operators
- Type-safe document updates
@@ -785,7 +779,7 @@ await session.withTransaction(async () => {
});
// ❌ Bad: Multiple operations without transactions
await debitAccount(fromAccount, amount); // What if this fails?
await debitAccount(fromAccount, amount); // What if this fails?
await creditAccount(toAccount, amount);
```
@@ -799,7 +793,7 @@ await cursor.forEach(async (doc) => {
});
// ❌ Bad: Loading everything into memory
const allDocs = await LargeCollection.getInstances({}); // Could OOM!
const allDocs = await LargeCollection.getInstances({}); // Could OOM!
```
### 5. Implement Proper Error Handling
@@ -819,7 +813,7 @@ try {
// ❌ Bad: Ignoring errors
const user = await User.getInstance({ id: userId });
await processUser(user); // What if user is null?
await processUser(user); // What if user is null?
```
## 🔧 Troubleshooting
@@ -854,17 +848,17 @@ Always clean up resources:
// Watchers
const watcher = await User.watch({});
// ... use watcher
await watcher.close(); // Always close!
await watcher.close(); // Always close!
// Cursors
const cursor = await User.getCursor({});
// ... use cursor
await cursor.close(); // Always close!
await cursor.close(); // Always close!
// Sessions
const session = db.startSession();
// ... use session
await session.endSession(); // Always end!
await session.endSession(); // Always end!
```
### Performance Issues