@gravito/dark-matter 1.0.0 → 1.1.1

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