@atscript/mongo 0.1.33 → 0.1.35
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/LICENSE +1 -1
- package/dist/index.cjs +162 -66
- package/dist/index.d.ts +48 -19
- package/dist/index.mjs +163 -68
- package/dist/plugin.cjs +16 -0
- package/dist/plugin.mjs +16 -0
- package/package.json +5 -5
package/LICENSE
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -26,6 +26,16 @@ const __atscript_utils_db = __toESM(require("@atscript/utils-db"));
|
|
|
26
26
|
const mongodb = __toESM(require("mongodb"));
|
|
27
27
|
const __atscript_typescript_utils = __toESM(require("@atscript/typescript/utils"));
|
|
28
28
|
|
|
29
|
+
//#region packages/mongo/src/lib/logger.ts
|
|
30
|
+
const NoopLogger = {
|
|
31
|
+
error: () => {},
|
|
32
|
+
warn: () => {},
|
|
33
|
+
log: () => {},
|
|
34
|
+
info: () => {},
|
|
35
|
+
debug: () => {}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
//#endregion
|
|
29
39
|
//#region packages/mongo/src/lib/validate-plugins.ts
|
|
30
40
|
const validateMongoIdPlugin = (ctx, def, value) => {
|
|
31
41
|
if (ctx.path === "_id" && def.type.tags.has("objectId")) return ctx.validateAnnotatedType(def, value instanceof mongodb.ObjectId ? value.toString() : value);
|
|
@@ -307,7 +317,7 @@ const mongoVisitor = {
|
|
|
307
317
|
};
|
|
308
318
|
function buildMongoFilter(filter) {
|
|
309
319
|
if (!filter || Object.keys(filter).length === 0) return EMPTY;
|
|
310
|
-
return (0, __atscript_utils_db.walkFilter)(filter, mongoVisitor);
|
|
320
|
+
return (0, __atscript_utils_db.walkFilter)(filter, mongoVisitor) ?? EMPTY;
|
|
311
321
|
}
|
|
312
322
|
|
|
313
323
|
//#endregion
|
|
@@ -328,13 +338,58 @@ function mongoIndexKey(type, name) {
|
|
|
328
338
|
const cleanName = name.replace(/[^a-z0-9_.-]/gi, "_").replace(/_+/g, "_").slice(0, 127 - INDEX_PREFIX.length - type.length - 2);
|
|
329
339
|
return `${INDEX_PREFIX}${type}__${cleanName}`;
|
|
330
340
|
}
|
|
331
|
-
var MongoAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
341
|
+
var MongoAdapter = class MongoAdapter extends __atscript_utils_db.BaseDbAdapter {
|
|
342
|
+
get _client() {
|
|
343
|
+
return this.asMongo?.client;
|
|
344
|
+
}
|
|
345
|
+
async _beginTransaction() {
|
|
346
|
+
if (this._txDisabled || !this._client) return undefined;
|
|
347
|
+
try {
|
|
348
|
+
const topology = this._client.topology;
|
|
349
|
+
if (topology) {
|
|
350
|
+
const desc = topology.description ?? topology.s?.description;
|
|
351
|
+
const type = desc?.type;
|
|
352
|
+
if (type === "Single" || type === "Unknown") {
|
|
353
|
+
this._txDisabled = true;
|
|
354
|
+
return undefined;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
const session = this._client.startSession();
|
|
358
|
+
session.startTransaction();
|
|
359
|
+
return session;
|
|
360
|
+
} catch {
|
|
361
|
+
this._txDisabled = true;
|
|
362
|
+
return undefined;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
async _commitTransaction(state) {
|
|
366
|
+
if (!state) return;
|
|
367
|
+
const session = state;
|
|
368
|
+
try {
|
|
369
|
+
await session.commitTransaction();
|
|
370
|
+
} finally {
|
|
371
|
+
session.endSession();
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
async _rollbackTransaction(state) {
|
|
375
|
+
if (!state) return;
|
|
376
|
+
const session = state;
|
|
377
|
+
try {
|
|
378
|
+
await session.abortTransaction();
|
|
379
|
+
} finally {
|
|
380
|
+
session.endSession();
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
/** Returns `{ session }` opts if inside a transaction, empty object otherwise. */ _getSessionOpts() {
|
|
384
|
+
const session = this._getTransactionState();
|
|
385
|
+
return session ? { session } : MongoAdapter._noSession;
|
|
386
|
+
}
|
|
332
387
|
get collection() {
|
|
333
388
|
if (!this._collection) this._collection = this.db.collection(this.resolveTableName(false));
|
|
334
389
|
return this._collection;
|
|
335
390
|
}
|
|
336
391
|
aggregate(pipeline) {
|
|
337
|
-
return this.collection.aggregate(pipeline);
|
|
392
|
+
return this.collection.aggregate(pipeline, this._getSessionOpts());
|
|
338
393
|
}
|
|
339
394
|
get idType() {
|
|
340
395
|
const idProp = this._table.type.type.props.get("_id");
|
|
@@ -408,7 +463,11 @@ var MongoAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
408
463
|
const mongoFilter = buildMongoFilter(filter);
|
|
409
464
|
const patcher = new CollectionPatcher(this.getPatcherContext(), patch);
|
|
410
465
|
const { updateFilter, updateOptions } = patcher.preparePatch();
|
|
411
|
-
|
|
466
|
+
this._log("updateOne (patch)", mongoFilter, updateFilter);
|
|
467
|
+
const result = await this.collection.updateOne(mongoFilter, updateFilter, {
|
|
468
|
+
...updateOptions,
|
|
469
|
+
...this._getSessionOpts()
|
|
470
|
+
});
|
|
412
471
|
return {
|
|
413
472
|
matchedCount: result.matchedCount,
|
|
414
473
|
modifiedCount: result.modifiedCount
|
|
@@ -416,6 +475,11 @@ var MongoAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
416
475
|
}
|
|
417
476
|
onBeforeFlatten(type) {
|
|
418
477
|
const typeMeta = type.metadata;
|
|
478
|
+
const capped = typeMeta.get("db.mongo.capped");
|
|
479
|
+
if (capped) this._cappedOptions = {
|
|
480
|
+
size: capped.size,
|
|
481
|
+
max: capped.max
|
|
482
|
+
};
|
|
419
483
|
const dynamicText = typeMeta.get("db.mongo.search.dynamic");
|
|
420
484
|
if (dynamicText) this._setSearchIndex("dynamic_text", "_", {
|
|
421
485
|
mappings: { dynamic: true },
|
|
@@ -542,7 +606,8 @@ var MongoAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
542
606
|
if (controls.$limit) pipeline.push({ $limit: controls.$limit });
|
|
543
607
|
else pipeline.push({ $limit: 1e3 });
|
|
544
608
|
if (controls.$select) pipeline.push({ $project: controls.$select.asProjection });
|
|
545
|
-
|
|
609
|
+
this._log("aggregate (search)", pipeline);
|
|
610
|
+
return this.collection.aggregate(pipeline, this._getSessionOpts()).toArray();
|
|
546
611
|
}
|
|
547
612
|
async searchWithCount(text, query, indexName) {
|
|
548
613
|
const searchStage = this.buildSearchStage(text, indexName);
|
|
@@ -562,7 +627,8 @@ else pipeline.push({ $limit: 1e3 });
|
|
|
562
627
|
meta: [{ $count: "count" }]
|
|
563
628
|
} }
|
|
564
629
|
];
|
|
565
|
-
|
|
630
|
+
this._log("aggregate (searchWithCount)", pipeline);
|
|
631
|
+
const result = await this.collection.aggregate(pipeline, this._getSessionOpts()).toArray();
|
|
566
632
|
return {
|
|
567
633
|
data: result[0]?.data || [],
|
|
568
634
|
count: result[0]?.meta[0]?.count || 0
|
|
@@ -580,7 +646,8 @@ else pipeline.push({ $limit: 1e3 });
|
|
|
580
646
|
].filter(Boolean),
|
|
581
647
|
meta: [{ $count: "count" }]
|
|
582
648
|
} }];
|
|
583
|
-
|
|
649
|
+
this._log("aggregate (findManyWithCount)", pipeline);
|
|
650
|
+
const result = await this.collection.aggregate(pipeline, this._getSessionOpts()).toArray();
|
|
584
651
|
return {
|
|
585
652
|
data: result[0]?.data || [],
|
|
586
653
|
count: result[0]?.meta[0]?.count || 0
|
|
@@ -593,7 +660,16 @@ else pipeline.push({ $limit: 1e3 });
|
|
|
593
660
|
}
|
|
594
661
|
async ensureCollectionExists() {
|
|
595
662
|
const exists = await this.collectionExists();
|
|
596
|
-
if (!exists)
|
|
663
|
+
if (!exists) {
|
|
664
|
+
this._log("createCollection", this._table.tableName);
|
|
665
|
+
const opts = { comment: "Created by Atscript Mongo Adapter" };
|
|
666
|
+
if (this._cappedOptions) {
|
|
667
|
+
opts.capped = true;
|
|
668
|
+
opts.size = this._cappedOptions.size;
|
|
669
|
+
if (this._cappedOptions.max !== null && this._cappedOptions.max !== undefined) opts.max = this._cappedOptions.max;
|
|
670
|
+
}
|
|
671
|
+
await this.db.createCollection(this._table.tableName, opts);
|
|
672
|
+
}
|
|
597
673
|
}
|
|
598
674
|
async insertOne(data) {
|
|
599
675
|
if (this._incrementFields.size > 0) {
|
|
@@ -603,7 +679,8 @@ else pipeline.push({ $limit: 1e3 });
|
|
|
603
679
|
for (const physical of fields) data[physical] = (maxValues.get(physical) ?? 0) + 1;
|
|
604
680
|
}
|
|
605
681
|
}
|
|
606
|
-
|
|
682
|
+
this._log("insertOne", data);
|
|
683
|
+
const result = await this.collection.insertOne(data, this._getSessionOpts());
|
|
607
684
|
return { insertedId: result.insertedId };
|
|
608
685
|
}
|
|
609
686
|
async insertMany(data) {
|
|
@@ -622,7 +699,8 @@ else pipeline.push({ $limit: 1e3 });
|
|
|
622
699
|
}
|
|
623
700
|
}
|
|
624
701
|
}
|
|
625
|
-
|
|
702
|
+
this._log("insertMany", `${data.length} docs`);
|
|
703
|
+
const result = await this.collection.insertMany(data, this._getSessionOpts());
|
|
626
704
|
return {
|
|
627
705
|
insertedCount: result.insertedCount,
|
|
628
706
|
insertedIds: Object.values(result.insertedIds)
|
|
@@ -631,20 +709,30 @@ else pipeline.push({ $limit: 1e3 });
|
|
|
631
709
|
async findOne(query) {
|
|
632
710
|
const filter = buildMongoFilter(query.filter);
|
|
633
711
|
const opts = this._buildFindOptions(query.controls);
|
|
634
|
-
|
|
712
|
+
this._log("findOne", filter, opts);
|
|
713
|
+
return this.collection.findOne(filter, {
|
|
714
|
+
...opts,
|
|
715
|
+
...this._getSessionOpts()
|
|
716
|
+
});
|
|
635
717
|
}
|
|
636
718
|
async findMany(query) {
|
|
637
719
|
const filter = buildMongoFilter(query.filter);
|
|
638
720
|
const opts = this._buildFindOptions(query.controls);
|
|
639
|
-
|
|
721
|
+
this._log("findMany", filter, opts);
|
|
722
|
+
return this.collection.find(filter, {
|
|
723
|
+
...opts,
|
|
724
|
+
...this._getSessionOpts()
|
|
725
|
+
}).toArray();
|
|
640
726
|
}
|
|
641
727
|
async count(query) {
|
|
642
728
|
const filter = buildMongoFilter(query.filter);
|
|
643
|
-
|
|
729
|
+
this._log("countDocuments", filter);
|
|
730
|
+
return this.collection.countDocuments(filter, this._getSessionOpts());
|
|
644
731
|
}
|
|
645
732
|
async updateOne(filter, data) {
|
|
646
733
|
const mongoFilter = buildMongoFilter(filter);
|
|
647
|
-
|
|
734
|
+
this._log("updateOne", mongoFilter, { $set: data });
|
|
735
|
+
const result = await this.collection.updateOne(mongoFilter, { $set: data }, this._getSessionOpts());
|
|
648
736
|
return {
|
|
649
737
|
matchedCount: result.matchedCount,
|
|
650
738
|
modifiedCount: result.modifiedCount
|
|
@@ -652,7 +740,8 @@ else pipeline.push({ $limit: 1e3 });
|
|
|
652
740
|
}
|
|
653
741
|
async replaceOne(filter, data) {
|
|
654
742
|
const mongoFilter = buildMongoFilter(filter);
|
|
655
|
-
|
|
743
|
+
this._log("replaceOne", mongoFilter, data);
|
|
744
|
+
const result = await this.collection.replaceOne(mongoFilter, data, this._getSessionOpts());
|
|
656
745
|
return {
|
|
657
746
|
matchedCount: result.matchedCount,
|
|
658
747
|
modifiedCount: result.modifiedCount
|
|
@@ -660,12 +749,14 @@ else pipeline.push({ $limit: 1e3 });
|
|
|
660
749
|
}
|
|
661
750
|
async deleteOne(filter) {
|
|
662
751
|
const mongoFilter = buildMongoFilter(filter);
|
|
663
|
-
|
|
752
|
+
this._log("deleteOne", mongoFilter);
|
|
753
|
+
const result = await this.collection.deleteOne(mongoFilter, this._getSessionOpts());
|
|
664
754
|
return { deletedCount: result.deletedCount };
|
|
665
755
|
}
|
|
666
756
|
async updateMany(filter, data) {
|
|
667
757
|
const mongoFilter = buildMongoFilter(filter);
|
|
668
|
-
|
|
758
|
+
this._log("updateMany", mongoFilter, { $set: data });
|
|
759
|
+
const result = await this.collection.updateMany(mongoFilter, { $set: data }, this._getSessionOpts());
|
|
669
760
|
return {
|
|
670
761
|
matchedCount: result.matchedCount,
|
|
671
762
|
modifiedCount: result.modifiedCount
|
|
@@ -673,7 +764,8 @@ else pipeline.push({ $limit: 1e3 });
|
|
|
673
764
|
}
|
|
674
765
|
async replaceMany(filter, data) {
|
|
675
766
|
const mongoFilter = buildMongoFilter(filter);
|
|
676
|
-
|
|
767
|
+
this._log("replaceMany", mongoFilter, { $set: data });
|
|
768
|
+
const result = await this.collection.updateMany(mongoFilter, { $set: data }, this._getSessionOpts());
|
|
677
769
|
return {
|
|
678
770
|
matchedCount: result.matchedCount,
|
|
679
771
|
modifiedCount: result.modifiedCount
|
|
@@ -681,12 +773,18 @@ else pipeline.push({ $limit: 1e3 });
|
|
|
681
773
|
}
|
|
682
774
|
async deleteMany(filter) {
|
|
683
775
|
const mongoFilter = buildMongoFilter(filter);
|
|
684
|
-
|
|
776
|
+
this._log("deleteMany", mongoFilter);
|
|
777
|
+
const result = await this.collection.deleteMany(mongoFilter, this._getSessionOpts());
|
|
685
778
|
return { deletedCount: result.deletedCount };
|
|
686
779
|
}
|
|
687
780
|
async ensureTable() {
|
|
688
781
|
return this.ensureCollectionExists();
|
|
689
782
|
}
|
|
783
|
+
async dropTable() {
|
|
784
|
+
this._log("drop", this._table.tableName);
|
|
785
|
+
await this.collection.drop();
|
|
786
|
+
this._collection = undefined;
|
|
787
|
+
}
|
|
690
788
|
async syncIndexes() {
|
|
691
789
|
await this.ensureCollectionExists();
|
|
692
790
|
const allIndexes = new Map();
|
|
@@ -730,21 +828,29 @@ else pipeline.push({ $limit: 1e3 });
|
|
|
730
828
|
case "unique":
|
|
731
829
|
case "text": {
|
|
732
830
|
if ((local.type === "text" || objMatch(local.fields, remote.key)) && objMatch(local.weights || {}, remote.weights || {})) indexesToCreate.delete(remote.name);
|
|
733
|
-
else
|
|
831
|
+
else {
|
|
832
|
+
this._log("dropIndex", remote.name);
|
|
833
|
+
await this.collection.dropIndex(remote.name);
|
|
834
|
+
}
|
|
734
835
|
break;
|
|
735
836
|
}
|
|
736
837
|
default:
|
|
737
838
|
}
|
|
738
|
-
} else
|
|
839
|
+
} else {
|
|
840
|
+
this._log("dropIndex", remote.name);
|
|
841
|
+
await this.collection.dropIndex(remote.name);
|
|
842
|
+
}
|
|
739
843
|
}
|
|
740
844
|
for (const [key, value] of allIndexes.entries()) switch (value.type) {
|
|
741
845
|
case "plain": {
|
|
742
846
|
if (!indexesToCreate.has(key)) continue;
|
|
847
|
+
this._log("createIndex", key, value.fields);
|
|
743
848
|
await this.collection.createIndex(value.fields, { name: key });
|
|
744
849
|
break;
|
|
745
850
|
}
|
|
746
851
|
case "unique": {
|
|
747
852
|
if (!indexesToCreate.has(key)) continue;
|
|
853
|
+
this._log("createIndex (unique)", key, value.fields);
|
|
748
854
|
await this.collection.createIndex(value.fields, {
|
|
749
855
|
name: key,
|
|
750
856
|
unique: true
|
|
@@ -753,6 +859,7 @@ else await this.collection.dropIndex(remote.name);
|
|
|
753
859
|
}
|
|
754
860
|
case "text": {
|
|
755
861
|
if (!indexesToCreate.has(key)) continue;
|
|
862
|
+
this._log("createIndex (text)", key, value.fields);
|
|
756
863
|
await this.collection.createIndex(value.fields, {
|
|
757
864
|
weights: value.weights,
|
|
758
865
|
name: key
|
|
@@ -784,18 +891,26 @@ else toUpdate.add(remote.name);
|
|
|
784
891
|
}
|
|
785
892
|
default:
|
|
786
893
|
}
|
|
787
|
-
} else if (remote.status !== "DELETING")
|
|
894
|
+
} else if (remote.status !== "DELETING") {
|
|
895
|
+
this._log("dropSearchIndex", remote.name);
|
|
896
|
+
await this.collection.dropSearchIndex(remote.name);
|
|
897
|
+
}
|
|
788
898
|
}
|
|
789
899
|
for (const [key, value] of indexesToCreate.entries()) switch (value.type) {
|
|
790
900
|
case "dynamic_text":
|
|
791
901
|
case "search_text":
|
|
792
902
|
case "vector": {
|
|
793
|
-
if (toUpdate.has(key))
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
903
|
+
if (toUpdate.has(key)) {
|
|
904
|
+
this._log("updateSearchIndex", key, value.definition);
|
|
905
|
+
await this.collection.updateSearchIndex(key, value.definition);
|
|
906
|
+
} else {
|
|
907
|
+
this._log("createSearchIndex", key, value.type);
|
|
908
|
+
await this.collection.createSearchIndex({
|
|
909
|
+
name: key,
|
|
910
|
+
type: value.type === "vector" ? "vectorSearch" : "search",
|
|
911
|
+
definition: value.definition
|
|
912
|
+
});
|
|
913
|
+
}
|
|
799
914
|
break;
|
|
800
915
|
}
|
|
801
916
|
default:
|
|
@@ -811,7 +926,7 @@ else await this.collection.createSearchIndex({
|
|
|
811
926
|
const aliases = physicalFields.map((f) => [`max__${f.replace(/\./g, "__")}`, f]);
|
|
812
927
|
const group = { _id: null };
|
|
813
928
|
for (const [alias, field] of aliases) group[alias] = { $max: `$${field}` };
|
|
814
|
-
const result = await this.collection.aggregate([{ $group: group }]).toArray();
|
|
929
|
+
const result = await this.collection.aggregate([{ $group: group }], this._getSessionOpts()).toArray();
|
|
815
930
|
const maxMap = new Map();
|
|
816
931
|
if (result.length > 0) {
|
|
817
932
|
const row = result[0];
|
|
@@ -873,9 +988,10 @@ else {
|
|
|
873
988
|
}
|
|
874
989
|
}
|
|
875
990
|
constructor(db, asMongo) {
|
|
876
|
-
super(), _define_property$1(this, "db", void 0), _define_property$1(this, "asMongo", void 0), _define_property$1(this, "_collection", void 0), _define_property$1(this, "_mongoIndexes", void 0), _define_property$1(this, "_vectorFilters", void 0), _define_property$1(this, "_searchIndexesMap", void 0), _define_property$1(this, "_incrementFields", void 0), this.db = db, this.asMongo = asMongo, this._mongoIndexes = new Map(), this._vectorFilters = new Map(), this._incrementFields = new Set();
|
|
991
|
+
super(), _define_property$1(this, "db", void 0), _define_property$1(this, "asMongo", void 0), _define_property$1(this, "_collection", void 0), _define_property$1(this, "_mongoIndexes", void 0), _define_property$1(this, "_vectorFilters", void 0), _define_property$1(this, "_searchIndexesMap", void 0), _define_property$1(this, "_incrementFields", void 0), _define_property$1(this, "_cappedOptions", void 0), _define_property$1(this, "_txDisabled", void 0), this.db = db, this.asMongo = asMongo, this._mongoIndexes = new Map(), this._vectorFilters = new Map(), this._incrementFields = new Set(), this._txDisabled = false;
|
|
877
992
|
}
|
|
878
993
|
};
|
|
994
|
+
_define_property$1(MongoAdapter, "_noSession", Object.freeze({}));
|
|
879
995
|
function objMatch(o1, o2) {
|
|
880
996
|
const keys1 = Object.keys(o1);
|
|
881
997
|
const keys2 = Object.keys(o2);
|
|
@@ -904,16 +1020,6 @@ function vectorFieldsMatch(left, right) {
|
|
|
904
1020
|
return true;
|
|
905
1021
|
}
|
|
906
1022
|
|
|
907
|
-
//#endregion
|
|
908
|
-
//#region packages/mongo/src/lib/logger.ts
|
|
909
|
-
const NoopLogger = {
|
|
910
|
-
error: () => {},
|
|
911
|
-
warn: () => {},
|
|
912
|
-
log: () => {},
|
|
913
|
-
info: () => {},
|
|
914
|
-
debug: () => {}
|
|
915
|
-
};
|
|
916
|
-
|
|
917
1023
|
//#endregion
|
|
918
1024
|
//#region packages/mongo/src/lib/as-mongo.ts
|
|
919
1025
|
function _define_property(obj, key, value) {
|
|
@@ -926,7 +1032,7 @@ function _define_property(obj, key, value) {
|
|
|
926
1032
|
else obj[key] = value;
|
|
927
1033
|
return obj;
|
|
928
1034
|
}
|
|
929
|
-
var AsMongo = class {
|
|
1035
|
+
var AsMongo = class extends __atscript_utils_db.DbSpace {
|
|
930
1036
|
get db() {
|
|
931
1037
|
return this.client.db();
|
|
932
1038
|
}
|
|
@@ -938,39 +1044,29 @@ var AsMongo = class {
|
|
|
938
1044
|
const list = await this.getCollectionsList();
|
|
939
1045
|
return list.has(name);
|
|
940
1046
|
}
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
this._ensureCreated(type, logger);
|
|
947
|
-
return this._tables.get(type);
|
|
948
|
-
}
|
|
949
|
-
_ensureCreated(type, logger) {
|
|
950
|
-
if (!this._adapters.has(type)) {
|
|
951
|
-
const adapter = new MongoAdapter(this.db, this);
|
|
952
|
-
const table = new __atscript_utils_db.AtscriptDbTable(type, adapter, logger || this.logger);
|
|
953
|
-
this._adapters.set(type, adapter);
|
|
954
|
-
this._tables.set(type, table);
|
|
955
|
-
}
|
|
1047
|
+
/**
|
|
1048
|
+
* Returns the MongoAdapter for the given type.
|
|
1049
|
+
* Convenience accessor for Mongo-specific adapter operations.
|
|
1050
|
+
*/ getAdapter(type) {
|
|
1051
|
+
return super.getAdapter(type);
|
|
956
1052
|
}
|
|
957
1053
|
constructor(client, logger = NoopLogger) {
|
|
958
|
-
|
|
959
|
-
_define_property(this, "client", void 0);
|
|
960
|
-
|
|
961
|
-
_define_property(this, "_adapters", void 0);
|
|
962
|
-
_define_property(this, "_tables", void 0);
|
|
963
|
-
this.logger = logger;
|
|
964
|
-
this._adapters = new WeakMap();
|
|
965
|
-
this._tables = new WeakMap();
|
|
966
|
-
if (typeof client === "string") this.client = new mongodb.MongoClient(client);
|
|
967
|
-
else this.client = client;
|
|
1054
|
+
const resolvedClient = typeof client === "string" ? new mongodb.MongoClient(client) : client;
|
|
1055
|
+
super(() => new MongoAdapter(this.db, this), logger), _define_property(this, "client", void 0), _define_property(this, "collectionsList", void 0);
|
|
1056
|
+
this.client = resolvedClient;
|
|
968
1057
|
}
|
|
969
1058
|
};
|
|
970
1059
|
|
|
1060
|
+
//#endregion
|
|
1061
|
+
//#region packages/mongo/src/lib/index.ts
|
|
1062
|
+
function createAdapter(connection, _options) {
|
|
1063
|
+
return new AsMongo(connection);
|
|
1064
|
+
}
|
|
1065
|
+
|
|
971
1066
|
//#endregion
|
|
972
1067
|
exports.AsMongo = AsMongo
|
|
973
1068
|
exports.CollectionPatcher = CollectionPatcher
|
|
974
1069
|
exports.MongoAdapter = MongoAdapter
|
|
975
1070
|
exports.buildMongoFilter = buildMongoFilter
|
|
1071
|
+
exports.createAdapter = createAdapter
|
|
976
1072
|
exports.validateMongoIdPlugin = validateMongoIdPlugin
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { TAtscriptAnnotatedType, TValidatorOptions, Validator, TValidatorPlugin, TMetadataMap } from '@atscript/typescript/utils';
|
|
2
|
-
import { AtscriptDbTable, getKeyProps, BaseDbAdapter, FilterExpr, TDbUpdateResult, TSearchIndexInfo, DbQuery, TDbInsertResult, TDbInsertManyResult, TDbDeleteResult } from '@atscript/utils-db';
|
|
3
1
|
import * as mongodb from 'mongodb';
|
|
4
|
-
import {
|
|
2
|
+
import { Filter, UpdateFilter, Document, UpdateOptions, Db, ClientSession, Collection, AggregationCursor, ObjectId, MongoClient } from 'mongodb';
|
|
3
|
+
import { TAtscriptAnnotatedType, TValidatorOptions, Validator, TValidatorPlugin, TMetadataMap } from '@atscript/typescript/utils';
|
|
4
|
+
import { getKeyProps, BaseDbAdapter, AtscriptDbTable, FilterExpr, TDbUpdateResult, TSearchIndexInfo, DbQuery, TDbInsertResult, TDbInsertManyResult, TDbDeleteResult, DbSpace } from '@atscript/utils-db';
|
|
5
5
|
|
|
6
6
|
interface TGenericLogger {
|
|
7
7
|
error(...messages: any[]): void;
|
|
@@ -11,21 +11,6 @@ interface TGenericLogger {
|
|
|
11
11
|
debug(...messages: any[]): void;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
declare class AsMongo {
|
|
15
|
-
protected readonly logger: TGenericLogger;
|
|
16
|
-
readonly client: MongoClient;
|
|
17
|
-
constructor(client: string | MongoClient, logger?: TGenericLogger);
|
|
18
|
-
get db(): mongodb.Db;
|
|
19
|
-
protected collectionsList?: Promise<Set<string>>;
|
|
20
|
-
protected getCollectionsList(): Promise<Set<string>>;
|
|
21
|
-
collectionExists(name: string): Promise<boolean>;
|
|
22
|
-
getAdapter<T extends TAtscriptAnnotatedType>(type: T): MongoAdapter;
|
|
23
|
-
getTable<T extends TAtscriptAnnotatedType>(type: T, logger?: TGenericLogger): AtscriptDbTable<T, any, any, any>;
|
|
24
|
-
private _ensureCreated;
|
|
25
|
-
private _adapters;
|
|
26
|
-
private _tables;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
14
|
/**
|
|
30
15
|
* Context interface for CollectionPatcher.
|
|
31
16
|
* Decouples the patcher from AsCollection, allowing MongoAdapter to provide this.
|
|
@@ -192,7 +177,23 @@ declare class MongoAdapter extends BaseDbAdapter {
|
|
|
192
177
|
protected _searchIndexesMap?: Map<string, TMongoIndex>;
|
|
193
178
|
/** Physical field names with @db.default.fn "increment". */
|
|
194
179
|
protected _incrementFields: Set<string>;
|
|
180
|
+
/** Capped collection options from @db.mongo.capped. */
|
|
181
|
+
protected _cappedOptions?: {
|
|
182
|
+
size: number;
|
|
183
|
+
max?: number;
|
|
184
|
+
};
|
|
195
185
|
constructor(db: Db, asMongo?: AsMongo | undefined);
|
|
186
|
+
private get _client();
|
|
187
|
+
/** Whether transaction support has been detected as unavailable (standalone MongoDB). */
|
|
188
|
+
private _txDisabled;
|
|
189
|
+
protected _beginTransaction(): Promise<unknown>;
|
|
190
|
+
protected _commitTransaction(state: unknown): Promise<void>;
|
|
191
|
+
protected _rollbackTransaction(state: unknown): Promise<void>;
|
|
192
|
+
private static readonly _noSession;
|
|
193
|
+
/** Returns `{ session }` opts if inside a transaction, empty object otherwise. */
|
|
194
|
+
protected _getSessionOpts(): {
|
|
195
|
+
session: ClientSession;
|
|
196
|
+
} | Record<string, never>;
|
|
196
197
|
get collection(): Collection<any>;
|
|
197
198
|
aggregate(pipeline: Document[]): AggregationCursor;
|
|
198
199
|
get idType(): 'string' | 'number' | 'objectId';
|
|
@@ -254,6 +255,7 @@ declare class MongoAdapter extends BaseDbAdapter {
|
|
|
254
255
|
replaceMany(filter: FilterExpr, data: Record<string, unknown>): Promise<TDbUpdateResult>;
|
|
255
256
|
deleteMany(filter: FilterExpr): Promise<TDbDeleteResult>;
|
|
256
257
|
ensureTable(): Promise<void>;
|
|
258
|
+
dropTable(): Promise<void>;
|
|
257
259
|
syncIndexes(): Promise<void>;
|
|
258
260
|
/** Returns physical field names of increment fields that are undefined in the data. */
|
|
259
261
|
private _fieldsNeedingIncrement;
|
|
@@ -287,6 +289,31 @@ interface TMongoSearchIndexDefinition {
|
|
|
287
289
|
};
|
|
288
290
|
}
|
|
289
291
|
|
|
292
|
+
/**
|
|
293
|
+
* MongoDB database space — extends {@link DbSpace} with MongoDB-specific
|
|
294
|
+
* features (cached collection list, `Db` access, `MongoAdapter` factory).
|
|
295
|
+
*
|
|
296
|
+
* ```typescript
|
|
297
|
+
* const asMongo = new AsMongo('mongodb://localhost:27017/mydb')
|
|
298
|
+
* const users = asMongo.getTable(UsersType)
|
|
299
|
+
* const posts = asMongo.getTable(PostsType)
|
|
300
|
+
* // Relation loading via $with works automatically
|
|
301
|
+
* ```
|
|
302
|
+
*/
|
|
303
|
+
declare class AsMongo extends DbSpace {
|
|
304
|
+
readonly client: MongoClient;
|
|
305
|
+
constructor(client: string | MongoClient, logger?: TGenericLogger);
|
|
306
|
+
get db(): mongodb.Db;
|
|
307
|
+
protected collectionsList?: Promise<Set<string>>;
|
|
308
|
+
protected getCollectionsList(): Promise<Set<string>>;
|
|
309
|
+
collectionExists(name: string): Promise<boolean>;
|
|
310
|
+
/**
|
|
311
|
+
* Returns the MongoAdapter for the given type.
|
|
312
|
+
* Convenience accessor for Mongo-specific adapter operations.
|
|
313
|
+
*/
|
|
314
|
+
getAdapter(type: TAtscriptAnnotatedType): MongoAdapter;
|
|
315
|
+
}
|
|
316
|
+
|
|
290
317
|
/**
|
|
291
318
|
* Translates a generic {@link FilterExpr} into a MongoDB-compatible
|
|
292
319
|
* {@link Filter} document.
|
|
@@ -298,5 +325,7 @@ declare function buildMongoFilter(filter: FilterExpr): Filter<any>;
|
|
|
298
325
|
|
|
299
326
|
declare const validateMongoIdPlugin: TValidatorPlugin;
|
|
300
327
|
|
|
301
|
-
|
|
328
|
+
declare function createAdapter(connection: string, _options?: Record<string, unknown>): AsMongo;
|
|
329
|
+
|
|
330
|
+
export { AsMongo, CollectionPatcher, MongoAdapter, buildMongoFilter, createAdapter, validateMongoIdPlugin };
|
|
302
331
|
export type { TCollectionPatcherContext, TMongoIndex, TMongoSearchIndexDefinition, TPlainIndex, TSearchIndex };
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BaseDbAdapter, DbSpace, getKeyProps, walkFilter } from "@atscript/utils-db";
|
|
2
2
|
import { MongoClient, ObjectId } from "mongodb";
|
|
3
3
|
import { defineAnnotatedType, isAnnotatedTypeOfPrimitive } from "@atscript/typescript/utils";
|
|
4
4
|
|
|
5
|
+
//#region packages/mongo/src/lib/logger.ts
|
|
6
|
+
const NoopLogger = {
|
|
7
|
+
error: () => {},
|
|
8
|
+
warn: () => {},
|
|
9
|
+
log: () => {},
|
|
10
|
+
info: () => {},
|
|
11
|
+
debug: () => {}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
//#endregion
|
|
5
15
|
//#region packages/mongo/src/lib/validate-plugins.ts
|
|
6
16
|
const validateMongoIdPlugin = (ctx, def, value) => {
|
|
7
17
|
if (ctx.path === "_id" && def.type.tags.has("objectId")) return ctx.validateAnnotatedType(def, value instanceof ObjectId ? value.toString() : value);
|
|
@@ -283,7 +293,7 @@ const mongoVisitor = {
|
|
|
283
293
|
};
|
|
284
294
|
function buildMongoFilter(filter) {
|
|
285
295
|
if (!filter || Object.keys(filter).length === 0) return EMPTY;
|
|
286
|
-
return walkFilter(filter, mongoVisitor);
|
|
296
|
+
return walkFilter(filter, mongoVisitor) ?? EMPTY;
|
|
287
297
|
}
|
|
288
298
|
|
|
289
299
|
//#endregion
|
|
@@ -304,13 +314,58 @@ function mongoIndexKey(type, name) {
|
|
|
304
314
|
const cleanName = name.replace(/[^a-z0-9_.-]/gi, "_").replace(/_+/g, "_").slice(0, 127 - INDEX_PREFIX.length - type.length - 2);
|
|
305
315
|
return `${INDEX_PREFIX}${type}__${cleanName}`;
|
|
306
316
|
}
|
|
307
|
-
var MongoAdapter = class extends BaseDbAdapter {
|
|
317
|
+
var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
|
|
318
|
+
get _client() {
|
|
319
|
+
return this.asMongo?.client;
|
|
320
|
+
}
|
|
321
|
+
async _beginTransaction() {
|
|
322
|
+
if (this._txDisabled || !this._client) return undefined;
|
|
323
|
+
try {
|
|
324
|
+
const topology = this._client.topology;
|
|
325
|
+
if (topology) {
|
|
326
|
+
const desc = topology.description ?? topology.s?.description;
|
|
327
|
+
const type = desc?.type;
|
|
328
|
+
if (type === "Single" || type === "Unknown") {
|
|
329
|
+
this._txDisabled = true;
|
|
330
|
+
return undefined;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const session = this._client.startSession();
|
|
334
|
+
session.startTransaction();
|
|
335
|
+
return session;
|
|
336
|
+
} catch {
|
|
337
|
+
this._txDisabled = true;
|
|
338
|
+
return undefined;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
async _commitTransaction(state) {
|
|
342
|
+
if (!state) return;
|
|
343
|
+
const session = state;
|
|
344
|
+
try {
|
|
345
|
+
await session.commitTransaction();
|
|
346
|
+
} finally {
|
|
347
|
+
session.endSession();
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
async _rollbackTransaction(state) {
|
|
351
|
+
if (!state) return;
|
|
352
|
+
const session = state;
|
|
353
|
+
try {
|
|
354
|
+
await session.abortTransaction();
|
|
355
|
+
} finally {
|
|
356
|
+
session.endSession();
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
/** Returns `{ session }` opts if inside a transaction, empty object otherwise. */ _getSessionOpts() {
|
|
360
|
+
const session = this._getTransactionState();
|
|
361
|
+
return session ? { session } : MongoAdapter._noSession;
|
|
362
|
+
}
|
|
308
363
|
get collection() {
|
|
309
364
|
if (!this._collection) this._collection = this.db.collection(this.resolveTableName(false));
|
|
310
365
|
return this._collection;
|
|
311
366
|
}
|
|
312
367
|
aggregate(pipeline) {
|
|
313
|
-
return this.collection.aggregate(pipeline);
|
|
368
|
+
return this.collection.aggregate(pipeline, this._getSessionOpts());
|
|
314
369
|
}
|
|
315
370
|
get idType() {
|
|
316
371
|
const idProp = this._table.type.type.props.get("_id");
|
|
@@ -384,7 +439,11 @@ var MongoAdapter = class extends BaseDbAdapter {
|
|
|
384
439
|
const mongoFilter = buildMongoFilter(filter);
|
|
385
440
|
const patcher = new CollectionPatcher(this.getPatcherContext(), patch);
|
|
386
441
|
const { updateFilter, updateOptions } = patcher.preparePatch();
|
|
387
|
-
|
|
442
|
+
this._log("updateOne (patch)", mongoFilter, updateFilter);
|
|
443
|
+
const result = await this.collection.updateOne(mongoFilter, updateFilter, {
|
|
444
|
+
...updateOptions,
|
|
445
|
+
...this._getSessionOpts()
|
|
446
|
+
});
|
|
388
447
|
return {
|
|
389
448
|
matchedCount: result.matchedCount,
|
|
390
449
|
modifiedCount: result.modifiedCount
|
|
@@ -392,6 +451,11 @@ var MongoAdapter = class extends BaseDbAdapter {
|
|
|
392
451
|
}
|
|
393
452
|
onBeforeFlatten(type) {
|
|
394
453
|
const typeMeta = type.metadata;
|
|
454
|
+
const capped = typeMeta.get("db.mongo.capped");
|
|
455
|
+
if (capped) this._cappedOptions = {
|
|
456
|
+
size: capped.size,
|
|
457
|
+
max: capped.max
|
|
458
|
+
};
|
|
395
459
|
const dynamicText = typeMeta.get("db.mongo.search.dynamic");
|
|
396
460
|
if (dynamicText) this._setSearchIndex("dynamic_text", "_", {
|
|
397
461
|
mappings: { dynamic: true },
|
|
@@ -518,7 +582,8 @@ var MongoAdapter = class extends BaseDbAdapter {
|
|
|
518
582
|
if (controls.$limit) pipeline.push({ $limit: controls.$limit });
|
|
519
583
|
else pipeline.push({ $limit: 1e3 });
|
|
520
584
|
if (controls.$select) pipeline.push({ $project: controls.$select.asProjection });
|
|
521
|
-
|
|
585
|
+
this._log("aggregate (search)", pipeline);
|
|
586
|
+
return this.collection.aggregate(pipeline, this._getSessionOpts()).toArray();
|
|
522
587
|
}
|
|
523
588
|
async searchWithCount(text, query, indexName) {
|
|
524
589
|
const searchStage = this.buildSearchStage(text, indexName);
|
|
@@ -538,7 +603,8 @@ else pipeline.push({ $limit: 1e3 });
|
|
|
538
603
|
meta: [{ $count: "count" }]
|
|
539
604
|
} }
|
|
540
605
|
];
|
|
541
|
-
|
|
606
|
+
this._log("aggregate (searchWithCount)", pipeline);
|
|
607
|
+
const result = await this.collection.aggregate(pipeline, this._getSessionOpts()).toArray();
|
|
542
608
|
return {
|
|
543
609
|
data: result[0]?.data || [],
|
|
544
610
|
count: result[0]?.meta[0]?.count || 0
|
|
@@ -556,7 +622,8 @@ else pipeline.push({ $limit: 1e3 });
|
|
|
556
622
|
].filter(Boolean),
|
|
557
623
|
meta: [{ $count: "count" }]
|
|
558
624
|
} }];
|
|
559
|
-
|
|
625
|
+
this._log("aggregate (findManyWithCount)", pipeline);
|
|
626
|
+
const result = await this.collection.aggregate(pipeline, this._getSessionOpts()).toArray();
|
|
560
627
|
return {
|
|
561
628
|
data: result[0]?.data || [],
|
|
562
629
|
count: result[0]?.meta[0]?.count || 0
|
|
@@ -569,7 +636,16 @@ else pipeline.push({ $limit: 1e3 });
|
|
|
569
636
|
}
|
|
570
637
|
async ensureCollectionExists() {
|
|
571
638
|
const exists = await this.collectionExists();
|
|
572
|
-
if (!exists)
|
|
639
|
+
if (!exists) {
|
|
640
|
+
this._log("createCollection", this._table.tableName);
|
|
641
|
+
const opts = { comment: "Created by Atscript Mongo Adapter" };
|
|
642
|
+
if (this._cappedOptions) {
|
|
643
|
+
opts.capped = true;
|
|
644
|
+
opts.size = this._cappedOptions.size;
|
|
645
|
+
if (this._cappedOptions.max !== null && this._cappedOptions.max !== undefined) opts.max = this._cappedOptions.max;
|
|
646
|
+
}
|
|
647
|
+
await this.db.createCollection(this._table.tableName, opts);
|
|
648
|
+
}
|
|
573
649
|
}
|
|
574
650
|
async insertOne(data) {
|
|
575
651
|
if (this._incrementFields.size > 0) {
|
|
@@ -579,7 +655,8 @@ else pipeline.push({ $limit: 1e3 });
|
|
|
579
655
|
for (const physical of fields) data[physical] = (maxValues.get(physical) ?? 0) + 1;
|
|
580
656
|
}
|
|
581
657
|
}
|
|
582
|
-
|
|
658
|
+
this._log("insertOne", data);
|
|
659
|
+
const result = await this.collection.insertOne(data, this._getSessionOpts());
|
|
583
660
|
return { insertedId: result.insertedId };
|
|
584
661
|
}
|
|
585
662
|
async insertMany(data) {
|
|
@@ -598,7 +675,8 @@ else pipeline.push({ $limit: 1e3 });
|
|
|
598
675
|
}
|
|
599
676
|
}
|
|
600
677
|
}
|
|
601
|
-
|
|
678
|
+
this._log("insertMany", `${data.length} docs`);
|
|
679
|
+
const result = await this.collection.insertMany(data, this._getSessionOpts());
|
|
602
680
|
return {
|
|
603
681
|
insertedCount: result.insertedCount,
|
|
604
682
|
insertedIds: Object.values(result.insertedIds)
|
|
@@ -607,20 +685,30 @@ else pipeline.push({ $limit: 1e3 });
|
|
|
607
685
|
async findOne(query) {
|
|
608
686
|
const filter = buildMongoFilter(query.filter);
|
|
609
687
|
const opts = this._buildFindOptions(query.controls);
|
|
610
|
-
|
|
688
|
+
this._log("findOne", filter, opts);
|
|
689
|
+
return this.collection.findOne(filter, {
|
|
690
|
+
...opts,
|
|
691
|
+
...this._getSessionOpts()
|
|
692
|
+
});
|
|
611
693
|
}
|
|
612
694
|
async findMany(query) {
|
|
613
695
|
const filter = buildMongoFilter(query.filter);
|
|
614
696
|
const opts = this._buildFindOptions(query.controls);
|
|
615
|
-
|
|
697
|
+
this._log("findMany", filter, opts);
|
|
698
|
+
return this.collection.find(filter, {
|
|
699
|
+
...opts,
|
|
700
|
+
...this._getSessionOpts()
|
|
701
|
+
}).toArray();
|
|
616
702
|
}
|
|
617
703
|
async count(query) {
|
|
618
704
|
const filter = buildMongoFilter(query.filter);
|
|
619
|
-
|
|
705
|
+
this._log("countDocuments", filter);
|
|
706
|
+
return this.collection.countDocuments(filter, this._getSessionOpts());
|
|
620
707
|
}
|
|
621
708
|
async updateOne(filter, data) {
|
|
622
709
|
const mongoFilter = buildMongoFilter(filter);
|
|
623
|
-
|
|
710
|
+
this._log("updateOne", mongoFilter, { $set: data });
|
|
711
|
+
const result = await this.collection.updateOne(mongoFilter, { $set: data }, this._getSessionOpts());
|
|
624
712
|
return {
|
|
625
713
|
matchedCount: result.matchedCount,
|
|
626
714
|
modifiedCount: result.modifiedCount
|
|
@@ -628,7 +716,8 @@ else pipeline.push({ $limit: 1e3 });
|
|
|
628
716
|
}
|
|
629
717
|
async replaceOne(filter, data) {
|
|
630
718
|
const mongoFilter = buildMongoFilter(filter);
|
|
631
|
-
|
|
719
|
+
this._log("replaceOne", mongoFilter, data);
|
|
720
|
+
const result = await this.collection.replaceOne(mongoFilter, data, this._getSessionOpts());
|
|
632
721
|
return {
|
|
633
722
|
matchedCount: result.matchedCount,
|
|
634
723
|
modifiedCount: result.modifiedCount
|
|
@@ -636,12 +725,14 @@ else pipeline.push({ $limit: 1e3 });
|
|
|
636
725
|
}
|
|
637
726
|
async deleteOne(filter) {
|
|
638
727
|
const mongoFilter = buildMongoFilter(filter);
|
|
639
|
-
|
|
728
|
+
this._log("deleteOne", mongoFilter);
|
|
729
|
+
const result = await this.collection.deleteOne(mongoFilter, this._getSessionOpts());
|
|
640
730
|
return { deletedCount: result.deletedCount };
|
|
641
731
|
}
|
|
642
732
|
async updateMany(filter, data) {
|
|
643
733
|
const mongoFilter = buildMongoFilter(filter);
|
|
644
|
-
|
|
734
|
+
this._log("updateMany", mongoFilter, { $set: data });
|
|
735
|
+
const result = await this.collection.updateMany(mongoFilter, { $set: data }, this._getSessionOpts());
|
|
645
736
|
return {
|
|
646
737
|
matchedCount: result.matchedCount,
|
|
647
738
|
modifiedCount: result.modifiedCount
|
|
@@ -649,7 +740,8 @@ else pipeline.push({ $limit: 1e3 });
|
|
|
649
740
|
}
|
|
650
741
|
async replaceMany(filter, data) {
|
|
651
742
|
const mongoFilter = buildMongoFilter(filter);
|
|
652
|
-
|
|
743
|
+
this._log("replaceMany", mongoFilter, { $set: data });
|
|
744
|
+
const result = await this.collection.updateMany(mongoFilter, { $set: data }, this._getSessionOpts());
|
|
653
745
|
return {
|
|
654
746
|
matchedCount: result.matchedCount,
|
|
655
747
|
modifiedCount: result.modifiedCount
|
|
@@ -657,12 +749,18 @@ else pipeline.push({ $limit: 1e3 });
|
|
|
657
749
|
}
|
|
658
750
|
async deleteMany(filter) {
|
|
659
751
|
const mongoFilter = buildMongoFilter(filter);
|
|
660
|
-
|
|
752
|
+
this._log("deleteMany", mongoFilter);
|
|
753
|
+
const result = await this.collection.deleteMany(mongoFilter, this._getSessionOpts());
|
|
661
754
|
return { deletedCount: result.deletedCount };
|
|
662
755
|
}
|
|
663
756
|
async ensureTable() {
|
|
664
757
|
return this.ensureCollectionExists();
|
|
665
758
|
}
|
|
759
|
+
async dropTable() {
|
|
760
|
+
this._log("drop", this._table.tableName);
|
|
761
|
+
await this.collection.drop();
|
|
762
|
+
this._collection = undefined;
|
|
763
|
+
}
|
|
666
764
|
async syncIndexes() {
|
|
667
765
|
await this.ensureCollectionExists();
|
|
668
766
|
const allIndexes = new Map();
|
|
@@ -706,21 +804,29 @@ else pipeline.push({ $limit: 1e3 });
|
|
|
706
804
|
case "unique":
|
|
707
805
|
case "text": {
|
|
708
806
|
if ((local.type === "text" || objMatch(local.fields, remote.key)) && objMatch(local.weights || {}, remote.weights || {})) indexesToCreate.delete(remote.name);
|
|
709
|
-
else
|
|
807
|
+
else {
|
|
808
|
+
this._log("dropIndex", remote.name);
|
|
809
|
+
await this.collection.dropIndex(remote.name);
|
|
810
|
+
}
|
|
710
811
|
break;
|
|
711
812
|
}
|
|
712
813
|
default:
|
|
713
814
|
}
|
|
714
|
-
} else
|
|
815
|
+
} else {
|
|
816
|
+
this._log("dropIndex", remote.name);
|
|
817
|
+
await this.collection.dropIndex(remote.name);
|
|
818
|
+
}
|
|
715
819
|
}
|
|
716
820
|
for (const [key, value] of allIndexes.entries()) switch (value.type) {
|
|
717
821
|
case "plain": {
|
|
718
822
|
if (!indexesToCreate.has(key)) continue;
|
|
823
|
+
this._log("createIndex", key, value.fields);
|
|
719
824
|
await this.collection.createIndex(value.fields, { name: key });
|
|
720
825
|
break;
|
|
721
826
|
}
|
|
722
827
|
case "unique": {
|
|
723
828
|
if (!indexesToCreate.has(key)) continue;
|
|
829
|
+
this._log("createIndex (unique)", key, value.fields);
|
|
724
830
|
await this.collection.createIndex(value.fields, {
|
|
725
831
|
name: key,
|
|
726
832
|
unique: true
|
|
@@ -729,6 +835,7 @@ else await this.collection.dropIndex(remote.name);
|
|
|
729
835
|
}
|
|
730
836
|
case "text": {
|
|
731
837
|
if (!indexesToCreate.has(key)) continue;
|
|
838
|
+
this._log("createIndex (text)", key, value.fields);
|
|
732
839
|
await this.collection.createIndex(value.fields, {
|
|
733
840
|
weights: value.weights,
|
|
734
841
|
name: key
|
|
@@ -760,18 +867,26 @@ else toUpdate.add(remote.name);
|
|
|
760
867
|
}
|
|
761
868
|
default:
|
|
762
869
|
}
|
|
763
|
-
} else if (remote.status !== "DELETING")
|
|
870
|
+
} else if (remote.status !== "DELETING") {
|
|
871
|
+
this._log("dropSearchIndex", remote.name);
|
|
872
|
+
await this.collection.dropSearchIndex(remote.name);
|
|
873
|
+
}
|
|
764
874
|
}
|
|
765
875
|
for (const [key, value] of indexesToCreate.entries()) switch (value.type) {
|
|
766
876
|
case "dynamic_text":
|
|
767
877
|
case "search_text":
|
|
768
878
|
case "vector": {
|
|
769
|
-
if (toUpdate.has(key))
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
879
|
+
if (toUpdate.has(key)) {
|
|
880
|
+
this._log("updateSearchIndex", key, value.definition);
|
|
881
|
+
await this.collection.updateSearchIndex(key, value.definition);
|
|
882
|
+
} else {
|
|
883
|
+
this._log("createSearchIndex", key, value.type);
|
|
884
|
+
await this.collection.createSearchIndex({
|
|
885
|
+
name: key,
|
|
886
|
+
type: value.type === "vector" ? "vectorSearch" : "search",
|
|
887
|
+
definition: value.definition
|
|
888
|
+
});
|
|
889
|
+
}
|
|
775
890
|
break;
|
|
776
891
|
}
|
|
777
892
|
default:
|
|
@@ -787,7 +902,7 @@ else await this.collection.createSearchIndex({
|
|
|
787
902
|
const aliases = physicalFields.map((f) => [`max__${f.replace(/\./g, "__")}`, f]);
|
|
788
903
|
const group = { _id: null };
|
|
789
904
|
for (const [alias, field] of aliases) group[alias] = { $max: `$${field}` };
|
|
790
|
-
const result = await this.collection.aggregate([{ $group: group }]).toArray();
|
|
905
|
+
const result = await this.collection.aggregate([{ $group: group }], this._getSessionOpts()).toArray();
|
|
791
906
|
const maxMap = new Map();
|
|
792
907
|
if (result.length > 0) {
|
|
793
908
|
const row = result[0];
|
|
@@ -849,9 +964,10 @@ else {
|
|
|
849
964
|
}
|
|
850
965
|
}
|
|
851
966
|
constructor(db, asMongo) {
|
|
852
|
-
super(), _define_property$1(this, "db", void 0), _define_property$1(this, "asMongo", void 0), _define_property$1(this, "_collection", void 0), _define_property$1(this, "_mongoIndexes", void 0), _define_property$1(this, "_vectorFilters", void 0), _define_property$1(this, "_searchIndexesMap", void 0), _define_property$1(this, "_incrementFields", void 0), this.db = db, this.asMongo = asMongo, this._mongoIndexes = new Map(), this._vectorFilters = new Map(), this._incrementFields = new Set();
|
|
967
|
+
super(), _define_property$1(this, "db", void 0), _define_property$1(this, "asMongo", void 0), _define_property$1(this, "_collection", void 0), _define_property$1(this, "_mongoIndexes", void 0), _define_property$1(this, "_vectorFilters", void 0), _define_property$1(this, "_searchIndexesMap", void 0), _define_property$1(this, "_incrementFields", void 0), _define_property$1(this, "_cappedOptions", void 0), _define_property$1(this, "_txDisabled", void 0), this.db = db, this.asMongo = asMongo, this._mongoIndexes = new Map(), this._vectorFilters = new Map(), this._incrementFields = new Set(), this._txDisabled = false;
|
|
853
968
|
}
|
|
854
969
|
};
|
|
970
|
+
_define_property$1(MongoAdapter, "_noSession", Object.freeze({}));
|
|
855
971
|
function objMatch(o1, o2) {
|
|
856
972
|
const keys1 = Object.keys(o1);
|
|
857
973
|
const keys2 = Object.keys(o2);
|
|
@@ -880,16 +996,6 @@ function vectorFieldsMatch(left, right) {
|
|
|
880
996
|
return true;
|
|
881
997
|
}
|
|
882
998
|
|
|
883
|
-
//#endregion
|
|
884
|
-
//#region packages/mongo/src/lib/logger.ts
|
|
885
|
-
const NoopLogger = {
|
|
886
|
-
error: () => {},
|
|
887
|
-
warn: () => {},
|
|
888
|
-
log: () => {},
|
|
889
|
-
info: () => {},
|
|
890
|
-
debug: () => {}
|
|
891
|
-
};
|
|
892
|
-
|
|
893
999
|
//#endregion
|
|
894
1000
|
//#region packages/mongo/src/lib/as-mongo.ts
|
|
895
1001
|
function _define_property(obj, key, value) {
|
|
@@ -902,7 +1008,7 @@ function _define_property(obj, key, value) {
|
|
|
902
1008
|
else obj[key] = value;
|
|
903
1009
|
return obj;
|
|
904
1010
|
}
|
|
905
|
-
var AsMongo = class {
|
|
1011
|
+
var AsMongo = class extends DbSpace {
|
|
906
1012
|
get db() {
|
|
907
1013
|
return this.client.db();
|
|
908
1014
|
}
|
|
@@ -914,35 +1020,24 @@ var AsMongo = class {
|
|
|
914
1020
|
const list = await this.getCollectionsList();
|
|
915
1021
|
return list.has(name);
|
|
916
1022
|
}
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
this._ensureCreated(type, logger);
|
|
923
|
-
return this._tables.get(type);
|
|
924
|
-
}
|
|
925
|
-
_ensureCreated(type, logger) {
|
|
926
|
-
if (!this._adapters.has(type)) {
|
|
927
|
-
const adapter = new MongoAdapter(this.db, this);
|
|
928
|
-
const table = new AtscriptDbTable(type, adapter, logger || this.logger);
|
|
929
|
-
this._adapters.set(type, adapter);
|
|
930
|
-
this._tables.set(type, table);
|
|
931
|
-
}
|
|
1023
|
+
/**
|
|
1024
|
+
* Returns the MongoAdapter for the given type.
|
|
1025
|
+
* Convenience accessor for Mongo-specific adapter operations.
|
|
1026
|
+
*/ getAdapter(type) {
|
|
1027
|
+
return super.getAdapter(type);
|
|
932
1028
|
}
|
|
933
1029
|
constructor(client, logger = NoopLogger) {
|
|
934
|
-
|
|
935
|
-
_define_property(this, "client", void 0);
|
|
936
|
-
|
|
937
|
-
_define_property(this, "_adapters", void 0);
|
|
938
|
-
_define_property(this, "_tables", void 0);
|
|
939
|
-
this.logger = logger;
|
|
940
|
-
this._adapters = new WeakMap();
|
|
941
|
-
this._tables = new WeakMap();
|
|
942
|
-
if (typeof client === "string") this.client = new MongoClient(client);
|
|
943
|
-
else this.client = client;
|
|
1030
|
+
const resolvedClient = typeof client === "string" ? new MongoClient(client) : client;
|
|
1031
|
+
super(() => new MongoAdapter(this.db, this), logger), _define_property(this, "client", void 0), _define_property(this, "collectionsList", void 0);
|
|
1032
|
+
this.client = resolvedClient;
|
|
944
1033
|
}
|
|
945
1034
|
};
|
|
946
1035
|
|
|
947
1036
|
//#endregion
|
|
948
|
-
|
|
1037
|
+
//#region packages/mongo/src/lib/index.ts
|
|
1038
|
+
function createAdapter(connection, _options) {
|
|
1039
|
+
return new AsMongo(connection);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
//#endregion
|
|
1043
|
+
export { AsMongo, CollectionPatcher, MongoAdapter, buildMongoFilter, createAdapter, validateMongoIdPlugin };
|
package/dist/plugin.cjs
CHANGED
|
@@ -82,6 +82,22 @@ const annotations = {
|
|
|
82
82
|
});
|
|
83
83
|
}
|
|
84
84
|
}),
|
|
85
|
+
capped: new __atscript_core.AnnotationSpec({
|
|
86
|
+
description: "Creates a **capped collection** with a fixed maximum size.\n\n- Capped collections have fixed size and maintain insertion order.\n- Ideal for logs, event streams, and cache-like data.\n- Changing the cap size requires dropping and recreating the collection — use `@db.sync.method \"drop\"` to allow this.\n\n**Example:**\n```atscript\n@db.table \"logs\"\n@db.mongo.collection\n@db.mongo.capped 10485760, 10000\n@db.sync.method \"drop\"\nexport interface LogEntry {\n message: string\n timestamp: number\n}\n```\n",
|
|
87
|
+
nodeType: ["interface"],
|
|
88
|
+
multiple: false,
|
|
89
|
+
argument: [{
|
|
90
|
+
optional: false,
|
|
91
|
+
name: "size",
|
|
92
|
+
type: "number",
|
|
93
|
+
description: "Maximum size of the collection in **bytes**."
|
|
94
|
+
}, {
|
|
95
|
+
optional: true,
|
|
96
|
+
name: "max",
|
|
97
|
+
type: "number",
|
|
98
|
+
description: "Maximum number of documents in the collection. If omitted, only the byte size limit applies."
|
|
99
|
+
}]
|
|
100
|
+
}),
|
|
85
101
|
search: {
|
|
86
102
|
dynamic: new __atscript_core.AnnotationSpec({
|
|
87
103
|
description: "Creates a **dynamic MongoDB Search Index** that applies to the entire collection.\n\n- **Indexes all text fields automatically** (no need to specify fields).\n- Supports **language analyzers** for text tokenization.\n- Enables **fuzzy search** (typo tolerance) if needed.\n\n**Example:**\n```atscript\n@db.mongo.search.dynamic \"lucene.english\", 1\nexport interface MongoCollection {}\n```\n",
|
package/dist/plugin.mjs
CHANGED
|
@@ -58,6 +58,22 @@ const annotations = {
|
|
|
58
58
|
});
|
|
59
59
|
}
|
|
60
60
|
}),
|
|
61
|
+
capped: new AnnotationSpec({
|
|
62
|
+
description: "Creates a **capped collection** with a fixed maximum size.\n\n- Capped collections have fixed size and maintain insertion order.\n- Ideal for logs, event streams, and cache-like data.\n- Changing the cap size requires dropping and recreating the collection — use `@db.sync.method \"drop\"` to allow this.\n\n**Example:**\n```atscript\n@db.table \"logs\"\n@db.mongo.collection\n@db.mongo.capped 10485760, 10000\n@db.sync.method \"drop\"\nexport interface LogEntry {\n message: string\n timestamp: number\n}\n```\n",
|
|
63
|
+
nodeType: ["interface"],
|
|
64
|
+
multiple: false,
|
|
65
|
+
argument: [{
|
|
66
|
+
optional: false,
|
|
67
|
+
name: "size",
|
|
68
|
+
type: "number",
|
|
69
|
+
description: "Maximum size of the collection in **bytes**."
|
|
70
|
+
}, {
|
|
71
|
+
optional: true,
|
|
72
|
+
name: "max",
|
|
73
|
+
type: "number",
|
|
74
|
+
description: "Maximum number of documents in the collection. If omitted, only the byte size limit applies."
|
|
75
|
+
}]
|
|
76
|
+
}),
|
|
61
77
|
search: {
|
|
62
78
|
dynamic: new AnnotationSpec({
|
|
63
79
|
description: "Creates a **dynamic MongoDB Search Index** that applies to the entire collection.\n\n- **Indexes all text fields automatically** (no need to specify fields).\n- Supports **language analyzers** for text tokenization.\n- Enables **fuzzy search** (typo tolerance) if needed.\n\n**Example:**\n```atscript\n@db.mongo.search.dynamic \"lucene.english\", 1\nexport interface MongoCollection {}\n```\n",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atscript/mongo",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.35",
|
|
4
4
|
"description": "Mongodb plugin for atscript.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"atscript",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"bugs": {
|
|
12
12
|
"url": "https://github.com/moostjs/atscript/issues"
|
|
13
13
|
},
|
|
14
|
-
"license": "
|
|
14
|
+
"license": "MIT",
|
|
15
15
|
"author": "Artem Maltsev",
|
|
16
16
|
"repository": {
|
|
17
17
|
"type": "git",
|
|
@@ -57,9 +57,9 @@
|
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"mongodb": "^6.17.0",
|
|
60
|
-
"@atscript/core": "^0.1.
|
|
61
|
-
"@atscript/
|
|
62
|
-
"@atscript/
|
|
60
|
+
"@atscript/core": "^0.1.35",
|
|
61
|
+
"@atscript/typescript": "^0.1.35",
|
|
62
|
+
"@atscript/utils-db": "^0.1.35"
|
|
63
63
|
},
|
|
64
64
|
"build": [
|
|
65
65
|
{},
|