@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 Atscript
3
+ Copyright (c) 2025-present Artem Maltsev
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
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
- const result = await this.collection.updateOne(mongoFilter, updateFilter, updateOptions);
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
- return this.collection.aggregate(pipeline).toArray();
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
- const result = await this.collection.aggregate(pipeline).toArray();
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
- const result = await this.collection.aggregate(pipeline).toArray();
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) await this.db.createCollection(this._table.tableName, { comment: "Created by Atscript Mongo Adapter" });
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
- const result = await this.collection.insertOne(data);
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
- const result = await this.collection.insertMany(data);
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
- return this.collection.findOne(filter, opts);
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
- return this.collection.find(filter, opts).toArray();
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
- return this.collection.countDocuments(filter);
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
- const result = await this.collection.updateOne(mongoFilter, { $set: data });
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
- const result = await this.collection.replaceOne(mongoFilter, data);
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
- const result = await this.collection.deleteOne(mongoFilter);
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
- const result = await this.collection.updateMany(mongoFilter, { $set: data });
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
- const result = await this.collection.updateMany(mongoFilter, { $set: data });
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
- const result = await this.collection.deleteMany(mongoFilter);
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 await this.collection.dropIndex(remote.name);
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 await this.collection.dropIndex(remote.name);
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") await this.collection.dropSearchIndex(remote.name);
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)) await this.collection.updateSearchIndex(key, value.definition);
794
- else await this.collection.createSearchIndex({
795
- name: key,
796
- type: value.type === "vector" ? "vectorSearch" : "search",
797
- definition: value.definition
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
- getAdapter(type) {
942
- this._ensureCreated(type);
943
- return this._adapters.get(type);
944
- }
945
- getTable(type, logger) {
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
- _define_property(this, "logger", void 0);
959
- _define_property(this, "client", void 0);
960
- _define_property(this, "collectionsList", void 0);
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 { MongoClient, Filter, UpdateFilter, Document, UpdateOptions, Db, Collection, AggregationCursor, ObjectId } from 'mongodb';
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
- export { AsMongo, CollectionPatcher, MongoAdapter, buildMongoFilter, validateMongoIdPlugin };
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 { AtscriptDbTable, BaseDbAdapter, getKeyProps, walkFilter } from "@atscript/utils-db";
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
- const result = await this.collection.updateOne(mongoFilter, updateFilter, updateOptions);
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
- return this.collection.aggregate(pipeline).toArray();
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
- const result = await this.collection.aggregate(pipeline).toArray();
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
- const result = await this.collection.aggregate(pipeline).toArray();
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) await this.db.createCollection(this._table.tableName, { comment: "Created by Atscript Mongo Adapter" });
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
- const result = await this.collection.insertOne(data);
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
- const result = await this.collection.insertMany(data);
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
- return this.collection.findOne(filter, opts);
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
- return this.collection.find(filter, opts).toArray();
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
- return this.collection.countDocuments(filter);
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
- const result = await this.collection.updateOne(mongoFilter, { $set: data });
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
- const result = await this.collection.replaceOne(mongoFilter, data);
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
- const result = await this.collection.deleteOne(mongoFilter);
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
- const result = await this.collection.updateMany(mongoFilter, { $set: data });
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
- const result = await this.collection.updateMany(mongoFilter, { $set: data });
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
- const result = await this.collection.deleteMany(mongoFilter);
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 await this.collection.dropIndex(remote.name);
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 await this.collection.dropIndex(remote.name);
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") await this.collection.dropSearchIndex(remote.name);
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)) await this.collection.updateSearchIndex(key, value.definition);
770
- else await this.collection.createSearchIndex({
771
- name: key,
772
- type: value.type === "vector" ? "vectorSearch" : "search",
773
- definition: value.definition
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
- getAdapter(type) {
918
- this._ensureCreated(type);
919
- return this._adapters.get(type);
920
- }
921
- getTable(type, logger) {
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
- _define_property(this, "logger", void 0);
935
- _define_property(this, "client", void 0);
936
- _define_property(this, "collectionsList", void 0);
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
- export { AsMongo, CollectionPatcher, MongoAdapter, buildMongoFilter, validateMongoIdPlugin };
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.33",
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": "ISC",
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.33",
61
- "@atscript/utils-db": "^0.1.33",
62
- "@atscript/typescript": "^0.1.33"
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
  {},