@lossless.org/client 0.1.0

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.
Files changed (255) hide show
  1. package/.smartconfig.json +37 -0
  2. package/dist_ts/00_commitinfo_data.d.ts +8 -0
  3. package/dist_ts/00_commitinfo_data.js +9 -0
  4. package/dist_ts/classes.losslessorgclient.d.ts +32 -0
  5. package/dist_ts/classes.losslessorgclient.js +103 -0
  6. package/dist_ts/core/classes.error.d.ts +16 -0
  7. package/dist_ts/core/classes.error.js +29 -0
  8. package/dist_ts/core/classes.operation.d.ts +17 -0
  9. package/dist_ts/core/classes.operation.js +70 -0
  10. package/dist_ts/core/interfaces.d.ts +18 -0
  11. package/dist_ts/core/interfaces.js +2 -0
  12. package/dist_ts/index.d.ts +4 -0
  13. package/dist_ts/index.js +3 -0
  14. package/dist_ts/nosqldb/classes.atomicdelete.d.ts +26 -0
  15. package/dist_ts/nosqldb/classes.atomicdelete.js +44 -0
  16. package/dist_ts/nosqldb/classes.atomicfindoneandupdate.d.ts +27 -0
  17. package/dist_ts/nosqldb/classes.atomicfindoneandupdate.js +86 -0
  18. package/dist_ts/nosqldb/classes.atomicupdate.d.ts +26 -0
  19. package/dist_ts/nosqldb/classes.atomicupdate.js +55 -0
  20. package/dist_ts/nosqldb/classes.bootstraprecord.d.ts +14 -0
  21. package/dist_ts/nosqldb/classes.bootstraprecord.js +80 -0
  22. package/dist_ts/nosqldb/classes.bsonassertion.d.ts +2 -0
  23. package/dist_ts/nosqldb/classes.bsonassertion.js +29 -0
  24. package/dist_ts/nosqldb/classes.bsonobjectid.d.ts +2 -0
  25. package/dist_ts/nosqldb/classes.bsonobjectid.js +50 -0
  26. package/dist_ts/nosqldb/classes.bsonsnapshot.d.ts +3 -0
  27. package/dist_ts/nosqldb/classes.bsonsnapshot.js +126 -0
  28. package/dist_ts/nosqldb/classes.cached-document.d.ts +27 -0
  29. package/dist_ts/nosqldb/classes.cached-document.js +112 -0
  30. package/dist_ts/nosqldb/classes.collection.d.ts +302 -0
  31. package/dist_ts/nosqldb/classes.collection.js +1568 -0
  32. package/dist_ts/nosqldb/classes.collectionfactory.d.ts +30 -0
  33. package/dist_ts/nosqldb/classes.collectionfactory.js +65 -0
  34. package/dist_ts/nosqldb/classes.collectionlifecycle.d.ts +4 -0
  35. package/dist_ts/nosqldb/classes.collectionlifecycle.js +15 -0
  36. package/dist_ts/nosqldb/classes.collectionpreparation.d.ts +34 -0
  37. package/dist_ts/nosqldb/classes.collectionpreparation.js +134 -0
  38. package/dist_ts/nosqldb/classes.collectiontopology.d.ts +47 -0
  39. package/dist_ts/nosqldb/classes.collectiontopology.js +388 -0
  40. package/dist_ts/nosqldb/classes.connection.d.ts +18 -0
  41. package/dist_ts/nosqldb/classes.connection.js +23 -0
  42. package/dist_ts/nosqldb/classes.convenience.d.ts +1 -0
  43. package/dist_ts/nosqldb/classes.convenience.js +5 -0
  44. package/dist_ts/nosqldb/classes.cursor.d.ts +15 -0
  45. package/dist_ts/nosqldb/classes.cursor.js +82 -0
  46. package/dist_ts/nosqldb/classes.cursoroperation.d.ts +7 -0
  47. package/dist_ts/nosqldb/classes.cursoroperation.js +36 -0
  48. package/dist_ts/nosqldb/classes.db.d.ts +98 -0
  49. package/dist_ts/nosqldb/classes.db.js +434 -0
  50. package/dist_ts/nosqldb/classes.distributedcoordinator.d.ts +52 -0
  51. package/dist_ts/nosqldb/classes.distributedcoordinator.js +314 -0
  52. package/dist_ts/nosqldb/classes.doc.d.ts +722 -0
  53. package/dist_ts/nosqldb/classes.doc.js +2976 -0
  54. package/dist_ts/nosqldb/classes.easystore.d.ts +42 -0
  55. package/dist_ts/nosqldb/classes.easystore.js +199 -0
  56. package/dist_ts/nosqldb/classes.exactpersistence.d.ts +141 -0
  57. package/dist_ts/nosqldb/classes.exactpersistence.js +1110 -0
  58. package/dist_ts/nosqldb/classes.lucene.adapter.d.ts +173 -0
  59. package/dist_ts/nosqldb/classes.lucene.adapter.js +623 -0
  60. package/dist_ts/nosqldb/classes.mongoadministration.d.ts +73 -0
  61. package/dist_ts/nosqldb/classes.mongoadministration.js +497 -0
  62. package/dist_ts/nosqldb/classes.namespaceinspection.d.ts +42 -0
  63. package/dist_ts/nosqldb/classes.namespaceinspection.js +138 -0
  64. package/dist_ts/nosqldb/classes.operationbudget.d.ts +22 -0
  65. package/dist_ts/nosqldb/classes.operationbudget.js +65 -0
  66. package/dist_ts/nosqldb/classes.ordinarypersistence.d.ts +18 -0
  67. package/dist_ts/nosqldb/classes.ordinarypersistence.js +71 -0
  68. package/dist_ts/nosqldb/classes.persistence.d.ts +14 -0
  69. package/dist_ts/nosqldb/classes.persistence.js +34 -0
  70. package/dist_ts/nosqldb/classes.session.d.ts +28 -0
  71. package/dist_ts/nosqldb/classes.session.js +246 -0
  72. package/dist_ts/nosqldb/classes.storedinspection.d.ts +44 -0
  73. package/dist_ts/nosqldb/classes.storedinspection.js +100 -0
  74. package/dist_ts/nosqldb/classes.testdatabase.d.ts +56 -0
  75. package/dist_ts/nosqldb/classes.testdatabase.js +250 -0
  76. package/dist_ts/nosqldb/classes.watcher.d.ts +37 -0
  77. package/dist_ts/nosqldb/classes.watcher.js +98 -0
  78. package/dist_ts/nosqldb/index.d.ts +27 -0
  79. package/dist_ts/nosqldb/index.js +18 -0
  80. package/dist_ts/nosqldb/logging.d.ts +2 -0
  81. package/dist_ts/nosqldb/logging.js +3 -0
  82. package/dist_ts/nosqldb/plugins.d.ts +17 -0
  83. package/dist_ts/nosqldb/plugins.js +20 -0
  84. package/dist_ts/nosqldb/shim.d.ts +1 -0
  85. package/dist_ts/nosqldb/shim.js +8 -0
  86. package/dist_ts/nosqldb/testsupport.d.ts +3 -0
  87. package/dist_ts/nosqldb/testsupport.js +3 -0
  88. package/dist_ts/objectstorage/classes.bucket.d.ts +227 -0
  89. package/dist_ts/objectstorage/classes.bucket.js +939 -0
  90. package/dist_ts/objectstorage/classes.connection.d.ts +21 -0
  91. package/dist_ts/objectstorage/classes.connection.js +27 -0
  92. package/dist_ts/objectstorage/classes.directory.d.ts +124 -0
  93. package/dist_ts/objectstorage/classes.directory.js +335 -0
  94. package/dist_ts/objectstorage/classes.exactpathpurgeerror.d.ts +5 -0
  95. package/dist_ts/objectstorage/classes.exactpathpurgeerror.js +18 -0
  96. package/dist_ts/objectstorage/classes.exactuploaderror.d.ts +5 -0
  97. package/dist_ts/objectstorage/classes.exactuploaderror.js +19 -0
  98. package/dist_ts/objectstorage/classes.file.d.ts +91 -0
  99. package/dist_ts/objectstorage/classes.file.js +255 -0
  100. package/dist_ts/objectstorage/classes.listcursor.d.ts +41 -0
  101. package/dist_ts/objectstorage/classes.listcursor.js +67 -0
  102. package/dist_ts/objectstorage/classes.metadata.d.ts +57 -0
  103. package/dist_ts/objectstorage/classes.metadata.js +113 -0
  104. package/dist_ts/objectstorage/classes.smartbucket.d.ts +57 -0
  105. package/dist_ts/objectstorage/classes.smartbucket.js +357 -0
  106. package/dist_ts/objectstorage/classes.trash.d.ts +11 -0
  107. package/dist_ts/objectstorage/classes.trash.js +22 -0
  108. package/dist_ts/objectstorage/classes.watcher.d.ts +79 -0
  109. package/dist_ts/objectstorage/classes.watcher.js +249 -0
  110. package/dist_ts/objectstorage/helpers.d.ts +25 -0
  111. package/dist_ts/objectstorage/helpers.js +215 -0
  112. package/dist_ts/objectstorage/index.d.ts +13 -0
  113. package/dist_ts/objectstorage/index.js +13 -0
  114. package/dist_ts/objectstorage/interfaces.d.ts +276 -0
  115. package/dist_ts/objectstorage/interfaces.js +2 -0
  116. package/dist_ts/objectstorage/internal.exactpathpurge.operations.d.ts +11 -0
  117. package/dist_ts/objectstorage/internal.exactpathpurge.operations.js +1069 -0
  118. package/dist_ts/objectstorage/internal.exactupload.capability.d.ts +40 -0
  119. package/dist_ts/objectstorage/internal.exactupload.capability.js +382 -0
  120. package/dist_ts/objectstorage/internal.exactupload.operations.d.ts +11 -0
  121. package/dist_ts/objectstorage/internal.exactupload.operations.js +1104 -0
  122. package/dist_ts/objectstorage/internal.multipart.planner.d.ts +8 -0
  123. package/dist_ts/objectstorage/internal.multipart.planner.js +63 -0
  124. package/dist_ts/objectstorage/internal.multipart.stream.d.ts +17 -0
  125. package/dist_ts/objectstorage/internal.multipart.stream.js +440 -0
  126. package/dist_ts/objectstorage/internal.multipart.types.d.ts +82 -0
  127. package/dist_ts/objectstorage/internal.multipart.types.js +2 -0
  128. package/dist_ts/objectstorage/plugins.d.ts +17 -0
  129. package/dist_ts/objectstorage/plugins.js +23 -0
  130. package/dist_ts/plugins.d.ts +4 -0
  131. package/dist_ts/plugins.js +5 -0
  132. package/dist_ts/sqldb/classes.clickhouseconnection.d.ts +39 -0
  133. package/dist_ts/sqldb/classes.clickhouseconnection.js +225 -0
  134. package/dist_ts/sqldb/classes.sqlconnection.d.ts +50 -0
  135. package/dist_ts/sqldb/classes.sqlconnection.js +376 -0
  136. package/dist_ts/sqldb/classes.sqltable.d.ts +29 -0
  137. package/dist_ts/sqldb/classes.sqltable.js +70 -0
  138. package/dist_ts/sqldb/clickhouse/classes.metricwriter.d.ts +27 -0
  139. package/dist_ts/sqldb/clickhouse/classes.metricwriter.js +89 -0
  140. package/dist_ts/sqldb/clickhouse/index.d.ts +9 -0
  141. package/dist_ts/sqldb/clickhouse/index.js +13 -0
  142. package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.clickhousetable.d.ts +88 -0
  143. package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.clickhousetable.js +304 -0
  144. package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.httpclient.d.ts +28 -0
  145. package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.httpclient.js +46 -0
  146. package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.querybuilder.d.ts +43 -0
  147. package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.querybuilder.js +155 -0
  148. package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.resultset.d.ts +13 -0
  149. package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.resultset.js +36 -0
  150. package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.smartclickhouse.d.ts +49 -0
  151. package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.smartclickhouse.js +106 -0
  152. package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.timedatatable.d.ts +51 -0
  153. package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.timedatatable.js +96 -0
  154. package/dist_ts/sqldb/clickhouse/smartclickhouse.plugins.d.ts +5 -0
  155. package/dist_ts/sqldb/clickhouse/smartclickhouse.plugins.js +6 -0
  156. package/dist_ts/sqldb/clickhouse/smartclickhouse.types.d.ts +53 -0
  157. package/dist_ts/sqldb/clickhouse/smartclickhouse.types.js +41 -0
  158. package/dist_ts/sqldb/index.d.ts +6 -0
  159. package/dist_ts/sqldb/index.js +6 -0
  160. package/dist_ts/sqldb/interfaces.d.ts +53 -0
  161. package/dist_ts/sqldb/interfaces.js +19 -0
  162. package/dist_ts/sqldb/internal.sqlpool.d.ts +51 -0
  163. package/dist_ts/sqldb/internal.sqlpool.js +214 -0
  164. package/dist_ts/sqldb/plugins.clickhouse.d.ts +4 -0
  165. package/dist_ts/sqldb/plugins.clickhouse.js +5 -0
  166. package/dist_ts/sqldb/plugins.mariadb.d.ts +4 -0
  167. package/dist_ts/sqldb/plugins.mariadb.js +5 -0
  168. package/docs/source/smartbucket/license.md +19 -0
  169. package/docs/source/smartclickhouse/license.md +19 -0
  170. package/license.md +21 -0
  171. package/package.json +101 -0
  172. package/readme.md +157 -0
  173. package/ts/00_commitinfo_data.ts +8 -0
  174. package/ts/classes.losslessorgclient.ts +104 -0
  175. package/ts/core/classes.error.ts +31 -0
  176. package/ts/core/classes.operation.ts +60 -0
  177. package/ts/core/interfaces.ts +24 -0
  178. package/ts/index.ts +4 -0
  179. package/ts/nosqldb/classes.atomicdelete.ts +81 -0
  180. package/ts/nosqldb/classes.atomicfindoneandupdate.ts +152 -0
  181. package/ts/nosqldb/classes.atomicupdate.ts +99 -0
  182. package/ts/nosqldb/classes.bootstraprecord.ts +107 -0
  183. package/ts/nosqldb/classes.bsonassertion.ts +25 -0
  184. package/ts/nosqldb/classes.bsonobjectid.ts +45 -0
  185. package/ts/nosqldb/classes.bsonsnapshot.ts +105 -0
  186. package/ts/nosqldb/classes.cached-document.ts +67 -0
  187. package/ts/nosqldb/classes.collection.ts +2399 -0
  188. package/ts/nosqldb/classes.collectionfactory.ts +87 -0
  189. package/ts/nosqldb/classes.collectionlifecycle.ts +24 -0
  190. package/ts/nosqldb/classes.collectionpreparation.ts +164 -0
  191. package/ts/nosqldb/classes.collectiontopology.ts +645 -0
  192. package/ts/nosqldb/classes.connection.ts +31 -0
  193. package/ts/nosqldb/classes.convenience.ts +5 -0
  194. package/ts/nosqldb/classes.cursor.ts +88 -0
  195. package/ts/nosqldb/classes.cursoroperation.ts +38 -0
  196. package/ts/nosqldb/classes.db.ts +637 -0
  197. package/ts/nosqldb/classes.distributedcoordinator.ts +305 -0
  198. package/ts/nosqldb/classes.doc.ts +4928 -0
  199. package/ts/nosqldb/classes.easystore.ts +154 -0
  200. package/ts/nosqldb/classes.exactpersistence.ts +1974 -0
  201. package/ts/nosqldb/classes.lucene.adapter.ts +780 -0
  202. package/ts/nosqldb/classes.mongoadministration.ts +867 -0
  203. package/ts/nosqldb/classes.namespaceinspection.ts +199 -0
  204. package/ts/nosqldb/classes.operationbudget.ts +75 -0
  205. package/ts/nosqldb/classes.ordinarypersistence.ts +84 -0
  206. package/ts/nosqldb/classes.persistence.ts +62 -0
  207. package/ts/nosqldb/classes.session.ts +389 -0
  208. package/ts/nosqldb/classes.storedinspection.ts +151 -0
  209. package/ts/nosqldb/classes.testdatabase.ts +272 -0
  210. package/ts/nosqldb/classes.watcher.ts +96 -0
  211. package/ts/nosqldb/index.ts +51 -0
  212. package/ts/nosqldb/logging.ts +3 -0
  213. package/ts/nosqldb/plugins.ts +37 -0
  214. package/ts/nosqldb/shim.ts +6 -0
  215. package/ts/nosqldb/testsupport.ts +13 -0
  216. package/ts/objectstorage/classes.bucket.ts +1114 -0
  217. package/ts/objectstorage/classes.connection.ts +36 -0
  218. package/ts/objectstorage/classes.directory.ts +425 -0
  219. package/ts/objectstorage/classes.exactpathpurgeerror.ts +24 -0
  220. package/ts/objectstorage/classes.exactuploaderror.ts +25 -0
  221. package/ts/objectstorage/classes.file.ts +303 -0
  222. package/ts/objectstorage/classes.listcursor.ts +89 -0
  223. package/ts/objectstorage/classes.metadata.ts +134 -0
  224. package/ts/objectstorage/classes.smartbucket.ts +445 -0
  225. package/ts/objectstorage/classes.trash.ts +30 -0
  226. package/ts/objectstorage/classes.watcher.ts +289 -0
  227. package/ts/objectstorage/helpers.ts +254 -0
  228. package/ts/objectstorage/index.ts +13 -0
  229. package/ts/objectstorage/interfaces.ts +389 -0
  230. package/ts/objectstorage/internal.exactpathpurge.operations.ts +1936 -0
  231. package/ts/objectstorage/internal.exactupload.capability.ts +580 -0
  232. package/ts/objectstorage/internal.exactupload.operations.ts +1715 -0
  233. package/ts/objectstorage/internal.multipart.planner.ts +90 -0
  234. package/ts/objectstorage/internal.multipart.stream.ts +606 -0
  235. package/ts/objectstorage/internal.multipart.types.ts +106 -0
  236. package/ts/objectstorage/plugins.ts +35 -0
  237. package/ts/plugins.ts +4 -0
  238. package/ts/sqldb/classes.clickhouseconnection.ts +189 -0
  239. package/ts/sqldb/classes.sqlconnection.ts +350 -0
  240. package/ts/sqldb/classes.sqltable.ts +70 -0
  241. package/ts/sqldb/clickhouse/classes.metricwriter.ts +72 -0
  242. package/ts/sqldb/clickhouse/index.ts +16 -0
  243. package/ts/sqldb/clickhouse/smartclickhouse.classes.clickhousetable.ts +372 -0
  244. package/ts/sqldb/clickhouse/smartclickhouse.classes.httpclient.ts +52 -0
  245. package/ts/sqldb/clickhouse/smartclickhouse.classes.querybuilder.ts +120 -0
  246. package/ts/sqldb/clickhouse/smartclickhouse.classes.resultset.ts +44 -0
  247. package/ts/sqldb/clickhouse/smartclickhouse.classes.smartclickhouse.ts +123 -0
  248. package/ts/sqldb/clickhouse/smartclickhouse.classes.timedatatable.ts +114 -0
  249. package/ts/sqldb/clickhouse/smartclickhouse.plugins.ts +5 -0
  250. package/ts/sqldb/clickhouse/smartclickhouse.types.ts +134 -0
  251. package/ts/sqldb/index.ts +6 -0
  252. package/ts/sqldb/interfaces.ts +61 -0
  253. package/ts/sqldb/internal.sqlpool.ts +179 -0
  254. package/ts/sqldb/plugins.clickhouse.ts +4 -0
  255. package/ts/sqldb/plugins.mariadb.ts +4 -0
@@ -0,0 +1,867 @@
1
+ import * as plugins from './plugins.js';
2
+
3
+ import type { SmartdataDb } from './classes.db.js';
4
+ import { SmartdataPersistenceError } from './classes.persistence.js';
5
+
6
+ export interface ISmartdataMongoOperationOptions {
7
+ timeoutMs?: number;
8
+ signal?: AbortSignal;
9
+ }
10
+
11
+ export interface ISmartdataMongoReplicaSetExpectation
12
+ extends ISmartdataMongoOperationOptions {
13
+ expectedSetName: string;
14
+ expectedMemberHost: string;
15
+ }
16
+
17
+ export interface ISmartdataMongoReplicaSetMember {
18
+ host: string;
19
+ health: number;
20
+ state: string;
21
+ }
22
+
23
+ export type TSmartdataMongoReplicaSetState =
24
+ | 'replication_disabled'
25
+ | 'uninitialized'
26
+ | 'exact_primary'
27
+ | 'exact_non_primary'
28
+ | 'divergent'
29
+ | 'unavailable';
30
+
31
+ export interface ISmartdataMongoReplicaSetInspection {
32
+ state: TSmartdataMongoReplicaSetState;
33
+ durationMs: number;
34
+ observedSetName?: string;
35
+ observedMembers?: ISmartdataMongoReplicaSetMember[];
36
+ }
37
+
38
+ export interface ISmartdataMongoReplicaSetInitializationResult {
39
+ status: 'initiated' | 'already_configured';
40
+ topology: ISmartdataMongoReplicaSetInspection;
41
+ }
42
+
43
+ export type TSmartdataMongoTransactionReadinessFailureReason =
44
+ | 'aborted'
45
+ | 'timeout'
46
+ | 'unsupported_topology'
47
+ | 'unavailable'
48
+ | 'cleanup_failed';
49
+
50
+ export type TSmartdataMongoTransactionReadinessResult =
51
+ | {
52
+ ready: true;
53
+ status: 'ready';
54
+ durationMs: number;
55
+ }
56
+ | {
57
+ ready: false;
58
+ status: 'unavailable';
59
+ reason: TSmartdataMongoTransactionReadinessFailureReason;
60
+ durationMs: number;
61
+ };
62
+
63
+ export interface ISmartdataMongoDatabaseUserOptions
64
+ extends ISmartdataMongoOperationOptions {
65
+ expectedDatabaseName: string;
66
+ username: string;
67
+ password: string;
68
+ }
69
+
70
+ export interface ISmartdataMongoDatabaseUserRemovalOptions
71
+ extends ISmartdataMongoOperationOptions {
72
+ expectedDatabaseName: string;
73
+ username: string;
74
+ }
75
+
76
+ export interface ISmartdataMongoDatabaseDropOptions
77
+ extends ISmartdataMongoOperationOptions {
78
+ expectedDatabaseName: string;
79
+ confirmationDatabaseName: string;
80
+ }
81
+
82
+ const mongoAdministrationDefaultTimeoutMs = 5_000;
83
+ const mongoAdministrationMaximumTimeoutMs = 30_000;
84
+ const transactionCapabilityCollectionName = '__smartdata_transaction_capability';
85
+
86
+ interface ITransactionCapabilityProbe extends plugins.mongodb.BSON.Document {
87
+ _id: string;
88
+ probeId: string;
89
+ part: number;
90
+ }
91
+
92
+ const replicaSetNameRegex = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
93
+ const replicaSetMemberRegex = /^([A-Za-z0-9][A-Za-z0-9.-]{0,252}):([0-9]{1,5})$/;
94
+ const databaseNameRegex = /^[^/\\.\s"$*<>:|?\u0000]{1,63}$/;
95
+ const databaseUsernameRegex = /^[^\u0000\r\n]{1,128}$/;
96
+
97
+ const assertExactDataObject = (
98
+ valueArg: unknown,
99
+ expectedKeysArg: readonly string[],
100
+ descriptionArg: string,
101
+ ): Record<string, unknown> => {
102
+ if (
103
+ valueArg === null
104
+ || typeof valueArg !== 'object'
105
+ || Array.isArray(valueArg)
106
+ || Object.getPrototypeOf(valueArg) !== Object.prototype
107
+ ) {
108
+ throw new SmartdataPersistenceError(
109
+ 'invalid_argument',
110
+ `${descriptionArg} must be a plain object.`,
111
+ );
112
+ }
113
+ const keys = Reflect.ownKeys(valueArg);
114
+ if (keys.some((keyArg) => (
115
+ typeof keyArg !== 'string' || !expectedKeysArg.includes(keyArg)
116
+ ))) {
117
+ throw new SmartdataPersistenceError(
118
+ 'invalid_argument',
119
+ `${descriptionArg} has an invalid shape.`,
120
+ );
121
+ }
122
+ for (const key of keys as string[]) {
123
+ const descriptor = Object.getOwnPropertyDescriptor(valueArg, key);
124
+ if (!descriptor || !('value' in descriptor)) {
125
+ throw new SmartdataPersistenceError(
126
+ 'invalid_argument',
127
+ `${descriptionArg} must contain data properties only.`,
128
+ );
129
+ }
130
+ }
131
+ return valueArg as Record<string, unknown>;
132
+ };
133
+
134
+ const isAbortSignal = (valueArg: unknown): valueArg is AbortSignal => (
135
+ valueArg !== null
136
+ && typeof valueArg === 'object'
137
+ && typeof (valueArg as AbortSignal).aborted === 'boolean'
138
+ && typeof (valueArg as AbortSignal).addEventListener === 'function'
139
+ );
140
+
141
+ const assertOperationOptions = (
142
+ optionsArg: Record<string, unknown>,
143
+ ): Required<Pick<ISmartdataMongoOperationOptions, 'timeoutMs'>>
144
+ & Pick<ISmartdataMongoOperationOptions, 'signal'> => {
145
+ const timeoutMs = optionsArg.timeoutMs ?? mongoAdministrationDefaultTimeoutMs;
146
+ if (
147
+ !Number.isSafeInteger(timeoutMs)
148
+ || (timeoutMs as number) <= 0
149
+ || (timeoutMs as number) > mongoAdministrationMaximumTimeoutMs
150
+ ) {
151
+ throw new SmartdataPersistenceError(
152
+ 'invalid_argument',
153
+ `Mongo administration timeoutMs must be a positive safe integer no greater than ${mongoAdministrationMaximumTimeoutMs}.`,
154
+ );
155
+ }
156
+ if (optionsArg.signal !== undefined && !isAbortSignal(optionsArg.signal)) {
157
+ throw new SmartdataPersistenceError(
158
+ 'invalid_argument',
159
+ 'Mongo administration signal must be an AbortSignal.',
160
+ );
161
+ }
162
+ return {
163
+ timeoutMs: timeoutMs as number,
164
+ signal: optionsArg.signal as AbortSignal | undefined,
165
+ };
166
+ };
167
+
168
+ const assertReplicaSetExpectation = (
169
+ optionsArg: ISmartdataMongoReplicaSetExpectation,
170
+ ): ISmartdataMongoReplicaSetExpectation & { timeoutMs: number } => {
171
+ const options = assertExactDataObject(
172
+ optionsArg,
173
+ ['expectedSetName', 'expectedMemberHost', 'timeoutMs', 'signal'],
174
+ 'Mongo replica-set expectation',
175
+ );
176
+ if (
177
+ typeof options.expectedSetName !== 'string'
178
+ || !replicaSetNameRegex.test(options.expectedSetName)
179
+ ) {
180
+ throw new SmartdataPersistenceError(
181
+ 'invalid_argument',
182
+ 'Mongo expectedSetName must be a canonical replica-set name.',
183
+ );
184
+ }
185
+ if (typeof options.expectedMemberHost !== 'string') {
186
+ throw new SmartdataPersistenceError(
187
+ 'invalid_argument',
188
+ 'Mongo expectedMemberHost must be a canonical hostname and port.',
189
+ );
190
+ }
191
+ const memberMatch = replicaSetMemberRegex.exec(options.expectedMemberHost);
192
+ const port = memberMatch ? Number(memberMatch[2]) : 0;
193
+ if (!memberMatch || port < 1 || port > 65_535) {
194
+ throw new SmartdataPersistenceError(
195
+ 'invalid_argument',
196
+ 'Mongo expectedMemberHost must be a canonical hostname and port.',
197
+ );
198
+ }
199
+ return {
200
+ expectedSetName: options.expectedSetName,
201
+ expectedMemberHost: options.expectedMemberHost,
202
+ ...assertOperationOptions(options),
203
+ };
204
+ };
205
+
206
+ const isMongoServerError = (
207
+ errorArg: unknown,
208
+ codeArg: number,
209
+ codeNameArg: string,
210
+ ): boolean => errorArg instanceof plugins.mongodb.MongoServerError
211
+ && (errorArg.code === codeArg || errorArg.codeName === codeNameArg);
212
+
213
+ const isMongoTimeoutError = (errorArg: unknown): boolean => (
214
+ errorArg instanceof plugins.mongodb.MongoOperationTimeoutError
215
+ );
216
+
217
+ const isUnsupportedTransactionTopologyError = (errorArg: unknown): boolean => (
218
+ isMongoServerError(errorArg, 20, 'IllegalOperation')
219
+ || isMongoServerError(errorArg, 263, 'OperationNotSupportedInTransaction')
220
+ );
221
+
222
+ const isUserAlreadyExistsError = (errorArg: unknown): boolean => (
223
+ isMongoServerError(errorArg, 51_003, 'UserAlreadyExists')
224
+ || isMongoServerError(errorArg, 51_003, 'Location51003')
225
+ );
226
+
227
+ const isUserNotFoundError = (errorArg: unknown): boolean => (
228
+ isMongoServerError(errorArg, 11, 'UserNotFound')
229
+ );
230
+
231
+ export class SmartdataMongoAdministration {
232
+ private readonly owner: SmartdataDb;
233
+ private readonly expectedMongoDb: plugins.mongodb.Db;
234
+ private readonly expectedMongoDbClient: plugins.mongodb.MongoClient;
235
+
236
+ constructor(ownerArg: SmartdataDb) {
237
+ if (
238
+ ownerArg.status !== 'connected'
239
+ || !ownerArg.mongoDb
240
+ || !ownerArg.mongoDbClient
241
+ || ownerArg.mongoDb.client !== ownerArg.mongoDbClient
242
+ ) {
243
+ throw new SmartdataPersistenceError(
244
+ 'unsupported_operation',
245
+ 'Mongo administration requires a connected SmartData database.',
246
+ );
247
+ }
248
+ this.owner = ownerArg;
249
+ this.expectedMongoDb = ownerArg.mongoDb;
250
+ this.expectedMongoDbClient = ownerArg.mongoDbClient;
251
+ }
252
+
253
+ public async inspectReplicaSet(
254
+ optionsArg: ISmartdataMongoReplicaSetExpectation,
255
+ ): Promise<ISmartdataMongoReplicaSetInspection> {
256
+ const options = assertReplicaSetExpectation(optionsArg);
257
+ const startedAt = performance.now();
258
+ const context = this.getConnectedContext();
259
+ if (options.signal?.aborted) {
260
+ return {
261
+ state: 'unavailable',
262
+ durationMs: performance.now() - startedAt,
263
+ };
264
+ }
265
+
266
+ let status: plugins.mongodb.BSON.Document;
267
+ try {
268
+ status = await context.mongoDb.admin().command(
269
+ { replSetGetStatus: 1 },
270
+ {
271
+ timeoutMS: options.timeoutMs,
272
+ signal: options.signal,
273
+ },
274
+ );
275
+ } catch (errorArg) {
276
+ if (!this.contextIsCurrent(context)) {
277
+ return this.createTopologyResult('unavailable', startedAt);
278
+ }
279
+ if (isMongoServerError(errorArg, 76, 'NoReplicationEnabled')) {
280
+ return this.createTopologyResult('replication_disabled', startedAt);
281
+ }
282
+ if (isMongoServerError(errorArg, 94, 'NotYetInitialized')) {
283
+ return this.createTopologyResult('uninitialized', startedAt);
284
+ }
285
+ return this.createTopologyResult('unavailable', startedAt);
286
+ }
287
+
288
+ if (!this.contextIsCurrent(context)) {
289
+ return this.createTopologyResult('unavailable', startedAt);
290
+ }
291
+ const observedSetName = typeof status.set === 'string' ? status.set : undefined;
292
+ const rawMembers = Array.isArray(status.members) ? status.members : [];
293
+ const observedMembers: ISmartdataMongoReplicaSetMember[] = [];
294
+ let membersWellFormed = Array.isArray(status.members);
295
+ for (const memberArg of rawMembers) {
296
+ if (!memberArg || typeof memberArg !== 'object' || Array.isArray(memberArg)) {
297
+ membersWellFormed = false;
298
+ continue;
299
+ }
300
+ const member = memberArg as Record<string, unknown>;
301
+ if (
302
+ typeof member.name !== 'string'
303
+ || typeof member.stateStr !== 'string'
304
+ || typeof member.health !== 'number'
305
+ || !Number.isFinite(member.health)
306
+ ) {
307
+ membersWellFormed = false;
308
+ continue;
309
+ }
310
+ observedMembers.push({
311
+ host: member.name,
312
+ health: member.health,
313
+ state: member.stateStr,
314
+ });
315
+ }
316
+ const exactConfiguration = membersWellFormed
317
+ && rawMembers.length === observedMembers.length
318
+ && observedSetName === options.expectedSetName
319
+ && observedMembers.length === 1
320
+ && observedMembers[0].host === options.expectedMemberHost;
321
+ if (!exactConfiguration) {
322
+ return {
323
+ state: 'divergent',
324
+ observedSetName,
325
+ observedMembers,
326
+ durationMs: performance.now() - startedAt,
327
+ };
328
+ }
329
+ const exactPrimary = status.myState === 1
330
+ && observedMembers[0].state === 'PRIMARY'
331
+ && observedMembers[0].health === 1;
332
+ return {
333
+ state: exactPrimary ? 'exact_primary' : 'exact_non_primary',
334
+ observedSetName,
335
+ observedMembers,
336
+ durationMs: performance.now() - startedAt,
337
+ };
338
+ }
339
+
340
+ public async initializeSingleMemberReplicaSet(
341
+ optionsArg: ISmartdataMongoReplicaSetExpectation,
342
+ ): Promise<ISmartdataMongoReplicaSetInitializationResult> {
343
+ const options = assertReplicaSetExpectation(optionsArg);
344
+ const topology = await this.inspectReplicaSet(options);
345
+ if (topology.state === 'exact_primary' || topology.state === 'exact_non_primary') {
346
+ return {
347
+ status: 'already_configured',
348
+ topology,
349
+ };
350
+ }
351
+ if (topology.state === 'divergent') {
352
+ throw new SmartdataPersistenceError(
353
+ 'invalid_configuration',
354
+ 'Mongo replica-set configuration differs from the exact expected topology.',
355
+ );
356
+ }
357
+ if (topology.state === 'replication_disabled') {
358
+ throw new SmartdataPersistenceError(
359
+ 'invalid_configuration',
360
+ 'Mongo replication must be enabled before replica-set initialization.',
361
+ );
362
+ }
363
+ if (topology.state !== 'uninitialized') {
364
+ throw new SmartdataPersistenceError(
365
+ 'unsupported_operation',
366
+ 'Mongo replica-set topology is unavailable for initialization.',
367
+ );
368
+ }
369
+
370
+ const context = this.getConnectedContext();
371
+ let initializationStatus: ISmartdataMongoReplicaSetInitializationResult['status'] = 'initiated';
372
+ try {
373
+ const response = await context.mongoDb.admin().command(
374
+ {
375
+ replSetInitiate: {
376
+ _id: options.expectedSetName,
377
+ members: [{
378
+ _id: 0,
379
+ host: options.expectedMemberHost,
380
+ }],
381
+ },
382
+ },
383
+ {
384
+ timeoutMS: options.timeoutMs,
385
+ signal: options.signal,
386
+ },
387
+ );
388
+ if (response.ok !== 1 || !this.contextIsCurrent(context)) {
389
+ throw new SmartdataPersistenceError(
390
+ 'unsupported_operation',
391
+ 'Mongo did not acknowledge replica-set initialization.',
392
+ );
393
+ }
394
+ } catch (errorArg) {
395
+ if (isMongoServerError(errorArg, 23, 'AlreadyInitialized')) {
396
+ initializationStatus = 'already_configured';
397
+ } else {
398
+ if (errorArg instanceof SmartdataPersistenceError) throw errorArg;
399
+ throw new SmartdataPersistenceError(
400
+ 'unsupported_operation',
401
+ 'Mongo replica-set initialization failed.',
402
+ );
403
+ }
404
+ }
405
+
406
+ const initializedTopology = await this.inspectReplicaSet(options);
407
+ if (initializedTopology.state === 'divergent') {
408
+ throw new SmartdataPersistenceError(
409
+ 'invalid_configuration',
410
+ 'Mongo initialized a replica-set topology that differs from the expectation.',
411
+ );
412
+ }
413
+ if (
414
+ initializedTopology.state !== 'exact_primary'
415
+ && initializedTopology.state !== 'exact_non_primary'
416
+ ) {
417
+ throw new SmartdataPersistenceError(
418
+ 'unsupported_operation',
419
+ 'Mongo replica-set initialization could not prove the exact expected topology.',
420
+ );
421
+ }
422
+ return {
423
+ status: initializationStatus,
424
+ topology: initializedTopology,
425
+ };
426
+ }
427
+
428
+ public async checkTransactionReadiness(
429
+ optionsArg: ISmartdataMongoOperationOptions = {},
430
+ ): Promise<TSmartdataMongoTransactionReadinessResult> {
431
+ const options = assertExactDataObject(
432
+ optionsArg,
433
+ ['timeoutMs', 'signal'],
434
+ 'Mongo transaction-readiness options',
435
+ );
436
+ const boundedOptions = assertOperationOptions(options);
437
+ const startedAt = performance.now();
438
+ const context = this.getConnectedContext();
439
+ const probeId = globalThis.crypto.randomUUID();
440
+ const collection = context.mongoDb.collection<ITransactionCapabilityProbe>(
441
+ transactionCapabilityCollectionName,
442
+ );
443
+ let session: plugins.mongodb.ClientSession | undefined;
444
+ let failure: unknown;
445
+ let cleanupFailed = false;
446
+
447
+ try {
448
+ try {
449
+ await context.mongoDb.command(
450
+ { create: transactionCapabilityCollectionName },
451
+ {
452
+ timeoutMS: boundedOptions.timeoutMs,
453
+ signal: boundedOptions.signal,
454
+ },
455
+ );
456
+ } catch (errorArg) {
457
+ if (!isMongoServerError(errorArg, 48, 'NamespaceExists')) throw errorArg;
458
+ }
459
+ session = context.mongoDbClient.startSession();
460
+ session.startTransaction({
461
+ readConcern: { level: 'snapshot' },
462
+ writeConcern: { w: 'majority' },
463
+ });
464
+ const insertResponse = await context.mongoDb.command(
465
+ {
466
+ insert: transactionCapabilityCollectionName,
467
+ documents: [
468
+ { _id: `${probeId}:1`, probeId, part: 1 },
469
+ { _id: `${probeId}:2`, probeId, part: 2 },
470
+ ],
471
+ ordered: true,
472
+ },
473
+ {
474
+ session,
475
+ timeoutMS: boundedOptions.timeoutMs,
476
+ signal: boundedOptions.signal,
477
+ },
478
+ );
479
+ if (insertResponse.ok !== 1 || insertResponse.n !== 2) {
480
+ throw new SmartdataPersistenceError(
481
+ 'unsupported_operation',
482
+ 'Mongo transaction probe did not acknowledge both writes.',
483
+ );
484
+ }
485
+ const abortResponse = await context.mongoDb.admin().command(
486
+ {
487
+ abortTransaction: 1,
488
+ writeConcern: {
489
+ w: 'majority',
490
+ wtimeout: boundedOptions.timeoutMs,
491
+ },
492
+ },
493
+ {
494
+ session,
495
+ timeoutMS: boundedOptions.timeoutMs,
496
+ signal: boundedOptions.signal,
497
+ },
498
+ );
499
+ if (abortResponse.ok !== 1 || !this.contextIsCurrent(context)) {
500
+ throw new SmartdataPersistenceError(
501
+ 'unsupported_operation',
502
+ 'Mongo transaction probe did not acknowledge its abort.',
503
+ );
504
+ }
505
+ // The acknowledged raw command proves the server-side abort. Calling the
506
+ // public driver method afterwards advances its local transaction state;
507
+ // that method intentionally ignores most server-side abort errors.
508
+ await session.abortTransaction({ timeoutMS: boundedOptions.timeoutMs });
509
+ const remainingCount = await collection.countDocuments(
510
+ { probeId },
511
+ {
512
+ limit: 2,
513
+ timeoutMS: boundedOptions.timeoutMs,
514
+ signal: boundedOptions.signal,
515
+ },
516
+ );
517
+ if (remainingCount !== 0 || !this.contextIsCurrent(context)) {
518
+ cleanupFailed = remainingCount !== 0;
519
+ throw new SmartdataPersistenceError(
520
+ 'unsupported_operation',
521
+ 'Mongo transaction probe did not abort cleanly.',
522
+ );
523
+ }
524
+ return {
525
+ ready: true,
526
+ status: 'ready',
527
+ durationMs: performance.now() - startedAt,
528
+ };
529
+ } catch (errorArg) {
530
+ failure = errorArg;
531
+ if (session?.inTransaction()) {
532
+ try {
533
+ await session.abortTransaction({ timeoutMS: boundedOptions.timeoutMs });
534
+ } catch {
535
+ cleanupFailed = true;
536
+ }
537
+ }
538
+ try {
539
+ await collection.deleteMany(
540
+ { probeId },
541
+ { timeoutMS: boundedOptions.timeoutMs },
542
+ );
543
+ } catch {
544
+ cleanupFailed = true;
545
+ }
546
+ } finally {
547
+ await session?.endSession().catch(() => undefined);
548
+ }
549
+
550
+ let reason: TSmartdataMongoTransactionReadinessFailureReason;
551
+ if (cleanupFailed) {
552
+ reason = 'cleanup_failed';
553
+ } else if (boundedOptions.signal?.aborted) {
554
+ reason = 'aborted';
555
+ } else if (isMongoTimeoutError(failure)) {
556
+ reason = 'timeout';
557
+ } else if (isUnsupportedTransactionTopologyError(failure)) {
558
+ reason = 'unsupported_topology';
559
+ } else {
560
+ reason = 'unavailable';
561
+ }
562
+ return {
563
+ ready: false,
564
+ status: 'unavailable',
565
+ reason,
566
+ durationMs: performance.now() - startedAt,
567
+ };
568
+ }
569
+
570
+ public async ensureReadWriteDatabaseUser(
571
+ optionsArg: ISmartdataMongoDatabaseUserOptions,
572
+ ): Promise<{ status: 'created' | 'updated' }> {
573
+ const options = this.assertDatabaseUserOptions(optionsArg, true);
574
+ const context = this.getConnectedContext(options.expectedDatabaseName);
575
+ const usersInfo = await this.runAdministrationCommand(
576
+ context,
577
+ {
578
+ usersInfo: {
579
+ user: options.username,
580
+ db: options.expectedDatabaseName,
581
+ },
582
+ },
583
+ options,
584
+ 'Mongo database-user inspection failed.',
585
+ );
586
+ if (!Array.isArray(usersInfo.users) || usersInfo.users.length > 1) {
587
+ throw new SmartdataPersistenceError(
588
+ 'unsupported_operation',
589
+ 'Mongo database-user inspection returned an invalid result.',
590
+ );
591
+ }
592
+ const updateCommand = {
593
+ updateUser: options.username,
594
+ pwd: options.password,
595
+ roles: [{ role: 'readWrite', db: options.expectedDatabaseName }],
596
+ };
597
+ if (usersInfo.users.length === 1) {
598
+ await this.runAdministrationCommand(
599
+ context,
600
+ updateCommand,
601
+ options,
602
+ 'Mongo database-user reconciliation failed.',
603
+ );
604
+ return { status: 'updated' };
605
+ }
606
+
607
+ try {
608
+ await this.executeAdministrationCommand(
609
+ context,
610
+ {
611
+ createUser: options.username,
612
+ pwd: options.password,
613
+ roles: [{ role: 'readWrite', db: options.expectedDatabaseName }],
614
+ },
615
+ options,
616
+ );
617
+ return { status: 'created' };
618
+ } catch (errorArg) {
619
+ if (!isUserAlreadyExistsError(errorArg)) {
620
+ throw new SmartdataPersistenceError(
621
+ 'unsupported_operation',
622
+ 'Mongo database-user reconciliation failed.',
623
+ );
624
+ }
625
+ }
626
+
627
+ await this.runAdministrationCommand(
628
+ context,
629
+ updateCommand,
630
+ options,
631
+ 'Mongo database-user reconciliation failed.',
632
+ );
633
+ return { status: 'updated' };
634
+ }
635
+
636
+ public async removeDatabaseUser(
637
+ optionsArg: ISmartdataMongoDatabaseUserRemovalOptions,
638
+ ): Promise<{ removed: boolean }> {
639
+ const options = this.assertDatabaseUserOptions(optionsArg, false);
640
+ const context = this.getConnectedContext(options.expectedDatabaseName);
641
+ const usersInfo = await this.runAdministrationCommand(
642
+ context,
643
+ {
644
+ usersInfo: {
645
+ user: options.username,
646
+ db: options.expectedDatabaseName,
647
+ },
648
+ },
649
+ options,
650
+ 'Mongo database-user inspection failed.',
651
+ );
652
+ if (!Array.isArray(usersInfo.users) || usersInfo.users.length > 1) {
653
+ throw new SmartdataPersistenceError(
654
+ 'unsupported_operation',
655
+ 'Mongo database-user inspection returned an invalid result.',
656
+ );
657
+ }
658
+ if (usersInfo.users.length === 0) return { removed: false };
659
+ try {
660
+ await this.executeAdministrationCommand(
661
+ context,
662
+ { dropUser: options.username },
663
+ options,
664
+ );
665
+ } catch (errorArg) {
666
+ if (isUserNotFoundError(errorArg)) return { removed: false };
667
+ throw new SmartdataPersistenceError(
668
+ 'unsupported_operation',
669
+ 'Mongo database-user removal failed.',
670
+ );
671
+ }
672
+ return { removed: true };
673
+ }
674
+
675
+ public async dropDatabase(
676
+ optionsArg: ISmartdataMongoDatabaseDropOptions,
677
+ ): Promise<{ dropped: boolean }> {
678
+ const options = assertExactDataObject(
679
+ optionsArg,
680
+ ['expectedDatabaseName', 'confirmationDatabaseName', 'timeoutMs', 'signal'],
681
+ 'Mongo database-drop options',
682
+ );
683
+ if (
684
+ typeof options.expectedDatabaseName !== 'string'
685
+ || !databaseNameRegex.test(options.expectedDatabaseName)
686
+ ) {
687
+ throw new SmartdataPersistenceError(
688
+ 'invalid_argument',
689
+ 'Mongo expectedDatabaseName must be a canonical database name.',
690
+ );
691
+ }
692
+ if (
693
+ options.confirmationDatabaseName !== options.expectedDatabaseName
694
+ || ['admin', 'config', 'local'].includes(options.expectedDatabaseName)
695
+ ) {
696
+ throw new SmartdataPersistenceError(
697
+ 'invalid_argument',
698
+ 'Mongo database-drop confirmation must exactly match a non-system database.',
699
+ );
700
+ }
701
+ const context = this.getConnectedContext(options.expectedDatabaseName);
702
+ await this.runAdministrationCommand(
703
+ context,
704
+ { dropDatabase: 1 },
705
+ assertOperationOptions(options),
706
+ 'Mongo database removal failed.',
707
+ );
708
+ return { dropped: true };
709
+ }
710
+
711
+ private getConnectedContext(expectedDatabaseNameArg?: string): {
712
+ mongoDb: plugins.mongodb.Db;
713
+ mongoDbClient: plugins.mongodb.MongoClient;
714
+ } {
715
+ if (
716
+ this.owner.status !== 'connected'
717
+ || !this.owner.mongoDb
718
+ || !this.owner.mongoDbClient
719
+ || this.owner.mongoDb.client !== this.owner.mongoDbClient
720
+ || this.owner.mongoDb !== this.expectedMongoDb
721
+ || this.owner.mongoDbClient !== this.expectedMongoDbClient
722
+ ) {
723
+ throw new SmartdataPersistenceError(
724
+ 'unsupported_operation',
725
+ 'Mongo administration requires a connected SmartData database.',
726
+ );
727
+ }
728
+ if (
729
+ expectedDatabaseNameArg !== undefined
730
+ && (
731
+ this.owner.mongoDb.databaseName !== expectedDatabaseNameArg
732
+ || this.owner.smartdataOptions.mongoDbName !== expectedDatabaseNameArg
733
+ )
734
+ ) {
735
+ throw new SmartdataPersistenceError(
736
+ 'invalid_configuration',
737
+ 'Mongo expectedDatabaseName does not match the connected database.',
738
+ );
739
+ }
740
+ return {
741
+ mongoDb: this.owner.mongoDb,
742
+ mongoDbClient: this.owner.mongoDbClient,
743
+ };
744
+ }
745
+
746
+ private contextIsCurrent(contextArg: {
747
+ mongoDb: plugins.mongodb.Db;
748
+ mongoDbClient: plugins.mongodb.MongoClient;
749
+ }): boolean {
750
+ return this.owner.status === 'connected'
751
+ && this.owner.mongoDb === contextArg.mongoDb
752
+ && this.owner.mongoDbClient === contextArg.mongoDbClient;
753
+ }
754
+
755
+ private createTopologyResult(
756
+ stateArg: TSmartdataMongoReplicaSetState,
757
+ startedAtArg: number,
758
+ ): ISmartdataMongoReplicaSetInspection {
759
+ return {
760
+ state: stateArg,
761
+ durationMs: performance.now() - startedAtArg,
762
+ };
763
+ }
764
+
765
+ private assertDatabaseUserOptions<THasPassword extends boolean>(
766
+ optionsArg: THasPassword extends true
767
+ ? ISmartdataMongoDatabaseUserOptions
768
+ : ISmartdataMongoDatabaseUserRemovalOptions,
769
+ hasPasswordArg: THasPassword,
770
+ ): {
771
+ expectedDatabaseName: string;
772
+ username: string;
773
+ password: THasPassword extends true ? string : undefined;
774
+ timeoutMs: number;
775
+ signal?: AbortSignal;
776
+ } {
777
+ const allowedKeys = hasPasswordArg
778
+ ? ['expectedDatabaseName', 'username', 'password', 'timeoutMs', 'signal']
779
+ : ['expectedDatabaseName', 'username', 'timeoutMs', 'signal'];
780
+ const options = assertExactDataObject(
781
+ optionsArg,
782
+ allowedKeys,
783
+ 'Mongo database-user options',
784
+ );
785
+ if (
786
+ typeof options.expectedDatabaseName !== 'string'
787
+ || !databaseNameRegex.test(options.expectedDatabaseName)
788
+ ) {
789
+ throw new SmartdataPersistenceError(
790
+ 'invalid_argument',
791
+ 'Mongo expectedDatabaseName must be a canonical database name.',
792
+ );
793
+ }
794
+ if (
795
+ typeof options.username !== 'string'
796
+ || !databaseUsernameRegex.test(options.username)
797
+ ) {
798
+ throw new SmartdataPersistenceError(
799
+ 'invalid_argument',
800
+ 'Mongo database username must be a bounded string without control characters.',
801
+ );
802
+ }
803
+ if (
804
+ hasPasswordArg
805
+ && (
806
+ typeof options.password !== 'string'
807
+ || options.password.length < 16
808
+ || options.password.length > 1024
809
+ || /[\u0000\r\n]/.test(options.password)
810
+ )
811
+ ) {
812
+ throw new SmartdataPersistenceError(
813
+ 'invalid_argument',
814
+ 'Mongo database-user password must be a bounded nonempty secret without control characters.',
815
+ );
816
+ }
817
+ return {
818
+ expectedDatabaseName: options.expectedDatabaseName,
819
+ username: options.username,
820
+ password: (hasPasswordArg ? options.password : undefined) as (
821
+ THasPassword extends true ? string : undefined
822
+ ),
823
+ ...assertOperationOptions(options),
824
+ };
825
+ }
826
+
827
+ private async runAdministrationCommand(
828
+ contextArg: {
829
+ mongoDb: plugins.mongodb.Db;
830
+ mongoDbClient: plugins.mongodb.MongoClient;
831
+ },
832
+ commandArg: plugins.mongodb.BSON.Document,
833
+ optionsArg: { timeoutMs: number; signal?: AbortSignal },
834
+ failureMessageArg: string,
835
+ ): Promise<plugins.mongodb.BSON.Document> {
836
+ try {
837
+ return await this.executeAdministrationCommand(
838
+ contextArg,
839
+ commandArg,
840
+ optionsArg,
841
+ );
842
+ } catch {
843
+ throw new SmartdataPersistenceError(
844
+ 'unsupported_operation',
845
+ failureMessageArg,
846
+ );
847
+ }
848
+ }
849
+
850
+ private async executeAdministrationCommand(
851
+ contextArg: {
852
+ mongoDb: plugins.mongodb.Db;
853
+ mongoDbClient: plugins.mongodb.MongoClient;
854
+ },
855
+ commandArg: plugins.mongodb.BSON.Document,
856
+ optionsArg: { timeoutMs: number; signal?: AbortSignal },
857
+ ): Promise<plugins.mongodb.BSON.Document> {
858
+ const response = await contextArg.mongoDb.command(commandArg, {
859
+ timeoutMS: optionsArg.timeoutMs,
860
+ signal: optionsArg.signal,
861
+ });
862
+ if (response.ok !== 1 || !this.contextIsCurrent(contextArg)) {
863
+ throw new Error('Mongo administration acknowledgement was unavailable.');
864
+ }
865
+ return response;
866
+ }
867
+ }