@gravito/dark-matter 1.0.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +213 -5
- package/dist/index.cjs +665 -16
- package/dist/index.d.cts +462 -1
- package/dist/index.d.ts +462 -1
- package/dist/index.js +662 -15
- package/package.json +14 -4
package/README.md
CHANGED
|
@@ -43,9 +43,10 @@ await Mongo.disconnect()
|
|
|
43
43
|
- 📊 **Aggregation Pipeline** - Fluent aggregation API
|
|
44
44
|
- 🔌 **Multi-connection** - Named connections support
|
|
45
45
|
- 🛡️ **Transactions** - ACID transactions with convenient API
|
|
46
|
+
- 🗑️ **Soft Deletes** - Built-in soft delete support with restore capability
|
|
46
47
|
- 📦 **GridFS** - Handle large file uploads/downloads
|
|
47
48
|
- ⚡ **Change Streams** - Real-time database event listening
|
|
48
|
-
- ✅ **Schema Validation** -
|
|
49
|
+
- ✅ **Schema Validation** - Type-safe Schema Builder API for MongoDB validation
|
|
49
50
|
|
|
50
51
|
## API Reference
|
|
51
52
|
|
|
@@ -123,6 +124,52 @@ await Mongo.collection('logs').bulkWrite([
|
|
|
123
124
|
])
|
|
124
125
|
```
|
|
125
126
|
|
|
127
|
+
### Soft Deletes
|
|
128
|
+
|
|
129
|
+
Dark Matter 支援開箱即用的軟刪除功能:
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
// 軟刪除一筆記錄(設置 deletedAt)
|
|
133
|
+
await Mongo.collection('users')
|
|
134
|
+
.where('_id', userId)
|
|
135
|
+
.softDelete()
|
|
136
|
+
|
|
137
|
+
// 查詢時自動排除已軟刪除的記錄
|
|
138
|
+
const activeUsers = await Mongo.collection('users').get()
|
|
139
|
+
|
|
140
|
+
// 包含已軟刪除的記錄
|
|
141
|
+
const allUsers = await Mongo.collection('users')
|
|
142
|
+
.withTrashed()
|
|
143
|
+
.get()
|
|
144
|
+
|
|
145
|
+
// 只查詢已軟刪除的記錄
|
|
146
|
+
const trashedUsers = await Mongo.collection('users')
|
|
147
|
+
.onlyTrashed()
|
|
148
|
+
.get()
|
|
149
|
+
|
|
150
|
+
// 恢復軟刪除的記錄
|
|
151
|
+
await Mongo.collection('users')
|
|
152
|
+
.where('_id', userId)
|
|
153
|
+
.restore()
|
|
154
|
+
|
|
155
|
+
// 批次軟刪除
|
|
156
|
+
await Mongo.collection('users')
|
|
157
|
+
.where('status', 'inactive')
|
|
158
|
+
.softDeleteMany()
|
|
159
|
+
|
|
160
|
+
// 批次恢復
|
|
161
|
+
await Mongo.collection('users')
|
|
162
|
+
.onlyTrashed()
|
|
163
|
+
.restoreMany()
|
|
164
|
+
|
|
165
|
+
// 永久刪除記錄
|
|
166
|
+
await Mongo.collection('users')
|
|
167
|
+
.where('_id', userId)
|
|
168
|
+
.forceDelete()
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**注意**:軟刪除使用 `deletedAt` 欄位(`Date | null`)。請確保在文檔中加入此欄位。
|
|
172
|
+
|
|
126
173
|
### Aggregation Pipeline
|
|
127
174
|
|
|
128
175
|
```typescript
|
|
@@ -152,6 +199,54 @@ const ordersWithCustomers = await Mongo.collection('orders')
|
|
|
152
199
|
.get()
|
|
153
200
|
```
|
|
154
201
|
|
|
202
|
+
### Schema Validation
|
|
203
|
+
|
|
204
|
+
使用友善的 Schema Builder API 建立型別安全的 MongoDB Schema 驗證:
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
import { schema } from '@gravito/dark-matter'
|
|
208
|
+
|
|
209
|
+
// 建構 Schema
|
|
210
|
+
const userSchema = schema()
|
|
211
|
+
.required('username', 'email', 'createdAt')
|
|
212
|
+
.string('username', { minLength: 3, maxLength: 50 })
|
|
213
|
+
.string('email', { pattern: '^.+@.+$' })
|
|
214
|
+
.integer('age', { minimum: 0, maximum: 150 })
|
|
215
|
+
.boolean('isActive')
|
|
216
|
+
.date('createdAt')
|
|
217
|
+
.array('roles', 'string', { minItems: 1 })
|
|
218
|
+
.object('profile', (s) =>
|
|
219
|
+
s
|
|
220
|
+
.string('bio', { maxLength: 500 })
|
|
221
|
+
.string('avatar')
|
|
222
|
+
.integer('followers')
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
// 建立帶有 Schema 驗證的 Collection
|
|
226
|
+
await Mongo.database().createCollectionWithSchema('users', userSchema)
|
|
227
|
+
|
|
228
|
+
// 或使用原生 API
|
|
229
|
+
await Mongo.database().createCollection('users', {
|
|
230
|
+
schema: userSchema.toValidationOptions({
|
|
231
|
+
validationLevel: 'strict',
|
|
232
|
+
validationAction: 'error'
|
|
233
|
+
})
|
|
234
|
+
})
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
#### 支援的欄位類型
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
schema()
|
|
241
|
+
.string('field', { minLength, maxLength, pattern, enum })
|
|
242
|
+
.number('field', { minimum, maximum, exclusiveMinimum, exclusiveMaximum })
|
|
243
|
+
.integer('field', { minimum, maximum })
|
|
244
|
+
.boolean('field')
|
|
245
|
+
.date('field')
|
|
246
|
+
.array('field', 'string', { minItems, maxItems, uniqueItems })
|
|
247
|
+
.object('field', (s) => s.string('nested'))
|
|
248
|
+
```
|
|
249
|
+
|
|
155
250
|
### Advanced Features
|
|
156
251
|
|
|
157
252
|
#### Transactions
|
|
@@ -184,14 +279,53 @@ for await (const event of stream) {
|
|
|
184
279
|
|
|
185
280
|
#### GridFS (File Storage)
|
|
186
281
|
|
|
282
|
+
GridFS 支援大檔案(>16MB)的儲存與串流處理:
|
|
283
|
+
|
|
187
284
|
```typescript
|
|
188
|
-
|
|
285
|
+
import { MongoGridFS } from '@gravito/dark-matter'
|
|
189
286
|
|
|
190
|
-
|
|
191
|
-
const fileId = await grid.upload(Buffer.from('Hello'), { filename: 'hello.txt' })
|
|
287
|
+
const grid = new MongoGridFS(Mongo.database())
|
|
192
288
|
|
|
193
|
-
//
|
|
289
|
+
// 基本上傳下載
|
|
290
|
+
const fileId = await grid.upload(Buffer.from('Hello'), {
|
|
291
|
+
filename: 'hello.txt',
|
|
292
|
+
contentType: 'text/plain',
|
|
293
|
+
metadata: { author: 'John' }
|
|
294
|
+
})
|
|
194
295
|
const content = await grid.download(fileId)
|
|
296
|
+
|
|
297
|
+
// 串流上傳(適合大檔案)
|
|
298
|
+
const stream = file.stream()
|
|
299
|
+
const fileId = await grid.uploadStream(stream, {
|
|
300
|
+
filename: 'large-video.mp4'
|
|
301
|
+
}, (progress) => {
|
|
302
|
+
console.log(`上傳進度: ${progress.bytesWritten} bytes`)
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
// 串流下載
|
|
306
|
+
const downloadStream = grid.downloadStream(fileId)
|
|
307
|
+
const reader = downloadStream.getReader()
|
|
308
|
+
while (true) {
|
|
309
|
+
const { done, value } = await reader.read()
|
|
310
|
+
if (done) break
|
|
311
|
+
// 處理 chunk
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// 大檔案分片上傳(帶進度追蹤)
|
|
315
|
+
const fileId = await grid.uploadLargeFile(largeFile, {
|
|
316
|
+
filename: 'movie.mp4',
|
|
317
|
+
chunkSizeBytes: 255 * 1024 // 255 KB
|
|
318
|
+
}, (progress) => {
|
|
319
|
+
console.log(`進度: ${progress.percentage}%`)
|
|
320
|
+
console.log(`已上傳: ${progress.bytesWritten} / ${progress.totalBytes}`)
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
// 取得檔案中繼資料
|
|
324
|
+
const fileInfo = await grid.findById(fileId)
|
|
325
|
+
console.log(fileInfo.filename, fileInfo.length, fileInfo.uploadDate)
|
|
326
|
+
|
|
327
|
+
// 刪除檔案
|
|
328
|
+
await grid.delete(fileId)
|
|
195
329
|
```
|
|
196
330
|
|
|
197
331
|
#### Schema Validation
|
|
@@ -227,6 +361,77 @@ const health = await Mongo.connection().getHealthStatus()
|
|
|
227
361
|
console.log(health.latencyMs) // e.g. 15
|
|
228
362
|
```
|
|
229
363
|
|
|
364
|
+
## Performance Optimization
|
|
365
|
+
|
|
366
|
+
### Indexing
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
// Create indexes for frequently queried fields
|
|
370
|
+
await Mongo.collection('users').createIndex({ email: 1 }, { unique: true })
|
|
371
|
+
await Mongo.collection('users').createIndex({ status: 1, createdAt: -1 })
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Query Optimization
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
// Use projection to reduce data transfer
|
|
378
|
+
const users = await Mongo.collection('users')
|
|
379
|
+
.select('name', 'email') // Only fetch needed fields
|
|
380
|
+
.where('status', 'active')
|
|
381
|
+
.get()
|
|
382
|
+
|
|
383
|
+
// Use limit for large result sets
|
|
384
|
+
const recentPosts = await Mongo.collection('posts')
|
|
385
|
+
.orderBy('createdAt', 'desc')
|
|
386
|
+
.limit(20) // Prevent fetching too much data
|
|
387
|
+
.get()
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Connection Pool
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
Mongo.configure({
|
|
394
|
+
default: 'main',
|
|
395
|
+
connections: {
|
|
396
|
+
main: {
|
|
397
|
+
uri: 'mongodb://localhost:27017',
|
|
398
|
+
database: 'myapp',
|
|
399
|
+
maxPoolSize: 50, // Adjust based on load
|
|
400
|
+
minPoolSize: 10, // Keep minimum connections
|
|
401
|
+
maxIdleTimeMS: 30000 // Idle connection timeout
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
})
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Batch Operations
|
|
408
|
+
|
|
409
|
+
```typescript
|
|
410
|
+
// Use bulkWrite to reduce round trips
|
|
411
|
+
await Mongo.collection('logs').bulkWrite([
|
|
412
|
+
{ insertOne: { document: { event: 'login', userId: 1 } } },
|
|
413
|
+
{ updateOne: { filter: { _id: 2 }, update: { $set: { status: 'active' } } } },
|
|
414
|
+
{ deleteOne: { filter: { _id: 3 } } }
|
|
415
|
+
])
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### Monitoring
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
import { MongoPoolMonitor } from '@gravito/dark-matter'
|
|
422
|
+
|
|
423
|
+
const monitor = new MongoPoolMonitor(Mongo.connection())
|
|
424
|
+
const metrics = monitor.getMetrics()
|
|
425
|
+
|
|
426
|
+
console.log('Pool status:', {
|
|
427
|
+
totalConnections: metrics.totalConnections,
|
|
428
|
+
availableConnections: metrics.availableConnections,
|
|
429
|
+
waitQueueSize: metrics.waitQueueSize
|
|
430
|
+
})
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
詳細的效能調優指南請參考 [docs/performance-analysis.md](./docs/performance-analysis.md)
|
|
434
|
+
|
|
230
435
|
## Roadmap
|
|
231
436
|
|
|
232
437
|
- [x] Connection retry & health check
|
|
@@ -234,6 +439,9 @@ console.log(health.latencyMs) // e.g. 15
|
|
|
234
439
|
- [x] Schema validation
|
|
235
440
|
- [x] Change streams
|
|
236
441
|
- [x] GridFS support
|
|
442
|
+
- [x] Soft deletes
|
|
443
|
+
- [x] Schema Builder API
|
|
444
|
+
- [x] GridFS streaming & progress tracking
|
|
237
445
|
|
|
238
446
|
## License
|
|
239
447
|
|