@gravito/dark-matter 1.0.0 → 1.1.2

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/dist/index.cjs CHANGED
@@ -1,4 +1,3 @@
1
- "use strict";
2
1
  var __create = Object.create;
3
2
  var __defProp = Object.defineProperty;
4
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -36,7 +35,9 @@ __export(index_exports, {
36
35
  MongoGridFS: () => MongoGridFS,
37
36
  MongoManager: () => MongoManager,
38
37
  MongoPoolMonitor: () => MongoPoolMonitor,
39
- MongoQueryBuilder: () => MongoQueryBuilder
38
+ MongoQueryBuilder: () => MongoQueryBuilder,
39
+ MongoSchemaBuilder: () => MongoSchemaBuilder,
40
+ schema: () => schema
40
41
  });
41
42
  module.exports = __toCommonJS(index_exports);
42
43
 
@@ -54,6 +55,7 @@ var MongoQueryBuilder = class _MongoQueryBuilder {
54
55
  sortSpec = {};
55
56
  limitCount;
56
57
  skipCount;
58
+ softDeleteMode = "exclude";
57
59
  // ============================================================================
58
60
  // WHERE Clauses
59
61
  // ============================================================================
@@ -649,6 +651,163 @@ var MongoQueryBuilder = class _MongoQueryBuilder {
649
651
  acknowledged: result.acknowledged
650
652
  };
651
653
  }
654
+ // ============================================================================
655
+ // Soft Delete Methods
656
+ // ============================================================================
657
+ /**
658
+ * 包含已軟刪除的記錄
659
+ *
660
+ * 查詢時包含所有記錄,不過濾已刪除的文檔
661
+ *
662
+ * @returns 當前查詢建構器實例,支援鏈式調用
663
+ *
664
+ * @example
665
+ * ```typescript
666
+ * const allUsers = await query.withTrashed().get();
667
+ * ```
668
+ */
669
+ withTrashed() {
670
+ this.softDeleteMode = "include";
671
+ return this;
672
+ }
673
+ /**
674
+ * 只查詢已軟刪除的記錄
675
+ *
676
+ * 只返回 deletedAt 不為 null 的文檔
677
+ *
678
+ * @returns 當前查詢建構器實例,支援鏈式調用
679
+ *
680
+ * @example
681
+ * ```typescript
682
+ * const trashedUsers = await query.onlyTrashed().get();
683
+ * ```
684
+ */
685
+ onlyTrashed() {
686
+ this.softDeleteMode = "only";
687
+ return this;
688
+ }
689
+ /**
690
+ * 軟刪除單一記錄
691
+ *
692
+ * 設置 deletedAt 為當前時間,而非真正刪除記錄
693
+ *
694
+ * @returns Promise 解析為更新結果
695
+ *
696
+ * @example
697
+ * ```typescript
698
+ * await query.where('_id', userId).softDelete();
699
+ * ```
700
+ */
701
+ async softDelete() {
702
+ const updateDoc = { $set: { deletedAt: /* @__PURE__ */ new Date() } };
703
+ const result = await this.nativeCollection.updateOne(this.toFilter(), updateDoc, {
704
+ session: this.session
705
+ });
706
+ return {
707
+ matchedCount: result.matchedCount,
708
+ modifiedCount: result.modifiedCount,
709
+ acknowledged: result.acknowledged
710
+ };
711
+ }
712
+ /**
713
+ * 批次軟刪除
714
+ *
715
+ * 設置所有符合條件的文檔的 deletedAt 為當前時間
716
+ *
717
+ * @returns Promise 解析為更新結果
718
+ *
719
+ * @example
720
+ * ```typescript
721
+ * await query.where('status', 'inactive').softDeleteMany();
722
+ * ```
723
+ */
724
+ async softDeleteMany() {
725
+ const updateDoc = { $set: { deletedAt: /* @__PURE__ */ new Date() } };
726
+ const result = await this.nativeCollection.updateMany(this.toFilter(), updateDoc, {
727
+ session: this.session
728
+ });
729
+ return {
730
+ matchedCount: result.matchedCount,
731
+ modifiedCount: result.modifiedCount,
732
+ acknowledged: result.acknowledged
733
+ };
734
+ }
735
+ /**
736
+ * 恢復軟刪除的記錄
737
+ *
738
+ * 將 deletedAt 設置為 null,恢復軟刪除的文檔
739
+ *
740
+ * @returns Promise 解析為更新結果
741
+ *
742
+ * @example
743
+ * ```typescript
744
+ * await query.where('_id', userId).restore();
745
+ * ```
746
+ */
747
+ async restore() {
748
+ const updateDoc = { $set: { deletedAt: null } };
749
+ const result = await this.nativeCollection.updateOne(this.toFilter(), updateDoc, {
750
+ session: this.session
751
+ });
752
+ return {
753
+ matchedCount: result.matchedCount,
754
+ modifiedCount: result.modifiedCount,
755
+ acknowledged: result.acknowledged
756
+ };
757
+ }
758
+ /**
759
+ * 批次恢復軟刪除的記錄
760
+ *
761
+ * 將所有符合條件的文檔的 deletedAt 設置為 null
762
+ *
763
+ * @returns Promise 解析為更新結果
764
+ *
765
+ * @example
766
+ * ```typescript
767
+ * await query.onlyTrashed().restoreMany();
768
+ * ```
769
+ */
770
+ async restoreMany() {
771
+ const updateDoc = { $set: { deletedAt: null } };
772
+ const result = await this.nativeCollection.updateMany(this.toFilter(), updateDoc, {
773
+ session: this.session
774
+ });
775
+ return {
776
+ matchedCount: result.matchedCount,
777
+ modifiedCount: result.modifiedCount,
778
+ acknowledged: result.acknowledged
779
+ };
780
+ }
781
+ /**
782
+ * 強制刪除(真正刪除記錄)
783
+ *
784
+ * 永久刪除單一文檔,無法恢復
785
+ *
786
+ * @returns Promise 解析為刪除結果
787
+ *
788
+ * @example
789
+ * ```typescript
790
+ * await query.where('_id', userId).forceDelete();
791
+ * ```
792
+ */
793
+ async forceDelete() {
794
+ return await this.delete();
795
+ }
796
+ /**
797
+ * 批次強制刪除
798
+ *
799
+ * 永久刪除所有符合條件的文檔,無法恢復
800
+ *
801
+ * @returns Promise 解析為刪除結果
802
+ *
803
+ * @example
804
+ * ```typescript
805
+ * await query.where('createdAt', '<', oneYearAgo).forceDeleteMany();
806
+ * ```
807
+ */
808
+ async forceDeleteMany() {
809
+ return await this.deleteMany();
810
+ }
652
811
  /**
653
812
  * Executes a bulk write operation.
654
813
  *
@@ -757,17 +916,15 @@ var MongoQueryBuilder = class _MongoQueryBuilder {
757
916
  toFilter() {
758
917
  const hasMainFilters = Object.keys(this.filters).length > 0;
759
918
  const hasOrFilters = this.orFilters.length > 0;
919
+ let baseFilter;
760
920
  if (!hasOrFilters) {
761
- return { ...this.filters };
762
- }
763
- if (!hasMainFilters) {
764
- return {
765
- $or: this.orFilters
766
- };
921
+ baseFilter = { ...this.filters };
922
+ } else if (!hasMainFilters) {
923
+ baseFilter = { $or: this.orFilters };
924
+ } else {
925
+ baseFilter = { $or: [this.filters, ...this.orFilters] };
767
926
  }
768
- return {
769
- $or: [this.filters, ...this.orFilters]
770
- };
927
+ return this.applySoftDeleteFilter(baseFilter);
771
928
  }
772
929
  /**
773
930
  * Creates a deep copy of the query builder.
@@ -795,6 +952,7 @@ var MongoQueryBuilder = class _MongoQueryBuilder {
795
952
  cloned.sortSpec = { ...this.sortSpec };
796
953
  cloned.limitCount = this.limitCount;
797
954
  cloned.skipCount = this.skipCount;
955
+ cloned.softDeleteMode = this.softDeleteMode;
798
956
  return cloned;
799
957
  }
800
958
  // ============================================================================
@@ -833,6 +991,34 @@ var MongoQueryBuilder = class _MongoQueryBuilder {
833
991
  }
834
992
  return { $set: update };
835
993
  }
994
+ /**
995
+ * 應用軟刪除過濾
996
+ *
997
+ * 根據 softDeleteMode 自動過濾已刪除的記錄
998
+ *
999
+ * @param filter - 基礎過濾條件
1000
+ * @returns 包含軟刪除過濾的完整過濾條件
1001
+ */
1002
+ applySoftDeleteFilter(filter) {
1003
+ if (this.softDeleteMode === "include") {
1004
+ return filter;
1005
+ }
1006
+ if (this.softDeleteMode === "only") {
1007
+ return {
1008
+ ...filter,
1009
+ deletedAt: { $ne: null }
1010
+ };
1011
+ }
1012
+ const softDeleteFilter = {
1013
+ $or: [{ deletedAt: null }, { deletedAt: { $exists: false } }]
1014
+ };
1015
+ if (Object.keys(filter).length === 0) {
1016
+ return softDeleteFilter;
1017
+ }
1018
+ return {
1019
+ $and: [filter, softDeleteFilter]
1020
+ };
1021
+ }
836
1022
  async getObjectId() {
837
1023
  if (_MongoQueryBuilder.ObjectIdCtor) {
838
1024
  return _MongoQueryBuilder.ObjectIdCtor;
@@ -959,17 +1145,27 @@ var MongoClient = class {
959
1145
  if (this.config.socketTimeoutMS) {
960
1146
  options.socketTimeoutMS = this.config.socketTimeoutMS;
961
1147
  }
962
- this.client = new this.mongodb.MongoClient(uri, options);
963
1148
  let lastError = null;
964
1149
  for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
1150
+ const client = new this.mongodb.MongoClient(uri, options);
1151
+ this.client = client;
965
1152
  try {
966
- await this.client.connect();
1153
+ await client.connect();
967
1154
  const dbName = this.config.database ?? "test";
968
- this.db = this.client.db(dbName);
1155
+ this.db = client.db(dbName);
969
1156
  this.connected = true;
970
1157
  return;
971
1158
  } catch (error) {
972
1159
  lastError = error;
1160
+ this.connected = false;
1161
+ this.db = null;
1162
+ try {
1163
+ await client.close();
1164
+ } catch {
1165
+ }
1166
+ if (this.client === client) {
1167
+ this.client = null;
1168
+ }
973
1169
  if (attempt < config.maxRetries) {
974
1170
  const delay = config.retryDelayMs * config.backoffMultiplier ** attempt;
975
1171
  await new Promise((resolve) => setTimeout(resolve, delay));
@@ -1210,12 +1406,38 @@ var MongoDatabaseWrapper = class {
1210
1406
  }
1211
1407
  await this.db.createCollection(name, createOptions);
1212
1408
  }
1213
- async setValidation(collectionName, schema) {
1409
+ async setValidation(collectionName, schema2) {
1214
1410
  await this.db.command({
1215
1411
  collMod: collectionName,
1216
- validator: schema.validator,
1217
- validationLevel: schema.validationLevel ?? "strict",
1218
- validationAction: schema.validationAction ?? "error"
1412
+ validator: schema2.validator,
1413
+ validationLevel: schema2.validationLevel ?? "strict",
1414
+ validationAction: schema2.validationAction ?? "error"
1415
+ });
1416
+ }
1417
+ /**
1418
+ * 使用 Schema Builder 建立 Collection
1419
+ *
1420
+ * 提供友善的 API 來建立帶有 Schema 驗證的 Collection
1421
+ *
1422
+ * @param name - Collection 名稱
1423
+ * @param schemaBuilder - Schema Builder 實例
1424
+ * @param options - 驗證選項(驗證等級、動作)
1425
+ *
1426
+ * @example
1427
+ * ```typescript
1428
+ * import { schema } from '@gravito/dark-matter'
1429
+ *
1430
+ * const userSchema = schema()
1431
+ * .required('name', 'email')
1432
+ * .string('name')
1433
+ * .string('email')
1434
+ *
1435
+ * await Mongo.database().createCollectionWithSchema('users', userSchema)
1436
+ * ```
1437
+ */
1438
+ async createCollectionWithSchema(name, schemaBuilder, options) {
1439
+ await this.createCollection(name, {
1440
+ schema: schemaBuilder.toValidationOptions(options)
1219
1441
  });
1220
1442
  }
1221
1443
  };
@@ -1525,7 +1747,9 @@ var MongoGridFS = class {
1525
1747
  const reader = source.getReader();
1526
1748
  while (true) {
1527
1749
  const { done, value } = await reader.read();
1528
- if (done) break;
1750
+ if (done) {
1751
+ break;
1752
+ }
1529
1753
  uploadStream.write(value);
1530
1754
  }
1531
1755
  uploadStream.end();
@@ -1594,6 +1818,190 @@ var MongoGridFS = class {
1594
1818
  const cursor = this.bucket.find(filter ?? {});
1595
1819
  return await cursor.toArray();
1596
1820
  }
1821
+ /**
1822
+ * 串流上傳檔案
1823
+ *
1824
+ * 支援進度回調的串流上傳,適合大檔案上傳
1825
+ *
1826
+ * @param stream - ReadableStream 來源
1827
+ * @param options - 上傳選項
1828
+ * @param onProgress - 可選的進度回調函數
1829
+ * @returns Promise 解析為檔案 ID
1830
+ *
1831
+ * @example
1832
+ * ```typescript
1833
+ * const stream = file.stream()
1834
+ * const fileId = await grid.uploadStream(stream, {
1835
+ * filename: 'video.mp4'
1836
+ * }, (progress) => {
1837
+ * console.log(`上傳進度: ${progress.percentage}%`)
1838
+ * })
1839
+ * ```
1840
+ */
1841
+ async uploadStream(stream, options, onProgress) {
1842
+ await this.ensureBucket();
1843
+ const uploadStream = this.bucket.openUploadStream(options.filename, {
1844
+ chunkSizeBytes: options.chunkSizeBytes,
1845
+ metadata: options.metadata,
1846
+ contentType: options.contentType
1847
+ });
1848
+ let bytesWritten = 0;
1849
+ const reader = stream.getReader();
1850
+ try {
1851
+ while (true) {
1852
+ const { done, value } = await reader.read();
1853
+ if (done) {
1854
+ break;
1855
+ }
1856
+ uploadStream.write(Buffer.from(value));
1857
+ bytesWritten += value.length;
1858
+ if (onProgress) {
1859
+ onProgress({
1860
+ bytesWritten,
1861
+ totalBytes: 0,
1862
+ // 串流無法預知總大小
1863
+ percentage: 0
1864
+ });
1865
+ }
1866
+ }
1867
+ uploadStream.end();
1868
+ return new Promise((resolve, reject) => {
1869
+ uploadStream.on("finish", () => resolve(uploadStream.id.toString()));
1870
+ uploadStream.on("error", reject);
1871
+ });
1872
+ } catch (error) {
1873
+ uploadStream.abort();
1874
+ throw error;
1875
+ }
1876
+ }
1877
+ /**
1878
+ * 串流下載檔案
1879
+ *
1880
+ * 返回 ReadableStream 以支援大檔案的串流下載
1881
+ *
1882
+ * @param fileId - 檔案 ID
1883
+ * @returns ReadableStream 提供檔案內容
1884
+ *
1885
+ * @example
1886
+ * ```typescript
1887
+ * const stream = grid.downloadStream(fileId)
1888
+ * const reader = stream.getReader()
1889
+ *
1890
+ * while (true) {
1891
+ * const { done, value } = await reader.read()
1892
+ * if (done) break
1893
+ * // 處理 chunk
1894
+ * }
1895
+ * ```
1896
+ */
1897
+ downloadStream(fileId) {
1898
+ return new ReadableStream({
1899
+ start: async (controller) => {
1900
+ try {
1901
+ await this.ensureBucket();
1902
+ const { ObjectId } = await import("mongodb");
1903
+ const downloadStream = this.bucket.openDownloadStream(new ObjectId(fileId));
1904
+ downloadStream.on("data", (chunk) => {
1905
+ controller.enqueue(new Uint8Array(chunk));
1906
+ });
1907
+ downloadStream.on("end", () => {
1908
+ controller.close();
1909
+ });
1910
+ downloadStream.on("error", (error) => {
1911
+ controller.error(error);
1912
+ });
1913
+ } catch (error) {
1914
+ controller.error(error);
1915
+ }
1916
+ },
1917
+ cancel: async () => {
1918
+ }
1919
+ });
1920
+ }
1921
+ /**
1922
+ * 分片上傳大檔案
1923
+ *
1924
+ * 支援進度追蹤的大檔案上傳,自動處理分片
1925
+ *
1926
+ * @param file - Blob 或 File 物件
1927
+ * @param options - 上傳選項
1928
+ * @param onProgress - 可選的進度回調函數
1929
+ * @returns Promise 解析為檔案 ID
1930
+ *
1931
+ * @example
1932
+ * ```typescript
1933
+ * const fileId = await grid.uploadLargeFile(file, {
1934
+ * filename: 'large-video.mp4',
1935
+ * chunkSizeBytes: 255 * 1024 // 255 KB
1936
+ * }, (progress) => {
1937
+ * console.log(`進度: ${progress.percentage}%`)
1938
+ * console.log(`已上傳: ${progress.bytesWritten} / ${progress.totalBytes}`)
1939
+ * })
1940
+ * ```
1941
+ */
1942
+ async uploadLargeFile(file, options, onProgress) {
1943
+ await this.ensureBucket();
1944
+ const chunkSize = options.chunkSizeBytes ?? 255 * 1024;
1945
+ const totalBytes = file.size;
1946
+ let bytesWritten = 0;
1947
+ const uploadStream = this.bucket.openUploadStream(options.filename, {
1948
+ chunkSizeBytes: chunkSize,
1949
+ metadata: { ...options.metadata, totalSize: totalBytes },
1950
+ contentType: options.contentType
1951
+ });
1952
+ const stream = file.stream();
1953
+ const reader = stream.getReader();
1954
+ try {
1955
+ while (true) {
1956
+ const { done, value } = await reader.read();
1957
+ if (done) {
1958
+ break;
1959
+ }
1960
+ uploadStream.write(Buffer.from(value));
1961
+ bytesWritten += value.length;
1962
+ if (onProgress) {
1963
+ onProgress({
1964
+ bytesWritten,
1965
+ totalBytes,
1966
+ percentage: Math.round(bytesWritten / totalBytes * 100)
1967
+ });
1968
+ }
1969
+ }
1970
+ uploadStream.end();
1971
+ return new Promise((resolve, reject) => {
1972
+ uploadStream.on("finish", () => resolve(uploadStream.id.toString()));
1973
+ uploadStream.on("error", reject);
1974
+ });
1975
+ } catch (error) {
1976
+ uploadStream.abort();
1977
+ throw error;
1978
+ }
1979
+ }
1980
+ /**
1981
+ * 取得檔案中繼資料
1982
+ *
1983
+ * 查詢檔案資訊但不下載內容
1984
+ *
1985
+ * @param fileId - 檔案 ID
1986
+ * @returns Promise 解析為檔案中繼資料,找不到時返回 null
1987
+ *
1988
+ * @example
1989
+ * ```typescript
1990
+ * const fileInfo = await grid.findById(fileId)
1991
+ * if (fileInfo) {
1992
+ * console.log(`檔案名稱: ${fileInfo.filename}`)
1993
+ * console.log(`檔案大小: ${fileInfo.length} bytes`)
1994
+ * console.log(`上傳日期: ${fileInfo.uploadDate}`)
1995
+ * }
1996
+ * ```
1997
+ */
1998
+ async findById(fileId) {
1999
+ await this.ensureBucket();
2000
+ const { ObjectId } = await import("mongodb");
2001
+ const cursor = this.bucket.find({ _id: new ObjectId(fileId) });
2002
+ const files = await cursor.toArray();
2003
+ return files.length > 0 ? files[0] : null;
2004
+ }
1597
2005
  async ensureBucket() {
1598
2006
  if (!this.bucket) {
1599
2007
  throw new Error("GridFS bucket not initialized. Please wait a moment after creation.");
@@ -1656,6 +2064,254 @@ var MongoPoolMonitor = class {
1656
2064
  };
1657
2065
  }
1658
2066
  };
2067
+
2068
+ // src/MongoSchemaBuilder.ts
2069
+ var MongoSchemaBuilder = class _MongoSchemaBuilder {
2070
+ schema = {
2071
+ bsonType: "object",
2072
+ required: [],
2073
+ properties: {}
2074
+ };
2075
+ /**
2076
+ * 定義必填欄位
2077
+ *
2078
+ * @param fields - 必填欄位名稱列表
2079
+ * @returns 當前 Schema Builder 實例
2080
+ *
2081
+ * @example
2082
+ * ```typescript
2083
+ * schema().required('name', 'email', 'age')
2084
+ * ```
2085
+ */
2086
+ required(...fields) {
2087
+ this.schema.required = [...this.schema.required, ...fields];
2088
+ return this;
2089
+ }
2090
+ /**
2091
+ * 定義字串欄位
2092
+ *
2093
+ * @param field - 欄位名稱
2094
+ * @param options - 字串選項(長度、模式、枚舉等)
2095
+ * @returns 當前 Schema Builder 實例
2096
+ *
2097
+ * @example
2098
+ * ```typescript
2099
+ * schema()
2100
+ * .string('username', { minLength: 3, maxLength: 50 })
2101
+ * .string('email', { pattern: '^.+@.+$' })
2102
+ * .string('status', { enum: ['active', 'inactive'] })
2103
+ * ```
2104
+ */
2105
+ string(field, options) {
2106
+ const property = { bsonType: "string" };
2107
+ if (options?.minLength !== void 0) {
2108
+ property.minLength = options.minLength;
2109
+ }
2110
+ if (options?.maxLength !== void 0) {
2111
+ property.maxLength = options.maxLength;
2112
+ }
2113
+ if (options?.pattern) {
2114
+ property.pattern = options.pattern;
2115
+ }
2116
+ if (options?.enum) {
2117
+ property.enum = options.enum;
2118
+ }
2119
+ ;
2120
+ this.schema.properties[field] = property;
2121
+ return this;
2122
+ }
2123
+ /**
2124
+ * 定義數字欄位
2125
+ *
2126
+ * @param field - 欄位名稱
2127
+ * @param options - 數字選項(最小值、最大值等)
2128
+ * @returns 當前 Schema Builder 實例
2129
+ *
2130
+ * @example
2131
+ * ```typescript
2132
+ * schema()
2133
+ * .number('price', { minimum: 0, maximum: 10000 })
2134
+ * .number('discount', { minimum: 0, maximum: 1, exclusiveMaximum: true })
2135
+ * ```
2136
+ */
2137
+ number(field, options) {
2138
+ const property = { bsonType: "number" };
2139
+ if (options?.minimum !== void 0) {
2140
+ property.minimum = options.minimum;
2141
+ }
2142
+ if (options?.maximum !== void 0) {
2143
+ property.maximum = options.maximum;
2144
+ }
2145
+ if (options?.exclusiveMinimum) {
2146
+ property.exclusiveMinimum = true;
2147
+ }
2148
+ if (options?.exclusiveMaximum) {
2149
+ property.exclusiveMaximum = true;
2150
+ }
2151
+ ;
2152
+ this.schema.properties[field] = property;
2153
+ return this;
2154
+ }
2155
+ /**
2156
+ * 定義整數欄位
2157
+ *
2158
+ * @param field - 欄位名稱
2159
+ * @param options - 整數選項(最小值、最大值)
2160
+ * @returns 當前 Schema Builder 實例
2161
+ *
2162
+ * @example
2163
+ * ```typescript
2164
+ * schema()
2165
+ * .integer('age', { minimum: 0, maximum: 150 })
2166
+ * .integer('count')
2167
+ * ```
2168
+ */
2169
+ integer(field, options) {
2170
+ const property = { bsonType: "int" };
2171
+ if (options?.minimum !== void 0) {
2172
+ property.minimum = options.minimum;
2173
+ }
2174
+ if (options?.maximum !== void 0) {
2175
+ property.maximum = options.maximum;
2176
+ }
2177
+ ;
2178
+ this.schema.properties[field] = property;
2179
+ return this;
2180
+ }
2181
+ /**
2182
+ * 定義布林欄位
2183
+ *
2184
+ * @param field - 欄位名稱
2185
+ * @returns 當前 Schema Builder 實例
2186
+ *
2187
+ * @example
2188
+ * ```typescript
2189
+ * schema().boolean('isActive').boolean('verified')
2190
+ * ```
2191
+ */
2192
+ boolean(field) {
2193
+ ;
2194
+ this.schema.properties[field] = {
2195
+ bsonType: "bool"
2196
+ };
2197
+ return this;
2198
+ }
2199
+ /**
2200
+ * 定義日期欄位
2201
+ *
2202
+ * @param field - 欄位名稱
2203
+ * @returns 當前 Schema Builder 實例
2204
+ *
2205
+ * @example
2206
+ * ```typescript
2207
+ * schema().date('createdAt').date('updatedAt')
2208
+ * ```
2209
+ */
2210
+ date(field) {
2211
+ ;
2212
+ this.schema.properties[field] = {
2213
+ bsonType: "date"
2214
+ };
2215
+ return this;
2216
+ }
2217
+ /**
2218
+ * 定義陣列欄位
2219
+ *
2220
+ * @param field - 欄位名稱
2221
+ * @param itemType - 陣列元素類型
2222
+ * @param options - 陣列選項(長度、唯一性等)
2223
+ * @returns 當前 Schema Builder 實例
2224
+ *
2225
+ * @example
2226
+ * ```typescript
2227
+ * schema()
2228
+ * .array('tags', 'string')
2229
+ * .array('scores', 'number')
2230
+ * .array('roles', 'string', { minItems: 1, uniqueItems: true })
2231
+ * ```
2232
+ */
2233
+ array(field, itemType, options) {
2234
+ const property = {
2235
+ bsonType: "array",
2236
+ items: { bsonType: itemType }
2237
+ };
2238
+ if (options?.minItems !== void 0) {
2239
+ property.minItems = options.minItems;
2240
+ }
2241
+ if (options?.maxItems !== void 0) {
2242
+ property.maxItems = options.maxItems;
2243
+ }
2244
+ if (options?.uniqueItems) {
2245
+ property.uniqueItems = true;
2246
+ }
2247
+ ;
2248
+ this.schema.properties[field] = property;
2249
+ return this;
2250
+ }
2251
+ /**
2252
+ * 定義物件欄位
2253
+ *
2254
+ * @param field - 欄位名稱
2255
+ * @param callback - 巢狀 Schema 建構函數
2256
+ * @returns 當前 Schema Builder 實例
2257
+ *
2258
+ * @example
2259
+ * ```typescript
2260
+ * schema().object('profile', (s) =>
2261
+ * s
2262
+ * .string('bio', { maxLength: 500 })
2263
+ * .string('avatar')
2264
+ * .integer('followers')
2265
+ * )
2266
+ * ```
2267
+ */
2268
+ object(field, callback) {
2269
+ const nestedBuilder = new _MongoSchemaBuilder();
2270
+ callback(nestedBuilder);
2271
+ this.schema.properties[field] = nestedBuilder.build();
2272
+ return this;
2273
+ }
2274
+ /**
2275
+ * 建構最終的 JSON Schema
2276
+ *
2277
+ * @returns JSON Schema 物件
2278
+ *
2279
+ * @example
2280
+ * ```typescript
2281
+ * const schema = schema()
2282
+ * .required('name')
2283
+ * .string('name')
2284
+ * .build()
2285
+ * ```
2286
+ */
2287
+ build() {
2288
+ return this.schema;
2289
+ }
2290
+ /**
2291
+ * 轉換為 MongoDB 驗證選項
2292
+ *
2293
+ * @param options - 驗證選項(驗證等級、動作)
2294
+ * @returns MongoDB Schema 驗證選項
2295
+ *
2296
+ * @example
2297
+ * ```typescript
2298
+ * const options = schema()
2299
+ * .required('name')
2300
+ * .string('name')
2301
+ * .toValidationOptions({ validationLevel: 'moderate' })
2302
+ * ```
2303
+ */
2304
+ toValidationOptions(options) {
2305
+ return {
2306
+ validator: { $jsonSchema: this.schema },
2307
+ validationLevel: options?.validationLevel ?? "strict",
2308
+ validationAction: options?.validationAction ?? "error"
2309
+ };
2310
+ }
2311
+ };
2312
+ function schema() {
2313
+ return new MongoSchemaBuilder();
2314
+ }
1659
2315
  // Annotate the CommonJS export names for ESM import in node:
1660
2316
  0 && (module.exports = {
1661
2317
  Mongo,
@@ -1664,5 +2320,7 @@ var MongoPoolMonitor = class {
1664
2320
  MongoGridFS,
1665
2321
  MongoManager,
1666
2322
  MongoPoolMonitor,
1667
- MongoQueryBuilder
2323
+ MongoQueryBuilder,
2324
+ MongoSchemaBuilder,
2325
+ schema
1668
2326
  });