@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,372 @@
1
+ import * as plugins from './smartclickhouse.plugins.js';
2
+ import type { SmartClickHouseDb } from './smartclickhouse.classes.smartclickhouse.js';
3
+ import { ClickhouseQueryBuilder } from './smartclickhouse.classes.querybuilder.js';
4
+ import type {
5
+ IClickhouseTableOptions,
6
+ IColumnInfo,
7
+ IEngineConfig,
8
+ TClickhouseColumnType,
9
+ } from './smartclickhouse.types.js';
10
+ import { detectClickhouseType } from './smartclickhouse.types.js';
11
+ import { quoteClickHouseIdentifier as quoteIdentifier } from '../interfaces.js';
12
+ import { MetricWriter, type IMetricWriterOptions } from './classes.metricwriter.js';
13
+ import { Operation } from '../../core/classes.operation.js';
14
+ import { LosslessClientError } from '../../core/classes.error.js';
15
+ import type { IOperationOptions } from '../../core/interfaces.js';
16
+
17
+ export class ClickhouseTable<T extends Record<string, any>> {
18
+ // ---- STATIC FACTORY ----
19
+
20
+ public static async create<T extends Record<string, any>>(
21
+ db: SmartClickHouseDb,
22
+ options: IClickhouseTableOptions<T>,
23
+ ): Promise<ClickhouseTable<T>> {
24
+ const table = new ClickhouseTable<T>(db, options);
25
+ await table.setup();
26
+ return table;
27
+ }
28
+
29
+ // ---- INSTANCE ----
30
+
31
+ public db: SmartClickHouseDb;
32
+ public options: IClickhouseTableOptions<T> & { database: string; engine: IEngineConfig };
33
+ public columns: IColumnInfo[] = [];
34
+
35
+
36
+ constructor(db: SmartClickHouseDb, options: IClickhouseTableOptions<T>) {
37
+ this.db = db;
38
+ this.options = {
39
+ autoSchemaEvolution: true,
40
+ ...options,
41
+ database: options.database || db.options.database,
42
+ engine: options.engine || { engine: 'MergeTree' },
43
+ };
44
+ }
45
+
46
+ protected get qualifiedName(): string {
47
+ return quoteIdentifier(this.options.database) + '.' + quoteIdentifier(this.options.tableName);
48
+ }
49
+
50
+ private interval(value: string): string {
51
+ if (!/^[1-9][0-9]* (SECOND|MINUTE|HOUR|DAY|WEEK|MONTH|QUARTER|YEAR)$/i.test(value)) {
52
+ throw new RangeError('An interval must be a positive integer and a supported unit.');
53
+ }
54
+ return value;
55
+ }
56
+
57
+ // ---- SCHEMA MANAGEMENT ----
58
+
59
+ /**
60
+ * Creates the table if it doesn't exist and refreshes column metadata
61
+ */
62
+ public async setup(): Promise<void> {
63
+ const { database, tableName, engine, orderBy, partitionBy, primaryKey, ttl, retainDataForDays, columns } = this.options;
64
+
65
+ // Build column definitions
66
+ let columnDefs: string;
67
+ if (columns && columns.length > 0) {
68
+ columnDefs = columns.map((col) => {
69
+ let def = `${quoteIdentifier(col.name)} ${col.type}`;
70
+ if (col.defaultExpression) def += ` DEFAULT ${col.defaultExpression}`;
71
+ if (col.codec) def += ` CODEC(${col.codec})`;
72
+ return def;
73
+ }).join(',\n ');
74
+ } else {
75
+ // Default minimal schema — downstream code can add columns via auto-schema evolution
76
+ columnDefs = `timestamp DateTime64(3, 'Europe/Berlin'),\n message String`;
77
+ }
78
+
79
+ // Build engine clause
80
+ if (!['MergeTree', 'ReplacingMergeTree', 'SummingMergeTree', 'AggregatingMergeTree',
81
+ 'CollapsingMergeTree', 'VersionedCollapsingMergeTree'].includes(engine.engine)) {
82
+ throw new RangeError('Unsupported table engine.');
83
+ }
84
+ let engineClause: string = engine.engine;
85
+ if (engine.engine === 'ReplacingMergeTree' && engine.versionColumn) {
86
+ engineClause = `ReplacingMergeTree(${quoteIdentifier(engine.versionColumn)})`;
87
+ } else if (engine.engine === 'CollapsingMergeTree' && engine.signColumn) {
88
+ engineClause = `CollapsingMergeTree(${quoteIdentifier(engine.signColumn)})`;
89
+ } else if (engine.engine === 'VersionedCollapsingMergeTree' && engine.signColumn && engine.versionColumn) {
90
+ engineClause = `VersionedCollapsingMergeTree(${quoteIdentifier(engine.signColumn)}, ${quoteIdentifier(engine.versionColumn)})`;
91
+ } else {
92
+ engineClause = `${engine.engine}()`;
93
+ }
94
+
95
+ // Build ORDER BY
96
+ const orderByStr = Array.isArray(orderBy) ? orderBy.map(quoteIdentifier).join(', ') : quoteIdentifier(orderBy);
97
+
98
+ let createSQL = `
99
+ CREATE TABLE IF NOT EXISTS ${this.qualifiedName} (
100
+ ${columnDefs}
101
+ ) ENGINE = ${engineClause}`;
102
+
103
+ if (partitionBy) {
104
+ createSQL += `\n PARTITION BY ${partitionBy}`;
105
+ }
106
+
107
+ createSQL += `\n ORDER BY (${orderByStr})`;
108
+
109
+ if (primaryKey) {
110
+ const primaryKeyStr = Array.isArray(primaryKey) ? primaryKey.map(quoteIdentifier).join(', ') : quoteIdentifier(primaryKey);
111
+ createSQL += `\n PRIMARY KEY (${primaryKeyStr})`;
112
+ }
113
+
114
+ await this.db.clickhouseHttpClient.queryPromise(createSQL);
115
+
116
+ // Apply TTL if configured
117
+ if (ttl) {
118
+ await this.db.clickhouseHttpClient.queryPromise(`
119
+ ALTER TABLE ${this.qualifiedName}
120
+ MODIFY TTL toDateTime(${quoteIdentifier(String(ttl.column))}) + INTERVAL ${this.interval(ttl.interval)}
121
+ `);
122
+ } else if (retainDataForDays && retainDataForDays > 0) {
123
+ // Legacy shorthand
124
+ await this.db.clickhouseHttpClient.queryPromise(`
125
+ ALTER TABLE ${this.qualifiedName}
126
+ MODIFY TTL toDateTime(timestamp) + INTERVAL ${this.interval(retainDataForDays + ' DAY')}
127
+ `);
128
+ }
129
+
130
+ await this.updateColumns();
131
+ }
132
+
133
+ /**
134
+ * Refresh column metadata from system.columns
135
+ */
136
+ public async updateColumns(): Promise<IColumnInfo[]> {
137
+ this.columns = await this.db.clickhouseHttpClient.queryPromise(`
138
+ SELECT * FROM system.columns
139
+ WHERE database = {database:String}
140
+ AND table = {table:String}
141
+ `, { database: this.options.database, table: this.options.tableName });
142
+ return this.columns;
143
+ }
144
+
145
+ /**
146
+ * Auto-schema evolution: detect new columns from data and add them
147
+ */
148
+ public async syncSchema(data: Record<string, any>): Promise<void> {
149
+ const flatData = plugins.smartobject.toFlatObject(data);
150
+ for (const key of Object.keys(flatData)) {
151
+ if (key === 'timestamp') continue;
152
+ const value = flatData[key];
153
+ const clickhouseType = detectClickhouseType(value);
154
+ if (!clickhouseType) continue;
155
+ await this.ensureColumn(key, clickhouseType);
156
+ }
157
+ }
158
+
159
+ // ---- INSERT ----
160
+
161
+ /**
162
+ * Insert a single row
163
+ */
164
+ public async insert(data: Partial<T>): Promise<void> {
165
+
166
+ const storageDoc = await this.prepareDocument(data);
167
+ await this.executeInsert([storageDoc]);
168
+ }
169
+
170
+ /**
171
+ * Insert multiple rows
172
+ */
173
+ public async insertMany(data: Partial<T>[]): Promise<void> {
174
+ if (data.length === 0) return;
175
+
176
+ // Schema sync across all documents
177
+ if (this.options.autoSchemaEvolution) {
178
+ const allKeys = new Set<string>();
179
+ const sampleValues: Record<string, any> = {};
180
+ for (const doc of data) {
181
+ const flat = plugins.smartobject.toFlatObject(doc);
182
+ for (const [key, value] of Object.entries(flat)) {
183
+ if (!allKeys.has(key)) {
184
+ allKeys.add(key);
185
+ sampleValues[key] = value;
186
+ }
187
+ }
188
+ }
189
+ await this.syncSchema(sampleValues);
190
+ }
191
+
192
+ const storageDocs = data.map((doc) => this.flattenDocument(doc));
193
+ await this.executeInsert(storageDocs);
194
+ }
195
+
196
+ /**
197
+ * Insert in batches of configurable size
198
+ */
199
+ public async insertBatch(data: Partial<T>[], options?: { batchSize?: number }): Promise<void> {
200
+ const size = options?.batchSize ?? 500;
201
+ if (!Number.isSafeInteger(size) || size < 1) throw new RangeError('batchSize must be positive.');
202
+ for (let offset = 0; offset < data.length; offset += size) {
203
+ await this.insertMany(data.slice(offset, offset + size));
204
+ }
205
+ }
206
+
207
+ /** Await write() for backpressure and close() for the final acknowledged flush. */
208
+ public createInsertStream(options: IMetricWriterOptions = {}): MetricWriter<Partial<T>> {
209
+ return this.db.ownWriter(new MetricWriter<Partial<T>>(rows => this.insertMany(rows), options));
210
+ }
211
+
212
+ // ---- QUERY ----
213
+
214
+ /**
215
+ * Returns a fluent query builder for this table
216
+ */
217
+ public query(): ClickhouseQueryBuilder<T> {
218
+ return new ClickhouseQueryBuilder<T>(
219
+ this.options.tableName,
220
+ this.options.database,
221
+ this.db.clickhouseHttpClient,
222
+ );
223
+ }
224
+
225
+ // ---- UPDATE ----
226
+
227
+ /**
228
+ * Update rows matching a where condition (ClickHouse mutation - use sparingly)
229
+ */
230
+ public async update(
231
+ set: Partial<T>,
232
+ whereFn: (q: ClickhouseQueryBuilder<T>) => ClickhouseQueryBuilder<T>,
233
+ ): Promise<void> {
234
+ const qb = whereFn(new ClickhouseQueryBuilder<T>(this.options.tableName, this.options.database, this.db.clickhouseHttpClient));
235
+ const whereClause = qb.buildWhereClause();
236
+ if (!whereClause) {
237
+ throw new Error('UPDATE requires a WHERE clause. Use a where condition to target specific rows.');
238
+ }
239
+
240
+ const setClauses = Object.entries(set)
241
+ .map(([key, value]) => `${quoteIdentifier(key)} = ${qb.bind(value)}`)
242
+ .join(', ');
243
+
244
+ await this.db.clickhouseHttpClient.mutatePromise(
245
+ `ALTER TABLE ${this.qualifiedName} UPDATE ${setClauses} WHERE ${whereClause}`, qb.parameters
246
+ );
247
+ await this.waitForMutations();
248
+ }
249
+
250
+ // ---- DELETE ----
251
+
252
+ /**
253
+ * Delete rows matching a where condition (ClickHouse mutation)
254
+ */
255
+ public async deleteWhere(
256
+ whereFn: (q: ClickhouseQueryBuilder<T>) => ClickhouseQueryBuilder<T>,
257
+ ): Promise<void> {
258
+ const qb = whereFn(new ClickhouseQueryBuilder<T>(this.options.tableName, this.options.database, this.db.clickhouseHttpClient));
259
+ const whereClause = qb.buildWhereClause();
260
+ if (!whereClause) {
261
+ throw new Error('DELETE requires a WHERE clause.');
262
+ }
263
+
264
+ await this.db.clickhouseHttpClient.mutatePromise(
265
+ `ALTER TABLE ${this.qualifiedName} DELETE WHERE ${whereClause}`, qb.parameters
266
+ );
267
+ await this.waitForMutations();
268
+ }
269
+
270
+ /**
271
+ * Delete entries older than a given interval on a column
272
+ */
273
+ public async deleteOlderThan(column: keyof T & string, interval: string): Promise<void> {
274
+ await this.db.clickhouseHttpClient.mutatePromise(
275
+ `ALTER TABLE ${this.qualifiedName} DELETE WHERE ${quoteIdentifier(String(column))} < now() - INTERVAL ${this.interval(interval)}`
276
+ );
277
+ await this.waitForMutations();
278
+ }
279
+
280
+ /**
281
+ * Drop the entire table
282
+ */
283
+ public async drop(): Promise<void> {
284
+ await this.db.clickhouseHttpClient.queryPromise(
285
+ `DROP TABLE IF EXISTS ${this.qualifiedName}`
286
+ );
287
+ this.columns = [];
288
+ }
289
+
290
+ // ---- UTILITIES ----
291
+
292
+ /**
293
+ * Wait for all pending mutations on this table to complete
294
+ */
295
+ public async waitForMutations(options: IOperationOptions = {}): Promise<void> {
296
+ const operation = new Operation(options, this.db.options.timeoutMs);
297
+ try {
298
+ while (true) {
299
+ operation.check();
300
+ const rows = await this.db.clickhouseHttpClient.queryPromise(
301
+ 'SELECT count() AS cnt FROM system.mutations WHERE is_done = 0 AND database = {database:String} AND table = {table:String}',
302
+ { database: this.options.database, table: this.options.tableName }, { signal: operation.signal });
303
+ if (BigInt(rows[0]?.cnt ?? 0) === 0n) return;
304
+ await plugins.smartdelay.delayFor(100);
305
+ }
306
+ } finally { operation.dispose(); }
307
+ }
308
+
309
+ /**
310
+ * Get the total row count
311
+ */
312
+ public async getRowCount(): Promise<number> {
313
+ const result = await this.db.clickhouseHttpClient.queryPromise(`
314
+ SELECT count() AS cnt FROM ${this.qualifiedName} FORMAT JSONEachRow
315
+ `);
316
+ const count = Number(result[0]?.cnt ?? 0);
317
+ if (!Number.isSafeInteger(count)) throw new RangeError('Count exceeds a safe JavaScript integer.');
318
+ return count;
319
+ }
320
+
321
+ /**
322
+ * Optimize table (useful for ReplacingMergeTree deduplication)
323
+ */
324
+ public async optimize(final: boolean = false): Promise<void> {
325
+ const finalClause = final ? ' FINAL' : '';
326
+ await this.db.clickhouseHttpClient.queryPromise(
327
+ `OPTIMIZE TABLE ${this.qualifiedName}${finalClause}`
328
+ );
329
+ }
330
+
331
+ // ---- OBSERVATION ----
332
+
333
+ /**
334
+ * Watch for new entries via polling. Returns an RxJS Observable.
335
+ */
336
+ public watch(_options?: { pollInterval?: number }): plugins.smartrx.rxjs.Observable<T> {
337
+ return new plugins.smartrx.rxjs.Observable<T>(observer => observer.error(
338
+ new LosslessClientError('unsupported_capability',
339
+ 'Timestamp polling cannot guarantee delivery of equal-timestamp or late rows. Use an explicit stable ingestion cursor before enabling a watch.')));
340
+ }
341
+
342
+ // ---- PRIVATE HELPERS ----
343
+ private async ensureColumn(name: string, type: TClickhouseColumnType): Promise<void> {
344
+ if (this.columns.some(column => column.name === name)) return;
345
+ await this.db.clickhouseHttpClient.queryPromise(
346
+ `ALTER TABLE ${this.qualifiedName} ADD COLUMN IF NOT EXISTS ${quoteIdentifier(name)} ${type}`);
347
+ await this.updateColumns();
348
+ }
349
+
350
+ private flattenDocument(data: Partial<T>): Record<string, any> {
351
+ const flat = plugins.smartobject.toFlatObject(data);
352
+ const storageDoc: Record<string, any> = {};
353
+ for (const [key, value] of Object.entries(flat)) {
354
+ const type = detectClickhouseType(value);
355
+ if (type || key === 'timestamp') {
356
+ storageDoc[key] = value;
357
+ }
358
+ }
359
+ return storageDoc;
360
+ }
361
+
362
+ private async prepareDocument(data: Partial<T>): Promise<Record<string, any>> {
363
+ if (this.options.autoSchemaEvolution) {
364
+ await this.syncSchema(data as Record<string, any>);
365
+ }
366
+ return this.flattenDocument(data);
367
+ }
368
+
369
+ private async executeInsert(docs: Record<string, any>[]): Promise<void> {
370
+ await this.db.clickhouseHttpClient.insertPromise(this.options.database, this.options.tableName, docs);
371
+ }
372
+ }
@@ -0,0 +1,52 @@
1
+ import { ClickHouseConnection } from '../classes.clickhouseconnection.js';
2
+ import type { IOperationOptions } from '../../core/interfaces.js';
3
+ import type { IQueryOptions } from '../interfaces.js';
4
+
5
+ export interface IClickhouseHttpClientOptions {
6
+ username?: string;
7
+ password?: string;
8
+ url: string;
9
+ timeoutMs?: number;
10
+ }
11
+
12
+ /** Migrated public transport API, using the official streaming ClickHouse connector. */
13
+ export class ClickhouseHttpClient {
14
+ public readonly connection: ClickHouseConnection;
15
+ private readonly ownsConnection: boolean;
16
+ constructor(public options: IClickhouseHttpClientOptions, connection?: ClickHouseConnection) {
17
+ this.ownsConnection = !connection;
18
+ this.connection = connection ?? new ClickHouseConnection({ ...options, backend: 'clickhouse', database: 'default' });
19
+ }
20
+ public static async createAndStart(options: IClickhouseHttpClientOptions) {
21
+ const instance = new ClickhouseHttpClient(options);
22
+ await instance.start();
23
+ return instance;
24
+ }
25
+ public async start(): Promise<void> { await this.connection.connect(); }
26
+ public async close(): Promise<void> { if (this.ownsConnection) await this.connection.close(); }
27
+ public async ping(options: IOperationOptions = {}): Promise<boolean> { return (await this.connection.ready(options)).ready; }
28
+
29
+ public async queryPromise(query: string, values?: Record<string, unknown>, options: IQueryOptions = {}): Promise<any[]> {
30
+ const sql = query.trim().replace(/\s+FORMAT\s+JSONEachRow\s*;?$/i, '');
31
+ if (/^(SELECT|WITH|SHOW|DESCRIBE|DESC|EXISTS|EXPLAIN)\b/i.test(sql)) {
32
+ return this.connection.query({ sql, values }, options);
33
+ }
34
+ await this.connection.execute({ sql, values }, options);
35
+ return [];
36
+ }
37
+ public async queryTyped<T>(query: string, values?: Record<string, unknown>, options: IQueryOptions = {}): Promise<T[]> {
38
+ return this.queryPromise(query, values, options) as Promise<T[]>;
39
+ }
40
+ public async insertPromise(database: string, table: string, documents: Record<string, unknown>[]) {
41
+ return this.connection.insertQualified(database, table, documents);
42
+ }
43
+ public async insertBatch(database: string, table: string, documents: Record<string, unknown>[], batchSize = 10_000) {
44
+ if (!Number.isSafeInteger(batchSize) || batchSize < 1) throw new RangeError('batchSize must be positive.');
45
+ for (let i = 0; i < documents.length; i += batchSize) {
46
+ await this.insertPromise(database, table, documents.slice(i, i + batchSize));
47
+ }
48
+ }
49
+ public async mutatePromise(query: string, values?: Record<string, unknown>, options: IOperationOptions = {}): Promise<void> {
50
+ await this.connection.execute({ sql: query, values }, options);
51
+ }
52
+ }
@@ -0,0 +1,120 @@
1
+ import type { ClickhouseHttpClient } from './smartclickhouse.classes.httpclient.js';
2
+ import { ClickhouseResultSet } from './smartclickhouse.classes.resultset.js';
3
+ import type { TComparisonOperator } from './smartclickhouse.types.js';
4
+ import { quoteClickHouseIdentifier as quoteIdentifier } from '../interfaces.js';
5
+ import type { IClickHouseStatement, IQueryOptions } from '../interfaces.js';
6
+ import { LosslessClientError } from '../../core/classes.error.js';
7
+
8
+ interface IWhereClause { connector: 'AND' | 'OR'; expression: string }
9
+
10
+ /** Identifier methods quote names. Methods ending in Raw and having() accept trusted SQL expressions. */
11
+ export class ClickhouseQueryBuilder<T extends Record<string, any>> {
12
+ private selectColumns = ['*'];
13
+ private whereClauses: IWhereClause[] = [];
14
+ private orderByClauses: string[] = [];
15
+ private groupByClauses: string[] = [];
16
+ private havingClauses: string[] = [];
17
+ private limitValue: number | null = null;
18
+ private offsetValue: number | null = null;
19
+ private readonly values: Record<string, unknown> = {};
20
+
21
+ constructor(private tableName: string, private database: string, private httpClient: ClickhouseHttpClient) {}
22
+
23
+ public select<K extends keyof T & string>(...columns: K[]): this {
24
+ this.selectColumns = columns.map(quoteIdentifier); return this;
25
+ }
26
+ public selectRaw(...expressions: string[]): this { this.selectColumns = expressions; return this; }
27
+ public where<K extends keyof T & string>(column: K, operator: TComparisonOperator, value: unknown): this {
28
+ return this.and(column, operator, value);
29
+ }
30
+ public and<K extends keyof T & string>(column: K, operator: TComparisonOperator, value: unknown): this {
31
+ this.whereClauses.push({ connector: 'AND', expression: this.buildCondition(column, operator, value) }); return this;
32
+ }
33
+ public or<K extends keyof T & string>(column: K, operator: TComparisonOperator, value: unknown): this {
34
+ this.whereClauses.push({ connector: 'OR', expression: this.buildCondition(column, operator, value) }); return this;
35
+ }
36
+ public whereRaw(expression: string): this {
37
+ this.whereClauses.push({ connector: 'AND', expression }); return this;
38
+ }
39
+ public orderBy(column: (keyof T & string) | string, direction: 'ASC' | 'DESC' = 'ASC'): this {
40
+ if (!['ASC', 'DESC'].includes(direction)) throw new RangeError('Invalid sort direction.');
41
+ this.orderByClauses.push(quoteIdentifier(column) + ' ' + direction); return this;
42
+ }
43
+ public groupBy<K extends keyof T & string>(...columns: K[]): this {
44
+ this.groupByClauses.push(...columns.map(quoteIdentifier)); return this;
45
+ }
46
+ public having(expression: string): this { this.havingClauses.push(expression); return this; }
47
+ public limit(count: number): this { this.validateCount(count); this.limitValue = count; return this; }
48
+ public offset(count: number): this { this.validateCount(count); this.offsetValue = count; return this; }
49
+ private validateCount(count: number): void {
50
+ if (!Number.isSafeInteger(count) || count < 0) throw new RangeError('Limit and offset must be nonnegative safe integers.');
51
+ }
52
+ public async execute(options: IQueryOptions = {}): Promise<ClickhouseResultSet<T>> {
53
+ return new ClickhouseResultSet<T>(await this.httpClient.queryTyped<T>(this.toSQL(), this.parameters, options));
54
+ }
55
+ public async first(): Promise<T | null> { this.limitValue = 1; return (await this.execute()).first(); }
56
+ public async count(): Promise<number> {
57
+ const selected = this.selectColumns;
58
+ this.selectColumns = ['count() as _count'];
59
+ let sql: string;
60
+ try { sql = this.toSQL(); } finally { this.selectColumns = selected; }
61
+ const rows = await this.httpClient.queryTyped<{ _count: string }>(sql, this.parameters);
62
+ const count = Number(rows[0]?._count ?? 0);
63
+ if (!Number.isSafeInteger(count)) throw new RangeError('Count exceeds a safe JavaScript integer; select count() as a string.');
64
+ return count;
65
+ }
66
+ public async toArray(options: IQueryOptions = {}): Promise<T[]> { return (await this.execute(options)).toArray(); }
67
+ /** SQL contains typed placeholders. Pass parameters alongside it, or use toStatement(). */
68
+ public toSQL(): string {
69
+ const parts = ['SELECT ' + this.selectColumns.join(', '), 'FROM ' + quoteIdentifier(this.database) + '.' + quoteIdentifier(this.tableName)];
70
+ const where = this.buildWhereClause();
71
+ if (where) parts.push('WHERE ' + where);
72
+ if (this.groupByClauses.length) parts.push('GROUP BY ' + this.groupByClauses.join(', '));
73
+ if (this.havingClauses.length) parts.push('HAVING ' + this.havingClauses.join(' AND '));
74
+ if (this.orderByClauses.length) parts.push('ORDER BY ' + this.orderByClauses.join(', '));
75
+ if (this.limitValue !== null) parts.push('LIMIT ' + this.limitValue);
76
+ if (this.offsetValue !== null) parts.push('OFFSET ' + this.offsetValue);
77
+ return parts.join(' ');
78
+ }
79
+ public get parameters(): Record<string, unknown> { return { ...this.values }; }
80
+ public toStatement(): IClickHouseStatement { return { sql: this.toSQL(), values: this.parameters }; }
81
+ public buildWhereClause(): string {
82
+ return this.whereClauses.map((clause, index) => (index ? clause.connector + ' ' : '') + '(' + clause.expression + ')').join(' ');
83
+ }
84
+ /** Bind a scalar for table mutations as well as predicates. */
85
+ public bind(value: unknown): string {
86
+ let type: string;
87
+ if (value === null) type = 'Nullable(String)';
88
+ else if (typeof value === 'string') type = 'String';
89
+ else if (typeof value === 'boolean') type = 'Bool';
90
+ else if (typeof value === 'bigint') {
91
+ if (value < -(2n ** 255n) || value >= 2n ** 255n) throw new RangeError('Integer exceeds Int256.');
92
+ type = 'Int256'; value = value.toString();
93
+ } else if (typeof value === 'number' && Number.isFinite(value)) {
94
+ if (Number.isInteger(value) && !Number.isSafeInteger(value)) throw new RangeError('Use bigint for large integers.');
95
+ type = Number.isInteger(value) ? 'Int64' : 'Float64';
96
+ } else if (value instanceof Date && Number.isFinite(value.getTime())) {
97
+ type = "DateTime64(3, 'UTC')"; value = value.toISOString().replace('T', ' ').replace('Z', '');
98
+ } else throw new LosslessClientError('invalid_argument', 'Use a supported scalar query parameter.');
99
+ const key = 'p' + Object.keys(this.values).length;
100
+ this.values[key] = value;
101
+ return '{' + key + ':' + type + '}';
102
+ }
103
+ private buildCondition(column: string, operator: TComparisonOperator, value: unknown): string {
104
+ const name = quoteIdentifier(column);
105
+ if (!['=', '!=', '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN', 'BETWEEN'].includes(operator)) {
106
+ throw new RangeError('Invalid comparison operator.');
107
+ }
108
+ if (value === null && (operator === '=' || operator === '!=')) return name + (operator === '=' ? ' IS NULL' : ' IS NOT NULL');
109
+ if (operator === 'IN' || operator === 'NOT IN') {
110
+ if (!Array.isArray(value)) throw new RangeError('IN requires an array.');
111
+ if (!value.length) return operator === 'IN' ? '0' : '1';
112
+ return name + ' ' + operator + ' (' + value.map(item => this.bind(item)).join(', ') + ')';
113
+ }
114
+ if (operator === 'BETWEEN') {
115
+ if (!Array.isArray(value) || value.length !== 2) throw new RangeError('BETWEEN requires two values.');
116
+ return name + ' BETWEEN ' + this.bind(value[0]) + ' AND ' + this.bind(value[1]);
117
+ }
118
+ return name + ' ' + operator + ' ' + this.bind(value);
119
+ }
120
+ }
@@ -0,0 +1,44 @@
1
+ import * as plugins from './smartclickhouse.plugins.js';
2
+
3
+ export class ClickhouseResultSet<T> {
4
+ public rows: T[];
5
+ public rowCount: number;
6
+
7
+ constructor(rows: T[]) {
8
+ this.rows = rows;
9
+ this.rowCount = rows.length;
10
+ }
11
+
12
+ public first(): T | null {
13
+ return this.rows[0] ?? null;
14
+ }
15
+
16
+ public last(): T | null {
17
+ return this.rows[this.rows.length - 1] ?? null;
18
+ }
19
+
20
+ public isEmpty(): boolean {
21
+ return this.rows.length === 0;
22
+ }
23
+
24
+ public toArray(): T[] {
25
+ return this.rows;
26
+ }
27
+
28
+ public map<U>(fn: (row: T) => U): U[] {
29
+ return this.rows.map(fn);
30
+ }
31
+
32
+ public filter(fn: (row: T) => boolean): ClickhouseResultSet<T> {
33
+ return new ClickhouseResultSet<T>(this.rows.filter(fn));
34
+ }
35
+
36
+ public toObservable(): plugins.smartrx.rxjs.Observable<T> {
37
+ return new plugins.smartrx.rxjs.Observable<T>((observer) => {
38
+ for (const row of this.rows) {
39
+ observer.next(row);
40
+ }
41
+ observer.complete();
42
+ });
43
+ }
44
+ }