@procedures/mongo 0.8.1 → 0.9.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/CHANGELOG.md +34 -0
- package/README.md +85 -669
- package/dist/.tsbuildinfo +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/storage/document-storage.d.ts +194 -0
- package/dist/storage/document-storage.d.ts.map +1 -0
- package/dist/storage/document-storage.js +694 -0
- package/dist/storage/document-storage.js.map +1 -0
- package/dist/storage/index.d.ts +4 -0
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +2 -0
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/pipeline-scope.d.ts +73 -0
- package/dist/storage/pipeline-scope.d.ts.map +1 -0
- package/dist/storage/pipeline-scope.js +262 -0
- package/dist/storage/pipeline-scope.js.map +1 -0
- package/package.json +3 -3
- package/src/index.ts +14 -2
- package/src/storage/document-storage.ts +787 -0
- package/src/storage/index.ts +13 -0
- package/src/storage/pipeline-scope.ts +281 -0
package/README.md
CHANGED
|
@@ -253,675 +253,35 @@ Check if a default connection exists.
|
|
|
253
253
|
|
|
254
254
|
### Registered Procedures
|
|
255
255
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
latencyMs: number; // Response time in milliseconds
|
|
286
|
-
ok: boolean; // Server status
|
|
287
|
-
}
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
##### `mongo.database.info`
|
|
291
|
-
|
|
292
|
-
Get database information and statistics.
|
|
293
|
-
|
|
294
|
-
```typescript
|
|
295
|
-
const result = await client.exec(
|
|
296
|
-
["mongo", "database", "info"],
|
|
297
|
-
{ includeStats: true }
|
|
298
|
-
);
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
**Input:**
|
|
302
|
-
```typescript
|
|
303
|
-
interface InfoInput {
|
|
304
|
-
includeStats?: boolean;
|
|
305
|
-
}
|
|
306
|
-
```
|
|
307
|
-
|
|
308
|
-
**Output:**
|
|
309
|
-
```typescript
|
|
310
|
-
interface DatabaseInfo {
|
|
311
|
-
name: string; // Database name
|
|
312
|
-
collections: number; // Number of collections
|
|
313
|
-
views: number; // Number of views
|
|
314
|
-
sizeOnDisk: number; // Size on disk in bytes
|
|
315
|
-
empty: boolean; // Whether database is empty
|
|
316
|
-
}
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
#### Collection Procedures
|
|
320
|
-
|
|
321
|
-
##### `mongo.collections.list`
|
|
322
|
-
|
|
323
|
-
List all collections in the database.
|
|
324
|
-
|
|
325
|
-
```typescript
|
|
326
|
-
const result = await client.exec(
|
|
327
|
-
["mongo", "collections", "list"],
|
|
328
|
-
{ filter: "user" }
|
|
329
|
-
);
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
**Input:**
|
|
333
|
-
```typescript
|
|
334
|
-
interface ListCollectionsInput {
|
|
335
|
-
filter?: string; // Filter by name pattern
|
|
336
|
-
}
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
**Output:**
|
|
340
|
-
```typescript
|
|
341
|
-
interface ListCollectionsOutput {
|
|
342
|
-
collections: string[]; // Collection names
|
|
343
|
-
}
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
##### `mongo.collections.create`
|
|
347
|
-
|
|
348
|
-
Create a new collection.
|
|
349
|
-
|
|
350
|
-
```typescript
|
|
351
|
-
const result = await client.exec(
|
|
352
|
-
["mongo", "collections", "create"],
|
|
353
|
-
{
|
|
354
|
-
name: "logs",
|
|
355
|
-
options: {
|
|
356
|
-
capped: true,
|
|
357
|
-
size: 10485760, // 10MB
|
|
358
|
-
max: 1000
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
);
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
**Input:**
|
|
365
|
-
```typescript
|
|
366
|
-
interface CreateCollectionInput {
|
|
367
|
-
name: string;
|
|
368
|
-
options?: CollectionOptions;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
interface CollectionOptions {
|
|
372
|
-
capped?: boolean;
|
|
373
|
-
size?: number; // Max size in bytes (for capped)
|
|
374
|
-
max?: number; // Max documents (for capped)
|
|
375
|
-
validator?: Document; // JSON Schema validator
|
|
376
|
-
}
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
**Output:**
|
|
380
|
-
```typescript
|
|
381
|
-
interface CreateCollectionOutput {
|
|
382
|
-
created: boolean;
|
|
383
|
-
name: string;
|
|
384
|
-
}
|
|
385
|
-
```
|
|
386
|
-
|
|
387
|
-
##### `mongo.collections.drop`
|
|
388
|
-
|
|
389
|
-
Drop a collection.
|
|
390
|
-
|
|
391
|
-
```typescript
|
|
392
|
-
const result = await client.exec(
|
|
393
|
-
["mongo", "collections", "drop"],
|
|
394
|
-
{ name: "old_collection" }
|
|
395
|
-
);
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
**Input:**
|
|
399
|
-
```typescript
|
|
400
|
-
interface DropCollectionInput {
|
|
401
|
-
name: string;
|
|
402
|
-
}
|
|
403
|
-
```
|
|
404
|
-
|
|
405
|
-
**Output:**
|
|
406
|
-
```typescript
|
|
407
|
-
interface DropCollectionOutput {
|
|
408
|
-
dropped: boolean;
|
|
409
|
-
}
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
##### `mongo.collections.stats`
|
|
413
|
-
|
|
414
|
-
Get collection statistics.
|
|
415
|
-
|
|
416
|
-
```typescript
|
|
417
|
-
const result = await client.exec(
|
|
418
|
-
["mongo", "collections", "stats"],
|
|
419
|
-
{},
|
|
420
|
-
{ metadata: { collection: "users" } }
|
|
421
|
-
);
|
|
422
|
-
```
|
|
423
|
-
|
|
424
|
-
**Input:**
|
|
425
|
-
```typescript
|
|
426
|
-
interface StatsInput {
|
|
427
|
-
// Collection from metadata
|
|
428
|
-
}
|
|
429
|
-
```
|
|
430
|
-
|
|
431
|
-
**Output:**
|
|
432
|
-
```typescript
|
|
433
|
-
interface CollectionStats {
|
|
434
|
-
count: number; // Number of documents
|
|
435
|
-
size: number; // Size of documents in bytes
|
|
436
|
-
avgObjSize: number; // Average document size
|
|
437
|
-
storageSize: number; // Total storage size
|
|
438
|
-
nindexes: number; // Number of indexes
|
|
439
|
-
totalIndexSize: number; // Total index size
|
|
440
|
-
}
|
|
441
|
-
```
|
|
442
|
-
|
|
443
|
-
#### Document Procedures
|
|
444
|
-
|
|
445
|
-
##### `mongo.documents.find`
|
|
446
|
-
|
|
447
|
-
Find documents with pagination, filtering, sorting, and projection.
|
|
448
|
-
|
|
449
|
-
```typescript
|
|
450
|
-
const result = await client.exec(
|
|
451
|
-
["mongo", "documents", "find"],
|
|
452
|
-
{
|
|
453
|
-
query: { status: "active", age: { $gte: 18 } },
|
|
454
|
-
projection: { name: 1, email: 1 },
|
|
455
|
-
sort: { createdAt: -1 },
|
|
456
|
-
page: 1,
|
|
457
|
-
limit: 20
|
|
458
|
-
},
|
|
459
|
-
{ metadata: { collection: "users" } }
|
|
460
|
-
);
|
|
461
|
-
```
|
|
462
|
-
|
|
463
|
-
**Input:**
|
|
464
|
-
```typescript
|
|
465
|
-
interface FindInput {
|
|
466
|
-
query?: DocumentQuery; // MongoDB filter
|
|
467
|
-
projection?: Record<string, 0 | 1>; // Field projection
|
|
468
|
-
sort?: SortSpec; // Sort specification
|
|
469
|
-
page?: number; // Page number (1-indexed)
|
|
470
|
-
limit?: number; // Items per page (max 100)
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
type DocumentQuery = Filter<Document>;
|
|
474
|
-
type SortSpec = Record<string, 1 | -1>;
|
|
475
|
-
```
|
|
476
|
-
|
|
477
|
-
**Output:**
|
|
478
|
-
```typescript
|
|
479
|
-
interface FindOutput {
|
|
480
|
-
documents: MongoDocument[];
|
|
481
|
-
pagination: PaginationOutput;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
interface PaginationOutput {
|
|
485
|
-
page: number;
|
|
486
|
-
limit: number;
|
|
487
|
-
total: number;
|
|
488
|
-
totalPages: number;
|
|
489
|
-
hasNext: boolean;
|
|
490
|
-
hasPrev: boolean;
|
|
491
|
-
}
|
|
492
|
-
```
|
|
493
|
-
|
|
494
|
-
##### `mongo.documents.get`
|
|
495
|
-
|
|
496
|
-
Get a single document by ID.
|
|
497
|
-
|
|
498
|
-
```typescript
|
|
499
|
-
const result = await client.exec(
|
|
500
|
-
["mongo", "documents", "get"],
|
|
501
|
-
{ id: "507f1f77bcf86cd799439011" },
|
|
502
|
-
{ metadata: { collection: "users" } }
|
|
503
|
-
);
|
|
504
|
-
```
|
|
505
|
-
|
|
506
|
-
**Input:**
|
|
507
|
-
```typescript
|
|
508
|
-
interface GetInput {
|
|
509
|
-
id: string; // Document ID (converted to ObjectId)
|
|
510
|
-
}
|
|
511
|
-
```
|
|
512
|
-
|
|
513
|
-
**Output:**
|
|
514
|
-
```typescript
|
|
515
|
-
interface GetOutput {
|
|
516
|
-
document: MongoDocument | null;
|
|
517
|
-
}
|
|
518
|
-
```
|
|
519
|
-
|
|
520
|
-
##### `mongo.documents.insert`
|
|
521
|
-
|
|
522
|
-
Insert one or more documents.
|
|
523
|
-
|
|
524
|
-
```typescript
|
|
525
|
-
// Insert single document
|
|
526
|
-
const result = await client.exec(
|
|
527
|
-
["mongo", "documents", "insert"],
|
|
528
|
-
{
|
|
529
|
-
documents: {
|
|
530
|
-
name: "John Doe",
|
|
531
|
-
email: "john@example.com",
|
|
532
|
-
createdAt: new Date()
|
|
533
|
-
}
|
|
534
|
-
},
|
|
535
|
-
{ metadata: { collection: "users" } }
|
|
536
|
-
);
|
|
537
|
-
|
|
538
|
-
// Insert multiple documents
|
|
539
|
-
const result = await client.exec(
|
|
540
|
-
["mongo", "documents", "insert"],
|
|
541
|
-
{
|
|
542
|
-
documents: [
|
|
543
|
-
{ name: "Alice" },
|
|
544
|
-
{ name: "Bob" }
|
|
545
|
-
]
|
|
546
|
-
},
|
|
547
|
-
{ metadata: { collection: "users" } }
|
|
548
|
-
);
|
|
549
|
-
```
|
|
550
|
-
|
|
551
|
-
**Input:**
|
|
552
|
-
```typescript
|
|
553
|
-
interface InsertInput {
|
|
554
|
-
documents: MongoDocument | MongoDocument[];
|
|
555
|
-
}
|
|
556
|
-
```
|
|
557
|
-
|
|
558
|
-
**Output:**
|
|
559
|
-
```typescript
|
|
560
|
-
interface InsertOutput {
|
|
561
|
-
insertedCount: number;
|
|
562
|
-
insertedIds: string[];
|
|
563
|
-
}
|
|
564
|
-
```
|
|
565
|
-
|
|
566
|
-
##### `mongo.documents.update`
|
|
567
|
-
|
|
568
|
-
Update one document by `id`, or the documents that match a `filter`.
|
|
569
|
-
|
|
570
|
-
A write needs exactly one of `id` or `filter`. An unknown field is an error: `query` is the field of `find` and `count`, not of `update`. An empty filter matches every document, so it needs `confirm: true`.
|
|
571
|
-
|
|
572
|
-
```typescript
|
|
573
|
-
// Update one document by ID
|
|
574
|
-
const result = await client.exec(
|
|
575
|
-
["mongo", "documents", "update"],
|
|
576
|
-
{
|
|
577
|
-
collection: "users",
|
|
578
|
-
id: "...",
|
|
579
|
-
update: { $set: { status: "inactive" } },
|
|
580
|
-
upsert: false
|
|
581
|
-
}
|
|
582
|
-
);
|
|
583
|
-
|
|
584
|
-
// Update multiple documents
|
|
585
|
-
const result = await client.exec(
|
|
586
|
-
["mongo", "documents", "update"],
|
|
587
|
-
{
|
|
588
|
-
collection: "users",
|
|
589
|
-
filter: { status: "pending" },
|
|
590
|
-
update: { $set: { status: "active" } },
|
|
591
|
-
multi: true
|
|
592
|
-
}
|
|
593
|
-
);
|
|
594
|
-
```
|
|
595
|
-
|
|
596
|
-
**Input:**
|
|
597
|
-
```typescript
|
|
598
|
-
interface UpdateInput {
|
|
599
|
-
collection?: string; // Or metadata.collection
|
|
600
|
-
database?: string; // Or metadata.database
|
|
601
|
-
id?: string; // Exactly one of id and filter
|
|
602
|
-
idType?: "auto" | "objectId" | "string";
|
|
603
|
-
filter?: DocumentQuery;
|
|
604
|
-
update: DocumentUpdate;
|
|
605
|
-
multi?: boolean; // Update all matching documents
|
|
606
|
-
upsert?: boolean; // Insert if not found
|
|
607
|
-
confirm?: boolean; // Required for an empty filter
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
type DocumentUpdate = UpdateFilter<Document>;
|
|
611
|
-
```
|
|
612
|
-
|
|
613
|
-
An upsert by `id` in the "auto" mode updates the document with that ID in either form. When no document matches, the new document gets the ObjectId form of the ID.
|
|
614
|
-
|
|
615
|
-
**Output:**
|
|
616
|
-
```typescript
|
|
617
|
-
interface UpdateOutput {
|
|
618
|
-
acknowledged: boolean;
|
|
619
|
-
matchedCount: number;
|
|
620
|
-
modifiedCount: number;
|
|
621
|
-
upsertedId: string | null;
|
|
622
|
-
upsertedCount: number;
|
|
623
|
-
}
|
|
624
|
-
```
|
|
625
|
-
|
|
626
|
-
##### `mongo.documents.delete`
|
|
627
|
-
|
|
628
|
-
Delete one document by `id`, or the documents that match a `filter`. The rules of `update` apply: exactly one target, no unknown fields, and `confirm: true` for an empty filter.
|
|
629
|
-
|
|
630
|
-
```typescript
|
|
631
|
-
// Delete one document by ID
|
|
632
|
-
const result = await client.exec(
|
|
633
|
-
["mongo", "documents", "delete"],
|
|
634
|
-
{ collection: "users", id: "..." }
|
|
635
|
-
);
|
|
636
|
-
|
|
637
|
-
// Delete multiple documents
|
|
638
|
-
const result = await client.exec(
|
|
639
|
-
["mongo", "documents", "delete"],
|
|
640
|
-
{
|
|
641
|
-
collection: "users",
|
|
642
|
-
filter: { status: "inactive" },
|
|
643
|
-
multi: true
|
|
644
|
-
}
|
|
645
|
-
);
|
|
646
|
-
```
|
|
647
|
-
|
|
648
|
-
**Input:**
|
|
649
|
-
```typescript
|
|
650
|
-
interface DeleteInput {
|
|
651
|
-
collection?: string; // Or metadata.collection
|
|
652
|
-
database?: string; // Or metadata.database
|
|
653
|
-
id?: string; // Exactly one of id and filter
|
|
654
|
-
idType?: "auto" | "objectId" | "string";
|
|
655
|
-
filter?: DocumentQuery;
|
|
656
|
-
multi?: boolean; // Delete all matching documents
|
|
657
|
-
confirm?: boolean; // Required for an empty filter
|
|
658
|
-
}
|
|
659
|
-
```
|
|
660
|
-
|
|
661
|
-
**Output:**
|
|
662
|
-
```typescript
|
|
663
|
-
interface DeleteOutput {
|
|
664
|
-
acknowledged: boolean;
|
|
665
|
-
deletedCount: number;
|
|
666
|
-
}
|
|
667
|
-
```
|
|
668
|
-
|
|
669
|
-
##### `mongo.documents.count`
|
|
670
|
-
|
|
671
|
-
Count documents matching a query.
|
|
672
|
-
|
|
673
|
-
```typescript
|
|
674
|
-
const result = await client.exec(
|
|
675
|
-
["mongo", "documents", "count"],
|
|
676
|
-
{ query: { status: "active" } },
|
|
677
|
-
{ metadata: { collection: "users" } }
|
|
678
|
-
);
|
|
679
|
-
```
|
|
680
|
-
|
|
681
|
-
**Input:**
|
|
682
|
-
```typescript
|
|
683
|
-
interface CountInput {
|
|
684
|
-
query?: DocumentQuery;
|
|
685
|
-
}
|
|
686
|
-
```
|
|
687
|
-
|
|
688
|
-
**Output:**
|
|
689
|
-
```typescript
|
|
690
|
-
interface CountOutput {
|
|
691
|
-
count: number;
|
|
692
|
-
}
|
|
693
|
-
```
|
|
694
|
-
|
|
695
|
-
##### `mongo.documents.aggregate`
|
|
696
|
-
|
|
697
|
-
Execute an aggregation pipeline.
|
|
698
|
-
|
|
699
|
-
```typescript
|
|
700
|
-
const result = await client.exec(
|
|
701
|
-
["mongo", "documents", "aggregate"],
|
|
702
|
-
{
|
|
703
|
-
pipeline: [
|
|
704
|
-
{ $match: { status: "active" } },
|
|
705
|
-
{ $group: { _id: "$country", count: { $sum: 1 } } },
|
|
706
|
-
{ $sort: { count: -1 } }
|
|
707
|
-
],
|
|
708
|
-
options: {
|
|
709
|
-
allowDiskUse: true,
|
|
710
|
-
maxTimeMS: 5000
|
|
711
|
-
}
|
|
712
|
-
},
|
|
713
|
-
{ metadata: { collection: "users" } }
|
|
714
|
-
);
|
|
715
|
-
```
|
|
716
|
-
|
|
717
|
-
**Input:**
|
|
718
|
-
```typescript
|
|
719
|
-
interface AggregateInput {
|
|
720
|
-
pipeline: AggregationStage[];
|
|
721
|
-
options?: AggregationOptions;
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
type AggregationStage = Document;
|
|
725
|
-
|
|
726
|
-
interface AggregationOptions {
|
|
727
|
-
allowDiskUse?: boolean;
|
|
728
|
-
maxTimeMS?: number;
|
|
729
|
-
}
|
|
730
|
-
```
|
|
731
|
-
|
|
732
|
-
**Output:**
|
|
733
|
-
```typescript
|
|
734
|
-
interface AggregateOutput {
|
|
735
|
-
results: MongoDocument[];
|
|
736
|
-
}
|
|
737
|
-
```
|
|
738
|
-
|
|
739
|
-
##### `mongo.documents.distinct`
|
|
740
|
-
|
|
741
|
-
The procedure gives the distinct values of one field in the documents that match `query`. The default `query` matches all documents. An array field gives each of its elements. Use dots for an embedded field, for example `address.city`.
|
|
742
|
-
|
|
743
|
-
```typescript
|
|
744
|
-
const { values } = await client.exec(
|
|
745
|
-
["mongo", "documents", "distinct"],
|
|
746
|
-
{ collection: "users", field: "country", query: { status: "active" } }
|
|
747
|
-
);
|
|
748
|
-
```
|
|
749
|
-
|
|
750
|
-
##### `mongo.documents.bulk`
|
|
751
|
-
|
|
752
|
-
The procedure sends several writes in one request. The operations have the form of the `bulkWrite` of the driver: `insertOne`, `updateOne`, `updateMany`, `deleteOne` and `deleteMany`. By default, the operations run in order, and the first failure stops the rest. With `ordered: false`, the server tries each operation, and the error tells which ones failed.
|
|
753
|
-
|
|
754
|
-
The rules of `update` and `delete` apply to each operation. An empty filter of an update or a delete needs `confirm: true` on the call. An unknown operation or field is an error.
|
|
755
|
-
|
|
756
|
-
```typescript
|
|
757
|
-
const result = await client.exec(["mongo", "documents", "bulk"], {
|
|
758
|
-
collection: "users",
|
|
759
|
-
operations: [
|
|
760
|
-
{ insertOne: { document: { name: "Di" } } },
|
|
761
|
-
{ updateOne: { filter: { name: "Ann" }, update: { $set: { status: "active" } } } },
|
|
762
|
-
{ updateMany: { filter: { status: "new" }, update: { $set: { status: "active" } } } },
|
|
763
|
-
{ deleteOne: { filter: { name: "Bob" } } },
|
|
764
|
-
{ deleteMany: { filter: { status: "deleted" } } },
|
|
765
|
-
],
|
|
766
|
-
});
|
|
767
|
-
// result: { ok, insertedCount, matchedCount, modifiedCount, deletedCount, upsertedCount, insertedIds, upsertedIds }
|
|
768
|
-
```
|
|
769
|
-
|
|
770
|
-
The ids in `insertedIds` and `upsertedIds` are text, and their keys are the indexes of the operations.
|
|
771
|
-
|
|
772
|
-
##### `mongo.documents.explain`
|
|
773
|
-
|
|
774
|
-
The procedure gives the plan of a find or of an aggregation pipeline. With `pipeline`, it explains an aggregation. Without `pipeline`, it explains a find with `query`, `projection`, `sort` and `limit`. The verbosity `queryPlanner` (the default) gives the plan only. With the verbosities `executionStats` and `allPlansExecution`, the server also does the plan and gives its statistics. As `documents.aggregate` does, the procedure refuses `$out` and `$merge`.
|
|
775
|
-
|
|
776
|
-
```typescript
|
|
777
|
-
const { operation, plan } = await client.exec(["mongo", "documents", "explain"], {
|
|
778
|
-
collection: "users",
|
|
779
|
-
query: { email: "ann@example.com" },
|
|
780
|
-
verbosity: "executionStats",
|
|
781
|
-
});
|
|
782
|
-
console.log(plan.queryPlanner.winningPlan);
|
|
783
|
-
```
|
|
784
|
-
|
|
785
|
-
#### Index Procedures
|
|
786
|
-
|
|
787
|
-
##### `mongo.indexes.list`
|
|
788
|
-
|
|
789
|
-
List all indexes on a collection.
|
|
790
|
-
|
|
791
|
-
```typescript
|
|
792
|
-
const result = await client.exec(
|
|
793
|
-
["mongo", "indexes", "list"],
|
|
794
|
-
{},
|
|
795
|
-
{ metadata: { collection: "users" } }
|
|
796
|
-
);
|
|
797
|
-
```
|
|
798
|
-
|
|
799
|
-
**Input:**
|
|
800
|
-
```typescript
|
|
801
|
-
interface ListIndexesInput {
|
|
802
|
-
// Collection from metadata
|
|
803
|
-
}
|
|
804
|
-
```
|
|
805
|
-
|
|
806
|
-
**Output:**
|
|
807
|
-
```typescript
|
|
808
|
-
interface ListIndexesOutput {
|
|
809
|
-
indexes: IndexInfo[];
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
interface IndexInfo {
|
|
813
|
-
name: string;
|
|
814
|
-
key: IndexSpec;
|
|
815
|
-
unique?: boolean;
|
|
816
|
-
sparse?: boolean;
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
type IndexSpec = Record<string, 1 | -1 | "text" | "2dsphere">;
|
|
820
|
-
```
|
|
821
|
-
|
|
822
|
-
##### `mongo.indexes.create`
|
|
823
|
-
|
|
824
|
-
Create an index on a collection.
|
|
825
|
-
|
|
826
|
-
```typescript
|
|
827
|
-
// Simple index
|
|
828
|
-
const result = await client.exec(
|
|
829
|
-
["mongo", "indexes", "create"],
|
|
830
|
-
{
|
|
831
|
-
keys: { email: 1 },
|
|
832
|
-
options: { unique: true }
|
|
833
|
-
},
|
|
834
|
-
{ metadata: { collection: "users" } }
|
|
835
|
-
);
|
|
836
|
-
|
|
837
|
-
// Compound index
|
|
838
|
-
const result = await client.exec(
|
|
839
|
-
["mongo", "indexes", "create"],
|
|
840
|
-
{
|
|
841
|
-
keys: { status: 1, createdAt: -1 },
|
|
842
|
-
options: { name: "status_created_idx" }
|
|
843
|
-
},
|
|
844
|
-
{ metadata: { collection: "users" } }
|
|
845
|
-
);
|
|
846
|
-
|
|
847
|
-
// Text index
|
|
848
|
-
const result = await client.exec(
|
|
849
|
-
["mongo", "indexes", "create"],
|
|
850
|
-
{
|
|
851
|
-
keys: { title: "text", description: "text" }
|
|
852
|
-
},
|
|
853
|
-
{ metadata: { collection: "articles" } }
|
|
854
|
-
);
|
|
855
|
-
```
|
|
856
|
-
|
|
857
|
-
**Input:**
|
|
858
|
-
```typescript
|
|
859
|
-
interface CreateIndexInput {
|
|
860
|
-
keys: IndexSpec;
|
|
861
|
-
options?: IndexOptions;
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
interface IndexOptions {
|
|
865
|
-
name?: string;
|
|
866
|
-
unique?: boolean;
|
|
867
|
-
sparse?: boolean;
|
|
868
|
-
background?: boolean;
|
|
869
|
-
expireAfterSeconds?: number; // TTL index
|
|
870
|
-
}
|
|
871
|
-
```
|
|
872
|
-
|
|
873
|
-
**Output:**
|
|
874
|
-
```typescript
|
|
875
|
-
interface CreateIndexOutput {
|
|
876
|
-
name: string; // Index name
|
|
877
|
-
}
|
|
878
|
-
```
|
|
879
|
-
|
|
880
|
-
##### `mongo.indexes.drop`
|
|
881
|
-
|
|
882
|
-
Drop an index from a collection.
|
|
883
|
-
|
|
884
|
-
```typescript
|
|
885
|
-
const result = await client.exec(
|
|
886
|
-
["mongo", "indexes", "drop"],
|
|
887
|
-
{ name: "email_1" },
|
|
888
|
-
{ metadata: { collection: "users" } }
|
|
889
|
-
);
|
|
890
|
-
```
|
|
891
|
-
|
|
892
|
-
**Input:**
|
|
893
|
-
```typescript
|
|
894
|
-
interface DropIndexInput {
|
|
895
|
-
name: string; // Index name to drop
|
|
896
|
-
}
|
|
897
|
-
```
|
|
898
|
-
|
|
899
|
-
**Output:**
|
|
900
|
-
```typescript
|
|
901
|
-
interface DropIndexOutput {
|
|
902
|
-
dropped: boolean;
|
|
903
|
-
}
|
|
904
|
-
```
|
|
905
|
-
|
|
906
|
-
#### Change Streams
|
|
907
|
-
|
|
908
|
-
##### `mongo.watch`
|
|
909
|
-
|
|
910
|
-
The procedure gives the changes of a collection as a stream. Without `collection`, it gives the changes of each collection of the database. The stream has no end (`metadata.stream: { unbounded: true }`). It stops when the reader stops or the call aborts, and the procedure then closes the change stream.
|
|
911
|
-
|
|
912
|
-
A change stream needs a replica set or a sharded cluster. A standalone server refuses it, and the reader gets that error.
|
|
913
|
-
|
|
914
|
-
```typescript
|
|
915
|
-
for await (const change of client.execStream(["mongo", "watch"], {
|
|
916
|
-
collection: "orders",
|
|
917
|
-
pipeline: [{ $match: { operationType: { $in: ["insert", "update"] } } }],
|
|
918
|
-
fullDocument: "updateLookup",
|
|
919
|
-
})) {
|
|
920
|
-
console.log(change.operationType, change.documentKey, change.resumeToken);
|
|
921
|
-
}
|
|
922
|
-
```
|
|
923
|
-
|
|
924
|
-
Each item has the `operationType`, the namespace `ns`, the `documentKey` and the `resumeToken`. An insert gives the `fullDocument`, and an update gives the `updateDescription`. With `fullDocument: "updateLookup"`, an update also gives the current document. To continue after a stop, give the last `resumeToken` as `resumeAfter`. The `pipeline` accepts only the stages of a change stream: `$match`, `$project`, `$addFields`, `$set`, `$unset`, `$replaceRoot`, `$replaceWith` and `$redact`.
|
|
256
|
+
`import "@procedures/mongo/register"` registers the procedures in the global registry. The package root registers nothing: a host gives `mongoModule` to `createRegistry`. Each procedure of a collection takes `collection`, and each procedure takes an optional `database`. The catalog of the site shows the input and the output schema of each procedure.
|
|
257
|
+
|
|
258
|
+
| Procedure | What it does | Main input |
|
|
259
|
+
|---|---|---|
|
|
260
|
+
| `mongo.database.ping` | Tests the connection and gives the latency. | `timeout` |
|
|
261
|
+
| `mongo.database.info` | Gives the name, the counts and the size of the database. | `includeStats` |
|
|
262
|
+
| `mongo.collections.list` | Lists the collections of the database. | `filter` |
|
|
263
|
+
| `mongo.collections.create` | Makes a collection, capped or with a validator. | `name`, `options` |
|
|
264
|
+
| `mongo.collections.drop` | Removes a collection. | `name` |
|
|
265
|
+
| `mongo.collections.stats` | Gives the count, the sizes and the indexes of a collection. | |
|
|
266
|
+
| `mongo.documents.find` | Finds documents, with a projection, a sort and pages. | `query`, `projection`, `sort`, `page`, `limit` |
|
|
267
|
+
| `mongo.documents.get` | Gives one document by its id. | `id` |
|
|
268
|
+
| `mongo.documents.insert` | Inserts one document or more. | `documents` |
|
|
269
|
+
| `mongo.documents.update` | Updates the document of `id`, or the documents that match `filter`. | `id` or `filter`, `update`, `multi`, `upsert`, `confirm` |
|
|
270
|
+
| `mongo.documents.delete` | Deletes the document of `id`, or the documents that match `filter`. | `id` or `filter`, `multi`, `confirm` |
|
|
271
|
+
| `mongo.documents.count` | Counts the documents that match a query. | `query` |
|
|
272
|
+
| `mongo.documents.aggregate` | Runs an aggregation pipeline. It refuses `$out` and `$merge`. | `pipeline`, `allowDiskUse`, `maxTimeMS` |
|
|
273
|
+
| `mongo.documents.distinct` | Gives the distinct values of a field. An array field gives each of its elements. | `field` (dots for an embedded field), `query` |
|
|
274
|
+
| `mongo.documents.bulk` | Sends several writes in one request, in the form of the `bulkWrite` of the driver. | `operations`, `ordered`, `confirm` |
|
|
275
|
+
| `mongo.documents.explain` | Gives the plan of a find, or of a pipeline. It refuses `$out` and `$merge`. | `query` or `pipeline`, `verbosity` |
|
|
276
|
+
| `mongo.indexes.list` | Lists the indexes of a collection. | |
|
|
277
|
+
| `mongo.indexes.create` | Makes an index. | `keys`, `options` |
|
|
278
|
+
| `mongo.indexes.drop` | Removes an index. | `name` |
|
|
279
|
+
| `mongo.watch` | Gives the changes of a collection as a stream, or of each collection without `collection`. | `collection`, `pipeline`, `fullDocument`, `resumeAfter` |
|
|
280
|
+
|
|
281
|
+
- `update` and `delete` need exactly one target (`id` or `filter`), and refuse unknown fields. An empty filter needs `confirm: true`.
|
|
282
|
+
- The operations of `bulk` are `insertOne`, `updateOne`, `updateMany`, `deleteOne` and `deleteMany`. By default the server does them in order, and the first failure stops the rest. With `ordered: false`, the server tries each operation, and the error tells which ones failed.
|
|
283
|
+
- The verbosity `queryPlanner` of `explain` (the default) gives the plan only. With `executionStats` and `allPlansExecution`, the server also does the plan and gives its statistics.
|
|
284
|
+
- The stream of `watch` has no end (`metadata.stream: { unbounded: true }`). It stops when the reader stops or the call aborts, and the procedure then closes the change stream.
|
|
925
285
|
|
|
926
286
|
### Type Definitions
|
|
927
287
|
|
|
@@ -1283,6 +643,62 @@ await client.exec(
|
|
|
1283
643
|
);
|
|
1284
644
|
```
|
|
1285
645
|
|
|
646
|
+
## Document storage: `MongoDocumentStorage`
|
|
647
|
+
|
|
648
|
+
`MongoDocumentStorage` is a `CollectionStorage` of `@procedures/collections` over MongoDB (roadmap CLI-24). It has flat documents, a change log, versions, queries in the database and partitions. `MongoStorage` keeps its `{ _id, data }` documents.
|
|
649
|
+
|
|
650
|
+
```ts
|
|
651
|
+
import { MongoDocumentStorage } from "@procedures/mongo";
|
|
652
|
+
|
|
653
|
+
const notes = new MongoDocumentStorage<Note>({ collection: "notes" });
|
|
654
|
+
await notes.set("n1", { title: "Plan", done: false });
|
|
655
|
+
await notes.query({ done: false }, { sort: { title: 1 }, limit: 20 });
|
|
656
|
+
|
|
657
|
+
// One storage for each tenant, on one collection (roadmap CLI-17)
|
|
658
|
+
server.usePartitions("notes", { storageOf: (partition) => notes.forPartition(partition), resolve });
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
| Option | Use |
|
|
662
|
+
| --- | --- |
|
|
663
|
+
| `collection` | The MongoDB collection |
|
|
664
|
+
| `db` | The database. The default is the database of the default connection. |
|
|
665
|
+
| `partition` | The partition of the storage. `forPartition(partition)` gives the storage of a partition, the same one each time. |
|
|
666
|
+
| `maxTombstones` | The most deletes that the change log keeps in each partition (default 10000) |
|
|
667
|
+
| `queryPolicy` | The query policy of `query()` without a policy of the call |
|
|
668
|
+
| `counters` | The collection of the sequence counters (default `__storage_counters`) |
|
|
669
|
+
| `writeLease` | The time after which a pending number of a write that stopped no longer holds the readers (default `seconds(60)`). A plain number is a `TypeError`. |
|
|
670
|
+
| `client` | The client of the database, for the transactions. The default is the client of the default connection when `db` is absent. |
|
|
671
|
+
| `transactions` | `auto` (the default): a transaction for each write on a replica set or a sharded cluster. `never`: the watermark only. |
|
|
672
|
+
|
|
673
|
+
- **The documents.** A plain object with safe keys is stored flat: its fields beside the fields of the storage (`_key`, `_part`, `_seq`, `_ord`, `_tomb`). Another item is stored as JSON text in `_json`. Both ways give the item back as it was. An item whose `_id` is its id keeps it.
|
|
674
|
+
- **The change log.** Each write gets the next number of the sequence of the collection, and a delete leaves a tombstone. `getChangesSince()` thus gives the exact changes and deletes. A version of another epoch, or a version before the oldest dropped tombstone, gives a full result.
|
|
675
|
+
- **The versions.** `versionOf(id)` gives the number of the last write of an item.
|
|
676
|
+
- **The queries.** `query()` sends the equality of a string, a number or a boolean, and `$in` of them, to MongoDB as a filter. `queryEntries()` of the core then gives the exact result, so a query gives the result of the reference.
|
|
677
|
+
- **Several writers.** On a replica set, a write takes its numbers and writes in one transaction. On a single server, a watermark keeps the log exact. Each write keeps its numbers pending until it ends, and a version stops below the lowest pending number. A write that ends after its lease moves its documents to new numbers.
|
|
678
|
+
- **The case that stays on a single server.** A process that stops between a late write and the move of its documents leaves that change below the watermark. A reader that passed those numbers does not get the change. On a replica set, this case does not occur.
|
|
679
|
+
- **The partitions.** An id cannot have the character U+001F, and each write filter has the partition. A write cannot change a document of another partition.
|
|
680
|
+
- **The tests.** The storage passes the `CollectionStorage` contract on MongoDB 7 in CI. Set `MONGO_TEST_URL` to start them locally.
|
|
681
|
+
|
|
682
|
+
### The guard of a pipeline: `scopePipeline`
|
|
683
|
+
|
|
684
|
+
`scopePipeline(pipeline, scope)` gives a pipeline that reads only the documents of a filter, on each collection that it reads. `MongoDocumentStorage.aggregate()` uses it with the filter of its partition. An app can use it for its own aggregations over shared collections:
|
|
685
|
+
|
|
686
|
+
```ts
|
|
687
|
+
import { boundPipeline, scopePipeline } from "@procedures/mongo";
|
|
688
|
+
|
|
689
|
+
const scoped = scopePipeline(pipeline, { match: { tenant: "t1" }, collections: ["orders", "customers"] });
|
|
690
|
+
const { pipeline: bounded, limited } = boundPipeline(scoped, 5000);
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
- The filter goes before the first stage, into the stages that must be first (`$geoNear`, `$search`, `$vectorSearch`), into a `$match` with `$text`, and into each sub-pipeline (`$lookup`, `$graphLookup`, `$unionWith`, `$facet`).
|
|
694
|
+
- A `$lookup` with `localField` and `foreignField` becomes the form with a pipeline, so the filter applies to the other collection too.
|
|
695
|
+
- The pipeline reads and writes only the collections of the scope (`from`, `coll`, `into`). Give only the collections that the caller can read: a pipeline needs the same right as a direct read.
|
|
696
|
+
- `$out` is refused. `$merge` is refused unless the scope has a `stamp`. Then each written document gets the stamp, and the match key of `$merge` gets its fields.
|
|
697
|
+
- Only the stages of `ALLOWED_PIPELINE_STAGES` pass, so a stage of the server is refused, also a stage that a later MongoDB adds. `$documents` is refused. The JavaScript operators of the server (`$function`, `$accumulator`, `$where`) are refused at each depth.
|
|
698
|
+
- A stage must have exactly one key, and a `$unset` of a field of the scope is refused.
|
|
699
|
+
- The function does not change the pipeline of the caller.
|
|
700
|
+
- `MongoDocumentStorage.aggregate(pipeline, { maxTime, maxResults })` is read-only, reads only its own collection, and keeps the tombstones out. It gives `{ docs, truncated }`. The defaults are `seconds(10)` and 5000 documents. `maxTime` is a duration of `@procedures/client`.
|
|
701
|
+
|
|
1286
702
|
## Integration with Ecosystem
|
|
1287
703
|
|
|
1288
704
|
### Bundle Packages
|