@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,289 @@
1
+ // classes.watcher.ts
2
+
3
+ import * as plugins from './plugins.js';
4
+ import * as interfaces from './interfaces.js';
5
+ import type { Bucket } from './classes.bucket.js';
6
+ import { EventEmitter } from 'node:events';
7
+
8
+ /**
9
+ * BucketWatcher monitors a storage bucket for changes (add/modify/delete)
10
+ * using a polling-based approach. Designed to follow the SmartdataDbWatcher pattern.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * const watcher = bucket.createWatcher({ prefix: 'uploads/', pollIntervalMs: 3000 });
15
+ *
16
+ * // RxJS Observable pattern
17
+ * watcher.changeSubject.subscribe((change) => {
18
+ * console.log('Change:', change);
19
+ * });
20
+ *
21
+ * // EventEmitter pattern
22
+ * watcher.on('change', (change) => console.log(change));
23
+ * watcher.on('error', (err) => console.error(err));
24
+ *
25
+ * await watcher.start();
26
+ * await watcher.readyDeferred.promise; // Wait for initial state
27
+ *
28
+ * // Later...
29
+ * await watcher.stop();
30
+ * ```
31
+ */
32
+ export class BucketWatcher extends EventEmitter {
33
+ /** Deferred that resolves when initial state is built and watcher is ready */
34
+ public readyDeferred = plugins.smartpromise.defer();
35
+
36
+ /** Observable for receiving change events (supports RxJS operators) */
37
+ public changeSubject: plugins.smartrx.rxjs.Observable<interfaces.IStorageChangeEvent | interfaces.IStorageChangeEvent[]>;
38
+
39
+ // Internal subjects and state
40
+ private rawSubject: plugins.smartrx.rxjs.Subject<interfaces.IStorageChangeEvent>;
41
+ private previousState: Map<string, interfaces.IStorageObjectState>;
42
+ private pollIntervalId: ReturnType<typeof setInterval> | null = null;
43
+ private isPolling = false;
44
+ private isStopped = false;
45
+
46
+ // Configuration
47
+ private readonly bucketRef: Bucket;
48
+ private readonly prefix: string;
49
+ private readonly pollIntervalMs: number;
50
+ private readonly bufferTimeMs?: number;
51
+ private readonly includeInitial: boolean;
52
+ private readonly pageSize: number;
53
+
54
+ constructor(bucketRef: Bucket, options: interfaces.IBucketWatcherOptions = {}) {
55
+ super();
56
+
57
+ this.bucketRef = bucketRef;
58
+ this.prefix = options.prefix ?? '';
59
+ this.pollIntervalMs = options.pollIntervalMs ?? 5000;
60
+ this.bufferTimeMs = options.bufferTimeMs;
61
+ this.includeInitial = options.includeInitial ?? false;
62
+ this.pageSize = options.pageSize ?? 1000;
63
+
64
+ // Initialize state tracking
65
+ this.previousState = new Map();
66
+
67
+ // Initialize raw subject for emitting changes
68
+ this.rawSubject = new plugins.smartrx.rxjs.Subject<interfaces.IStorageChangeEvent>();
69
+
70
+ // Configure the public observable with optional buffering
71
+ if (this.bufferTimeMs && this.bufferTimeMs > 0) {
72
+ this.changeSubject = this.rawSubject.pipe(
73
+ plugins.smartrx.rxjs.ops.bufferTime(this.bufferTimeMs),
74
+ plugins.smartrx.rxjs.ops.filter((events: interfaces.IStorageChangeEvent[]) => events.length > 0)
75
+ );
76
+ } else {
77
+ this.changeSubject = this.rawSubject.asObservable();
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Start watching the bucket for changes
83
+ */
84
+ public async start(): Promise<void> {
85
+ if (this.pollIntervalId !== null) {
86
+ console.log('BucketWatcher is already running');
87
+ return;
88
+ }
89
+
90
+ this.isStopped = false;
91
+
92
+ // Build initial state
93
+ await this.buildInitialState();
94
+
95
+ // Emit initial state as 'add' events if configured
96
+ if (this.includeInitial) {
97
+ for (const state of this.previousState.values()) {
98
+ this.emitChange({
99
+ type: 'add',
100
+ key: state.key,
101
+ size: state.size,
102
+ etag: state.etag,
103
+ lastModified: state.lastModified,
104
+ bucket: this.bucketRef.name,
105
+ });
106
+ }
107
+ }
108
+
109
+ // Mark as ready
110
+ this.readyDeferred.resolve();
111
+
112
+ // Start polling loop
113
+ this.pollIntervalId = setInterval(() => {
114
+ this.poll().catch((err) => {
115
+ this.emit('error', err);
116
+ });
117
+ }, this.pollIntervalMs);
118
+ }
119
+
120
+ /**
121
+ * Stop watching the bucket
122
+ */
123
+ public async stop(): Promise<void> {
124
+ this.isStopped = true;
125
+
126
+ if (this.pollIntervalId !== null) {
127
+ clearInterval(this.pollIntervalId);
128
+ this.pollIntervalId = null;
129
+ }
130
+
131
+ // Wait for any in-progress poll to complete
132
+ while (this.isPolling) {
133
+ await new Promise<void>((resolve) => setTimeout(resolve, 50));
134
+ }
135
+
136
+ this.rawSubject.complete();
137
+ }
138
+
139
+ /**
140
+ * Alias for stop() - for consistency with other APIs
141
+ */
142
+ public async close(): Promise<void> {
143
+ return this.stop();
144
+ }
145
+
146
+ /**
147
+ * Build the initial state by listing all objects with metadata
148
+ */
149
+ private async buildInitialState(): Promise<void> {
150
+ this.previousState.clear();
151
+
152
+ for await (const obj of this.listObjectsWithMetadata()) {
153
+ if (obj.Key) {
154
+ this.previousState.set(obj.Key, {
155
+ key: obj.Key,
156
+ etag: obj.ETag ?? '',
157
+ size: obj.Size ?? 0,
158
+ lastModified: obj.LastModified ?? new Date(0),
159
+ });
160
+ }
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Poll for changes by comparing current state against previous state
166
+ */
167
+ private async poll(): Promise<void> {
168
+ // Guard against overlapping polls
169
+ if (this.isPolling || this.isStopped) {
170
+ return;
171
+ }
172
+
173
+ this.isPolling = true;
174
+
175
+ try {
176
+ // Build current state
177
+ const currentState = new Map<string, interfaces.IStorageObjectState>();
178
+
179
+ for await (const obj of this.listObjectsWithMetadata()) {
180
+ if (this.isStopped) {
181
+ break;
182
+ }
183
+
184
+ if (obj.Key) {
185
+ currentState.set(obj.Key, {
186
+ key: obj.Key,
187
+ etag: obj.ETag ?? '',
188
+ size: obj.Size ?? 0,
189
+ lastModified: obj.LastModified ?? new Date(0),
190
+ });
191
+ }
192
+ }
193
+
194
+ if (!this.isStopped) {
195
+ this.detectChanges(currentState);
196
+ this.previousState = currentState;
197
+ }
198
+ } catch (err) {
199
+ this.emit('error', err);
200
+ } finally {
201
+ this.isPolling = false;
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Detect changes between current and previous state
207
+ */
208
+ private detectChanges(currentState: Map<string, interfaces.IStorageObjectState>): void {
209
+ // Detect added and modified objects
210
+ for (const [key, current] of currentState) {
211
+ const previous = this.previousState.get(key);
212
+
213
+ if (!previous) {
214
+ // New object - emit 'add' event
215
+ this.emitChange({
216
+ type: 'add',
217
+ key: current.key,
218
+ size: current.size,
219
+ etag: current.etag,
220
+ lastModified: current.lastModified,
221
+ bucket: this.bucketRef.name,
222
+ });
223
+ } else if (
224
+ previous.etag !== current.etag ||
225
+ previous.size !== current.size ||
226
+ previous.lastModified.getTime() !== current.lastModified.getTime()
227
+ ) {
228
+ // Object modified - emit 'modify' event
229
+ this.emitChange({
230
+ type: 'modify',
231
+ key: current.key,
232
+ size: current.size,
233
+ etag: current.etag,
234
+ lastModified: current.lastModified,
235
+ bucket: this.bucketRef.name,
236
+ });
237
+ }
238
+ }
239
+
240
+ // Detect deleted objects
241
+ for (const [key, previous] of this.previousState) {
242
+ if (!currentState.has(key)) {
243
+ // Object deleted - emit 'delete' event
244
+ this.emitChange({
245
+ type: 'delete',
246
+ key: previous.key,
247
+ bucket: this.bucketRef.name,
248
+ });
249
+ }
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Emit a change event via both RxJS Subject and EventEmitter
255
+ */
256
+ private emitChange(event: interfaces.IStorageChangeEvent): void {
257
+ this.rawSubject.next(event);
258
+ this.emit('change', event);
259
+ }
260
+
261
+ /**
262
+ * List objects with full metadata (ETag, Size, LastModified)
263
+ * This is a private method that yields full _Object data, not just keys
264
+ */
265
+ private async *listObjectsWithMetadata(): AsyncIterableIterator<plugins.s3._Object> {
266
+ let continuationToken: string | undefined;
267
+
268
+ do {
269
+ if (this.isStopped) {
270
+ return;
271
+ }
272
+
273
+ const command = new plugins.s3.ListObjectsV2Command({
274
+ Bucket: this.bucketRef.name,
275
+ Prefix: this.prefix,
276
+ MaxKeys: this.pageSize,
277
+ ContinuationToken: continuationToken,
278
+ });
279
+
280
+ const response = await this.bucketRef.smartbucketRef.storageClient.send(command);
281
+
282
+ for (const obj of response.Contents || []) {
283
+ yield obj;
284
+ }
285
+
286
+ continuationToken = response.NextContinuationToken;
287
+ } while (continuationToken);
288
+ }
289
+ }
@@ -0,0 +1,254 @@
1
+ import * as plugins from './plugins.js';
2
+ import * as interfaces from './interfaces.js';
3
+
4
+ export const reducePathDescriptorToPath = async (pathDescriptorArg: interfaces.IPathDecriptor): Promise<string> => {
5
+ let returnPath = ``
6
+ if (pathDescriptorArg.directory) {
7
+ if (pathDescriptorArg.path && plugins.path.isAbsolute(pathDescriptorArg.path)) {
8
+ console.warn('Directory is being ignored when path is absolute.');
9
+ returnPath = pathDescriptorArg.path;
10
+ } else if (pathDescriptorArg.path) {
11
+ returnPath = plugins.path.join(pathDescriptorArg.directory.getBasePath(), pathDescriptorArg.path);
12
+ }
13
+ } else if (pathDescriptorArg.path) {
14
+ returnPath = pathDescriptorArg.path;
15
+ } else {
16
+ throw new Error('You must specify either a path or a directory.');
17
+ }
18
+ if (returnPath.startsWith('/')) {
19
+ returnPath = returnPath.substring(1);
20
+ }
21
+ return returnPath;
22
+ }
23
+
24
+ // Storage Descriptor Normalization
25
+ export interface IStorageWarning {
26
+ code: string;
27
+ message: string;
28
+ }
29
+
30
+ export interface INormalizedStorageConfig {
31
+ endpointUrl: string;
32
+ host: string;
33
+ protocol: 'http' | 'https';
34
+ port?: number;
35
+ region: string;
36
+ credentials: {
37
+ accessKeyId: string;
38
+ secretAccessKey: string;
39
+ };
40
+ forcePathStyle: boolean;
41
+ }
42
+
43
+ function coerceBooleanMaybe(value: unknown): { value: boolean | undefined; warning?: IStorageWarning } {
44
+ if (typeof value === 'boolean') return { value };
45
+ if (typeof value === 'string') {
46
+ const v = value.trim().toLowerCase();
47
+ if (v === 'true' || v === '1') {
48
+ return {
49
+ value: true,
50
+ warning: {
51
+ code: 'SBK_S3_COERCED_USESSL',
52
+ message: `Coerced useSsl='${value}' (string) to boolean true.`
53
+ }
54
+ };
55
+ }
56
+ if (v === 'false' || v === '0') {
57
+ return {
58
+ value: false,
59
+ warning: {
60
+ code: 'SBK_S3_COERCED_USESSL',
61
+ message: `Coerced useSsl='${value}' (string) to boolean false.`
62
+ }
63
+ };
64
+ }
65
+ }
66
+ return { value: undefined };
67
+ }
68
+
69
+ function coercePortMaybe(port: unknown): { value: number | undefined; warning?: IStorageWarning } {
70
+ if (port === undefined || port === null || port === '') return { value: undefined };
71
+ const n = typeof port === 'number' ? port : Number(String(port).trim());
72
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0 || n > 65535) {
73
+ return {
74
+ value: undefined,
75
+ warning: {
76
+ code: 'SBK_S3_INVALID_PORT',
77
+ message: `Invalid port '${String(port)}' - expected integer in [1..65535].`
78
+ }
79
+ };
80
+ }
81
+ return { value: n };
82
+ }
83
+
84
+ function sanitizeEndpointString(raw: unknown): { value: string; warnings: IStorageWarning[] } {
85
+ const warnings: IStorageWarning[] = [];
86
+ let s = String(raw ?? '').trim();
87
+ if (s !== String(raw ?? '')) {
88
+ warnings.push({
89
+ code: 'SBK_S3_TRIMMED_ENDPOINT',
90
+ message: 'Trimmed surrounding whitespace from endpoint.'
91
+ });
92
+ }
93
+ return { value: s, warnings };
94
+ }
95
+
96
+ function parseEndpointHostPort(
97
+ endpoint: string,
98
+ provisionalProtocol: 'http' | 'https'
99
+ ): {
100
+ hadScheme: boolean;
101
+ host: string;
102
+ port?: number;
103
+ extras: {
104
+ droppedPath?: boolean;
105
+ droppedQuery?: boolean;
106
+ droppedCreds?: boolean
107
+ }
108
+ } {
109
+ let url: URL | undefined;
110
+ const extras: { droppedPath?: boolean; droppedQuery?: boolean; droppedCreds?: boolean } = {};
111
+
112
+ // Check if endpoint already has a scheme
113
+ const hasScheme = /^https?:\/\//i.test(endpoint);
114
+
115
+ // Try parsing as full URL first
116
+ try {
117
+ if (hasScheme) {
118
+ url = new URL(endpoint);
119
+ } else {
120
+ // Not a full URL; try host[:port] by attaching provisional scheme
121
+ // Remove anything after first '/' for safety
122
+ const cleanEndpoint = endpoint.replace(/\/.*/, '');
123
+ url = new URL(`${provisionalProtocol}://${cleanEndpoint}`);
124
+ }
125
+ } catch (e) {
126
+ throw new Error(`Unable to parse endpoint '${endpoint}'.`);
127
+ }
128
+
129
+ // Check for dropped components
130
+ if (url.username || url.password) extras.droppedCreds = true;
131
+ if (url.pathname && url.pathname !== '/') extras.droppedPath = true;
132
+ if (url.search) extras.droppedQuery = true;
133
+
134
+ const hadScheme = hasScheme;
135
+ const host = url.hostname; // hostnames lowercased by URL; IPs preserved
136
+ const port = url.port ? Number(url.port) : undefined;
137
+
138
+ return { hadScheme, host, port, extras };
139
+ }
140
+
141
+ export function normalizeStorageDescriptor(
142
+ input: plugins.tsclass.storage.IStorageDescriptor,
143
+ logger?: { warn: (msg: string) => void }
144
+ ): { normalized: INormalizedStorageConfig; warnings: IStorageWarning[] } {
145
+ const warnings: IStorageWarning[] = [];
146
+ const logWarn = (w: IStorageWarning) => {
147
+ warnings.push(w);
148
+ if (logger) {
149
+ logger.warn(`[SmartBucket] ${w.code}: ${w.message}`);
150
+ } else {
151
+ console.warn(`[SmartBucket] ${w.code}: ${w.message}`);
152
+ }
153
+ };
154
+
155
+ // Coerce and sanitize inputs
156
+ const { value: coercedUseSsl, warning: useSslWarn } = coerceBooleanMaybe((input as any).useSsl);
157
+ if (useSslWarn) logWarn(useSslWarn);
158
+
159
+ const { value: coercedPort, warning: portWarn } = coercePortMaybe((input as any).port);
160
+ if (portWarn) logWarn(portWarn);
161
+
162
+ const { value: endpointStr, warnings: endpointSanWarnings } = sanitizeEndpointString((input as any).endpoint);
163
+ endpointSanWarnings.forEach(logWarn);
164
+
165
+ if (!endpointStr) {
166
+ throw new Error('Storage endpoint is required (got empty string). Provide hostname or URL.');
167
+ }
168
+
169
+ // Provisional protocol selection for parsing host:port forms
170
+ const provisionalProtocol: 'http' | 'https' = coercedUseSsl === false ? 'http' : 'https';
171
+
172
+ const { hadScheme, host, port: epPort, extras } = parseEndpointHostPort(endpointStr, provisionalProtocol);
173
+
174
+ if (extras.droppedCreds) {
175
+ logWarn({
176
+ code: 'SBK_S3_DROPPED_CREDENTIALS',
177
+ message: 'Ignored credentials in endpoint URL.'
178
+ });
179
+ }
180
+ if (extras.droppedPath) {
181
+ logWarn({
182
+ code: 'SBK_S3_DROPPED_PATH',
183
+ message: 'Removed path segment from endpoint URL; S3 endpoint should be host[:port] only.'
184
+ });
185
+ }
186
+ if (extras.droppedQuery) {
187
+ logWarn({
188
+ code: 'SBK_S3_DROPPED_QUERY',
189
+ message: 'Removed query string from endpoint URL; S3 endpoint should be host[:port] only.'
190
+ });
191
+ }
192
+
193
+ // Final protocol decision
194
+ let finalProtocol: 'http' | 'https';
195
+ if (hadScheme) {
196
+ // Scheme from endpoint wins
197
+ const schemeFromEndpoint = endpointStr.trim().toLowerCase().startsWith('http://') ? 'http' : 'https';
198
+ finalProtocol = schemeFromEndpoint;
199
+ if (typeof coercedUseSsl === 'boolean') {
200
+ const expected = coercedUseSsl ? 'https' : 'http';
201
+ if (expected !== finalProtocol) {
202
+ logWarn({
203
+ code: 'SBK_S3_SCHEME_CONFLICT',
204
+ message: `useSsl=${String(coercedUseSsl)} conflicts with endpoint scheme '${finalProtocol}'; using endpoint scheme.`
205
+ });
206
+ }
207
+ }
208
+ } else {
209
+ if (typeof coercedUseSsl === 'boolean') {
210
+ finalProtocol = coercedUseSsl ? 'https' : 'http';
211
+ } else {
212
+ finalProtocol = 'https';
213
+ logWarn({
214
+ code: 'SBK_S3_GUESSED_PROTOCOL',
215
+ message: "No scheme in endpoint and useSsl not provided; defaulting to 'https'."
216
+ });
217
+ }
218
+ }
219
+
220
+ // Final port decision
221
+ let finalPort: number | undefined = undefined;
222
+ if (coercedPort !== undefined && epPort !== undefined && coercedPort !== epPort) {
223
+ logWarn({
224
+ code: 'SBK_S3_PORT_CONFLICT',
225
+ message: `Port in config (${coercedPort}) conflicts with endpoint port (${epPort}); using config port.`
226
+ });
227
+ finalPort = coercedPort;
228
+ } else {
229
+ finalPort = (coercedPort !== undefined) ? coercedPort : epPort;
230
+ }
231
+
232
+ // Build canonical endpoint URL (origin only, no trailing slash)
233
+ const url = new URL(`${finalProtocol}://${host}`);
234
+ if (finalPort !== undefined) url.port = String(finalPort);
235
+ const endpointUrl = url.origin;
236
+
237
+ const region = input.region || 'us-east-1';
238
+
239
+ return {
240
+ normalized: {
241
+ endpointUrl,
242
+ host,
243
+ protocol: finalProtocol,
244
+ port: finalPort,
245
+ region,
246
+ credentials: {
247
+ accessKeyId: input.accessKey,
248
+ secretAccessKey: input.accessSecret,
249
+ },
250
+ forcePathStyle: true,
251
+ },
252
+ warnings,
253
+ };
254
+ }
@@ -0,0 +1,13 @@
1
+ export * from './classes.smartbucket.js';
2
+ export * from './classes.bucket.js';
3
+ export * from './classes.exactuploaderror.js';
4
+ export * from './classes.exactpathpurgeerror.js';
5
+ export * from './classes.directory.js';
6
+ export * from './classes.file.js';
7
+ export * from './classes.listcursor.js';
8
+ export * from './classes.metadata.js';
9
+ export * from './classes.trash.js';
10
+ export * from './classes.watcher.js';
11
+ export * from './interfaces.js';
12
+ export { ObjectStorageConnection } from './classes.connection.js';
13
+ export type { IObjectStorageConnectionOptions } from './classes.connection.js';