@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,199 @@
1
+ import * as plugins from './plugins.js';
2
+ import type { SmartdataDb } from './classes.db.js';
3
+ import { SmartdataPersistenceError } from './classes.persistence.js';
4
+ import { SmartdataSession, acquireSmartdataSession, isSmartdataSession } from './classes.session.js';
5
+ import { runCursorOperation } from './classes.cursoroperation.js';
6
+ import {
7
+ collectionTopologyDbInspectionSymbol,
8
+ compareSmartdataTopologyStrings,
9
+ inspectCollectionTopologySnapshot,
10
+ type ISmartdataCollectionTopologyIndex,
11
+ type TSmartdataCollectionTopologyReasonCode,
12
+ } from './classes.collectiontopology.js';
13
+
14
+ export interface ISmartdataNamespaceInspectionOptions {
15
+ /** Default 256, maximum 1024. Overflow fails the entire observation. */
16
+ maxCollections?: number;
17
+ /** Default bounded. Exact counts scan the collection within the shared deadline. */
18
+ documentCountMode?: 'bounded' | 'exact';
19
+ /** Default 1, maximum 10000. Read one additional document to detect overflow. */
20
+ documentCountUpTo?: number;
21
+ /** Cumulative deadline. Default 5000ms, maximum 120000ms. */
22
+ timeoutMs?: number;
23
+ signal?: AbortSignal;
24
+ /** Owned session for sequential observation; active transactions are unsupported. */
25
+ session?: SmartdataSession;
26
+ }
27
+
28
+ export type TSmartdataObservedDocumentCount =
29
+ | { readonly kind: 'exact'; readonly value: number }
30
+ | { readonly kind: 'at_least'; readonly value: number };
31
+
32
+ export interface ISmartdataObservedCollection {
33
+ readonly collectionName: string;
34
+ readonly namespaceType: 'collection' | 'view' | 'other';
35
+ readonly reasonCodes: readonly TSmartdataCollectionTopologyReasonCode[];
36
+ readonly actualIndexes: readonly ISmartdataCollectionTopologyIndex[];
37
+ readonly documentCount: TSmartdataObservedDocumentCount | null;
38
+ }
39
+
40
+ export interface ISmartdataNamespaceInspection {
41
+ /** Explicitly observational: catalog and data reads are not an atomic snapshot. */
42
+ readonly consistency: 'observational';
43
+ readonly collections: readonly ISmartdataObservedCollection[];
44
+ }
45
+
46
+ const requireBound = (
47
+ valueArg: number | undefined,
48
+ defaultArg: number,
49
+ minimumArg: number,
50
+ maximumArg: number,
51
+ labelArg: string,
52
+ ): number => {
53
+ const value = valueArg === undefined ? defaultArg : valueArg;
54
+ if (!Number.isSafeInteger(value) || value < minimumArg || value > maximumArg) {
55
+ throw new SmartdataPersistenceError('invalid_argument',
56
+ `${labelArg} must be an integer from ${minimumArg} through ${maximumArg}.`);
57
+ }
58
+ return value;
59
+ };
60
+
61
+ /** @internal Bounded catalog reading shared with collection preparation. */
62
+ export const readBoundedInspectionCursor = async <T>(
63
+ cursorArg: plugins.mongodb.AbstractCursor<T>,
64
+ maximumArg: number,
65
+ signalArg: AbortSignal,
66
+ closeOptionsArg?: () => { timeoutMS: number },
67
+ ): Promise<T[]> => runCursorOperation(cursorArg, async (cursor) => {
68
+ const result: T[] = [];
69
+ while (true) {
70
+ signalArg.throwIfAborted();
71
+ const next = await cursor.next();
72
+ if (next === null) return result;
73
+ if (result.length === maximumArg) {
74
+ throw new SmartdataPersistenceError('unsupported_operation',
75
+ 'Namespace inspection exceeded its metadata bound.');
76
+ }
77
+ if (plugins.mongodb.BSON.calculateObjectSize(next as plugins.mongodb.Document) > 65_536) {
78
+ throw new SmartdataPersistenceError('unsupported_operation',
79
+ 'Namespace inspection metadata exceeds 64 KiB per document.');
80
+ }
81
+ result.push(next);
82
+ }
83
+ }, signalArg, closeOptionsArg ? () => [closeOptionsArg()] as [{ timeoutMS: number }] : undefined);
84
+
85
+ /** @internal Entry point exposed through SmartdataDb.inspectNamespace(). */
86
+ export const inspectSmartdataNamespace = async (
87
+ dbArg: SmartdataDb,
88
+ opts: ISmartdataNamespaceInspectionOptions = {},
89
+ ): Promise<ISmartdataNamespaceInspection> => {
90
+ if (!opts || Object.getPrototypeOf(opts) !== Object.prototype ||
91
+ Reflect.ownKeys(opts).some((keyArg) => typeof keyArg !== 'string' ||
92
+ !['maxCollections', 'documentCountMode', 'documentCountUpTo', 'timeoutMs', 'signal', 'session'].includes(keyArg) ||
93
+ !('value' in Object.getOwnPropertyDescriptor(opts, keyArg)!))) {
94
+ throw new SmartdataPersistenceError('invalid_argument', 'Invalid namespace inspection options.');
95
+ }
96
+ const maxCollections = requireBound(opts.maxCollections, 256, 1, 1024, 'maxCollections');
97
+ const documentCountMode = opts.documentCountMode === undefined ? 'bounded' : opts.documentCountMode;
98
+ if (!['bounded', 'exact'].includes(documentCountMode) ||
99
+ (documentCountMode === 'exact' && opts.documentCountUpTo !== undefined)) {
100
+ throw new SmartdataPersistenceError('invalid_argument', 'Invalid namespace document count mode.');
101
+ }
102
+ const documentCountUpTo = requireBound(opts.documentCountUpTo, 1, 0, 10_000, 'documentCountUpTo');
103
+ const timeoutMs = requireBound(opts.timeoutMs, 5_000, 1, 120_000, 'timeoutMs');
104
+ if (opts.signal !== undefined && !(opts.signal instanceof AbortSignal)) {
105
+ throw new SmartdataPersistenceError('invalid_argument', 'signal must be an AbortSignal.');
106
+ }
107
+ opts.signal?.throwIfAborted();
108
+ if (opts.session !== undefined && !isSmartdataSession(opts.session)) {
109
+ throw new SmartdataPersistenceError('invalid_argument', 'Namespace inspection requires an owned session.');
110
+ }
111
+ const deadline = new AbortController();
112
+ const timer = setTimeout(() => deadline.abort(new SmartdataPersistenceError(
113
+ 'unsupported_operation', 'Namespace inspection deadline exceeded.',
114
+ )), timeoutMs);
115
+ const signal = opts.signal ? AbortSignal.any([opts.signal, deadline.signal]) : deadline.signal;
116
+ const expiresAt = performance.now() + timeoutMs;
117
+ const cleanupOptions = () => ({ timeoutMS: Math.max(1, Math.ceil(expiresAt - performance.now())) });
118
+ const operationOptions = () => {
119
+ signal.throwIfAborted();
120
+ return cleanupOptions();
121
+ };
122
+ let lease: ReturnType<typeof acquireSmartdataSession> | undefined;
123
+ try {
124
+ if (opts.session) {
125
+ lease = acquireSmartdataSession(opts.session, dbArg);
126
+ if (lease.rawSession.inTransaction()) {
127
+ throw new SmartdataPersistenceError('unsupported_operation',
128
+ 'Namespace inspection cannot run in a transaction.');
129
+ }
130
+ }
131
+ const result = await dbArg[collectionTopologyDbInspectionSymbol](async (mongoDbArg) => {
132
+ const session = lease?.rawSession;
133
+ const namespaces = await readBoundedInspectionCursor(mongoDbArg.listCollections({}, {
134
+ nameOnly: false, batchSize: 1,
135
+ ...operationOptions(), signal, session,
136
+ }), maxCollections, signal, cleanupOptions);
137
+ const collections: ISmartdataObservedCollection[] = [];
138
+ const names = new Set<string>();
139
+ for (const namespace of namespaces) {
140
+ if (typeof namespace.name !== 'string' || namespace.name.length === 0 ||
141
+ namespace.name.length > 255 || names.has(namespace.name)) {
142
+ throw new SmartdataPersistenceError('unsupported_operation', 'Unreadable namespace metadata.');
143
+ }
144
+ names.add(namespace.name);
145
+ const namespaceType = namespace.type === 'collection' || namespace.type === 'view'
146
+ ? namespace.type : 'other';
147
+ let indexes: unknown[] = [];
148
+ let documentCount: TSmartdataObservedDocumentCount | null = null;
149
+ if (namespaceType === 'collection') {
150
+ const collection = mongoDbArg.collection(namespace.name);
151
+ indexes = await readBoundedInspectionCursor(collection.listIndexes({
152
+ batchSize: 1, ...operationOptions(), session,
153
+ }), 64, signal, cleanupOptions);
154
+ // Both modes reduce on the server; no document bodies enter the
155
+ // observation. Exact mode retains the cumulative operation deadline.
156
+ const pipeline = documentCountMode === 'exact'
157
+ ? [{ $count: 'count' }]
158
+ : [{ $limit: documentCountUpTo + 1 }, { $count: 'count' }];
159
+ const countCursor = collection.aggregate<{ count: number }>(pipeline, {
160
+ ...operationOptions(), session, signal,
161
+ });
162
+ const count = await runCursorOperation(countCursor, async (cursorArg) => {
163
+ const reduction = await cursorArg.next();
164
+ return reduction === null ? 0 : reduction.count;
165
+ }, signal, (): [{ timeoutMS: number }] => [cleanupOptions()]);
166
+ if (!Number.isSafeInteger(count) || count < 0 ||
167
+ (documentCountMode === 'bounded' && count > documentCountUpTo + 1)) {
168
+ throw new SmartdataPersistenceError('unsupported_operation', 'Unreadable namespace count.');
169
+ }
170
+ documentCount = Object.freeze({
171
+ kind: documentCountMode === 'bounded' && count > documentCountUpTo ? 'at_least' : 'exact',
172
+ value: count,
173
+ });
174
+ }
175
+ const topology = inspectCollectionTopologySnapshot({
176
+ collectionName: namespace.name, indexes: [],
177
+ }, { namespace: namespace as unknown as Record<string, unknown>, indexes });
178
+ collections.push(Object.freeze({
179
+ collectionName: namespace.name, namespaceType, documentCount,
180
+ actualIndexes: topology.actualIndexes,
181
+ reasonCodes: Object.freeze(topology.reasonCodes.filter(
182
+ (reasonArg) => reasonArg !== 'unexpected_index',
183
+ )),
184
+ }));
185
+ }
186
+ signal.throwIfAborted();
187
+ collections.sort((leftArg, rightArg) =>
188
+ compareSmartdataTopologyStrings(leftArg.collectionName, rightArg.collectionName));
189
+ return Object.freeze({ consistency: 'observational' as const, collections: Object.freeze(collections) });
190
+ });
191
+ return result;
192
+ } catch (errorArg) {
193
+ signal.throwIfAborted();
194
+ throw errorArg;
195
+ } finally {
196
+ lease?.release();
197
+ clearTimeout(timer);
198
+ }
199
+ };
@@ -0,0 +1,75 @@
1
+ import { SmartdataPersistenceError } from './classes.persistence.js';
2
+ import * as plugins from './plugins.js';
3
+
4
+ /** @internal Options must not execute accessors or accept inherited configuration. */
5
+ export const assertDataOptions = (
6
+ valueArg: unknown,
7
+ keysArg: readonly string[],
8
+ ): Record<string, unknown> => {
9
+ if (!valueArg || plugins.nodeUtil.types.isProxy(valueArg) || Object.getPrototypeOf(valueArg) !== Object.prototype ||
10
+ Reflect.ownKeys(valueArg).some((keyArg) => typeof keyArg !== 'string' ||
11
+ !keysArg.includes(keyArg) ||
12
+ !('value' in Object.getOwnPropertyDescriptor(valueArg, keyArg)!))) {
13
+ throw new SmartdataPersistenceError('invalid_argument', 'Invalid SmartData operation options.');
14
+ }
15
+ const snapshot = Object.create(null) as Record<string, unknown>;
16
+ for (const key of Reflect.ownKeys(valueArg) as string[]) {
17
+ snapshot[key] = Object.getOwnPropertyDescriptor(valueArg, key)!.value;
18
+ }
19
+ return snapshot;
20
+ };
21
+
22
+ /** @internal One deadline for an operation and all of its phases. Never exposes abort reasons. */
23
+ export class SmartdataOperationBudget {
24
+ public readonly signal: AbortSignal;
25
+ private readonly controller = new AbortController();
26
+ private readonly expiresAt: number;
27
+ private readonly timer: ReturnType<typeof setTimeout>;
28
+
29
+ constructor(timeoutMsArg: unknown, signalArg?: unknown) {
30
+ if (!Number.isSafeInteger(timeoutMsArg) || (timeoutMsArg as number) < 1 ||
31
+ (timeoutMsArg as number) > 120_000 ||
32
+ (signalArg !== undefined && (plugins.nodeUtil.types.isProxy(signalArg) || !(signalArg instanceof AbortSignal)))) {
33
+ throw new SmartdataPersistenceError('invalid_argument', 'Invalid SmartData timeout or cancellation signal.');
34
+ }
35
+ this.expiresAt = performance.now() + (timeoutMsArg as number);
36
+ this.signal = signalArg instanceof AbortSignal
37
+ ? AbortSignal.any([signalArg, this.controller.signal]) : this.controller.signal;
38
+ this.timer = setTimeout(() => this.controller.abort(), timeoutMsArg as number);
39
+ }
40
+
41
+ public check(): void {
42
+ if (performance.now() >= this.expiresAt) this.controller.abort();
43
+ if (this.signal.aborted) {
44
+ throw new SmartdataPersistenceError('unsupported_operation',
45
+ 'SmartData operation was cancelled or exceeded its deadline.');
46
+ }
47
+ }
48
+
49
+ public remainingMs(): number {
50
+ this.check();
51
+ return this.cleanupOptions().timeoutMS;
52
+ }
53
+
54
+ public operationOptions(): { timeoutMS: number; signal: AbortSignal } {
55
+ return { timeoutMS: this.remainingMs(), signal: this.signal };
56
+ }
57
+
58
+ public cleanupOptions(): { timeoutMS: number } {
59
+ return { timeoutMS: Math.max(1, Math.ceil(this.expiresAt - performance.now())) };
60
+ }
61
+
62
+ /** Waiting does not cancel or abandon the owner of the pending operation. */
63
+ public async waitFor(operationArg: Promise<unknown>): Promise<void> {
64
+ this.check();
65
+ await new Promise<void>((resolveArg) => {
66
+ const finish = () => { this.signal.removeEventListener('abort', finish); resolveArg(); };
67
+ this.signal.addEventListener('abort', finish, { once: true });
68
+ operationArg.then(finish, finish);
69
+ if (this.signal.aborted) finish();
70
+ });
71
+ this.check();
72
+ }
73
+
74
+ public close(): void { clearTimeout(this.timer); }
75
+ }
@@ -0,0 +1,84 @@
1
+ import * as plugins from './plugins.js';
2
+ import { SmartdataPersistenceError } from './classes.persistence.js';
3
+ import { assertBsonWithoutMutation } from './classes.bsonassertion.js';
4
+
5
+ export interface IOrdinaryPersistenceOptions {
6
+ /** Defaults to ObjectId. String IDs are caller-owned and immutable. */
7
+ idType?: 'objectId' | 'string';
8
+ /** Defaults to managed. `none` forbids SmartData timestamp fields in storage. */
9
+ timestamps?: 'managed' | 'none';
10
+ /** Validates the complete BSON envelope before model hydration. */
11
+ assertStoredDocument: (valueArg: unknown) => void;
12
+ }
13
+
14
+ export interface IOrdinaryPersistencePolicy {
15
+ readonly idType: 'objectId' | 'string';
16
+ readonly timestamps: 'managed' | 'none';
17
+ readonly assertStoredDocument: (valueArg: unknown) => void;
18
+ }
19
+
20
+ const policySymbol = Symbol.for('@push.rocks/smartdata.ordinaryPersistencePolicy');
21
+ const exactSymbol = Symbol.for('@push.rocks/smartdata.exactPersistencePolicy');
22
+
23
+ export const getOrdinaryPersistencePolicy = (modelArg: any): IOrdinaryPersistencePolicy | undefined => {
24
+ const policy = modelArg?.[policySymbol] as IOrdinaryPersistencePolicy | undefined;
25
+ if (policy && modelArg[exactSymbol]) {
26
+ throw new SmartdataPersistenceError('invalid_configuration',
27
+ 'Ordinary and exact persistence policies cannot be combined.');
28
+ }
29
+ return policy;
30
+ };
31
+
32
+ /** Opt into immutable whole-stored-document validation for an ordinary model. */
33
+ export const ordinaryPersistence = (opts: IOrdinaryPersistenceOptions) => {
34
+ if (!opts || Object.getPrototypeOf(opts) !== Object.prototype ||
35
+ Reflect.ownKeys(opts).some((keyArg) => typeof keyArg !== 'string' ||
36
+ !['idType', 'timestamps', 'assertStoredDocument'].includes(keyArg) ||
37
+ !('value' in Object.getOwnPropertyDescriptor(opts, keyArg)!)) ||
38
+ typeof opts.assertStoredDocument !== 'function' ||
39
+ (opts.idType !== undefined && !['objectId', 'string'].includes(opts.idType)) ||
40
+ (opts.timestamps !== undefined && !['managed', 'none'].includes(opts.timestamps))) {
41
+ throw new SmartdataPersistenceError('invalid_configuration', 'Invalid ordinary persistence policy.');
42
+ }
43
+ const policy: IOrdinaryPersistencePolicy = Object.freeze({
44
+ idType: opts.idType ?? 'objectId', timestamps: opts.timestamps ?? 'managed',
45
+ assertStoredDocument: opts.assertStoredDocument,
46
+ });
47
+ return <T extends new (...args: any[]) => any>(modelArg: T, _contextArg?: ClassDecoratorContext<T>): T => {
48
+ if (modelArg[policySymbol] || modelArg[exactSymbol]) {
49
+ throw new SmartdataPersistenceError('invalid_configuration', 'A persistence policy cannot be overridden.');
50
+ }
51
+ Object.defineProperty(modelArg, policySymbol, { value: policy });
52
+ return modelArg;
53
+ };
54
+ };
55
+
56
+ /** @internal Returns an isolated validated BSON document, never a hydrated model. */
57
+ export const validateOrdinaryStoredDocument = (
58
+ documentArg: unknown,
59
+ policyArg: IOrdinaryPersistencePolicy,
60
+ persistedFieldsArg: readonly string[],
61
+ ): Record<string, any> => {
62
+ const invalid = () => new SmartdataPersistenceError('invalid_document',
63
+ 'Stored document does not satisfy its ordinary persistence policy.');
64
+ if (!documentArg || typeof documentArg !== 'object' ||
65
+ ![Object.prototype, null].includes(Object.getPrototypeOf(documentArg))) throw invalid();
66
+ const allowed = new Set([...persistedFieldsArg, '_id']);
67
+ if (policyArg.timestamps === 'managed') { allowed.add('_createdAt'); allowed.add('_updatedAt'); }
68
+ for (const key of Reflect.ownKeys(documentArg)) {
69
+ if (typeof key !== 'string' || !allowed.has(key) ||
70
+ (policyArg.timestamps === 'none' && ['_createdAt', '_updatedAt'].includes(key)) ||
71
+ key === '_smartdataRevision' || !('value' in Object.getOwnPropertyDescriptor(documentArg, key)!)) throw invalid();
72
+ }
73
+ let clone: Record<string, any>;
74
+ try { clone = plugins.mongodb.BSON.deserialize(plugins.mongodb.BSON.serialize(documentArg)); }
75
+ catch { throw invalid(); }
76
+ if (policyArg.idType === 'string'
77
+ ? typeof clone._id !== 'string' || clone._id.trim().length === 0
78
+ : !(clone._id instanceof plugins.mongodb.ObjectId)) throw invalid();
79
+ if (policyArg.timestamps === 'managed' &&
80
+ (typeof clone._createdAt !== 'string' || typeof clone._updatedAt !== 'string')) throw invalid();
81
+ assertBsonWithoutMutation(clone, policyArg.assertStoredDocument, invalid);
82
+ // The assertion must not retain authority to mutate the returned document.
83
+ return plugins.mongodb.BSON.deserialize(plugins.mongodb.BSON.serialize(clone));
84
+ };
@@ -0,0 +1,62 @@
1
+ import * as plugins from './plugins.js';
2
+
3
+ export type TSmartdataPersistenceErrorCode =
4
+ | 'invalid_argument'
5
+ | 'invalid_configuration'
6
+ | 'invalid_document'
7
+ | 'unique_conflict'
8
+ | 'ambiguous_write'
9
+ | 'unsupported_operation';
10
+
11
+ /**
12
+ * Stable errors for ordinary SmartData model configuration and persistence.
13
+ *
14
+ * Exact-persistence models keep using SmartdataExactPersistenceError.
15
+ */
16
+ export class SmartdataPersistenceError extends Error {
17
+ public readonly code: TSmartdataPersistenceErrorCode;
18
+
19
+ constructor(
20
+ codeArg: TSmartdataPersistenceErrorCode,
21
+ messageArg: string,
22
+ optionsArg?: ErrorOptions,
23
+ ) {
24
+ super(messageArg, optionsArg);
25
+ this.name = 'SmartdataPersistenceError';
26
+ this.code = codeArg;
27
+ }
28
+ }
29
+
30
+ export const isMongoTransactionRetryError = (errorArg: unknown): boolean =>
31
+ errorArg instanceof plugins.mongodb.MongoServerError &&
32
+ (
33
+ errorArg.hasErrorLabel('TransientTransactionError') ||
34
+ errorArg.hasErrorLabel('UnknownTransactionCommitResult')
35
+ );
36
+
37
+ export const isMongoDuplicateKeyError = (
38
+ errorArg: unknown,
39
+ ): errorArg is plugins.mongodb.MongoServerError => {
40
+ return (
41
+ errorArg instanceof plugins.mongodb.MongoServerError &&
42
+ (errorArg.code === 11000 || errorArg.codeName === 'DuplicateKey')
43
+ );
44
+ };
45
+
46
+ export const normalizeOrdinaryPersistenceError = (
47
+ errorArg: unknown,
48
+ messageArg: string,
49
+ ): never => {
50
+ if (
51
+ errorArg instanceof SmartdataPersistenceError ||
52
+ isMongoTransactionRetryError(errorArg)
53
+ ) {
54
+ throw errorArg;
55
+ }
56
+ if (isMongoDuplicateKeyError(errorArg)) {
57
+ throw new SmartdataPersistenceError('unique_conflict', messageArg, {
58
+ cause: errorArg,
59
+ });
60
+ }
61
+ throw errorArg;
62
+ };