@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,350 @@
1
+ import * as plugins from './plugins.mariadb.js';
2
+ import { SqlTable } from './classes.sqltable.js';
3
+ import { SqlPool, type SqlPooledConnection } from './internal.sqlpool.js';
4
+ import { Operation } from '../core/classes.operation.js';
5
+ import { LosslessClientError, backendError } from '../core/classes.error.js';
6
+ import type { ICapabilities, IOperationOptions, IReadiness } from '../core/interfaces.js';
7
+ import { quoteIdentifier, resultLimits, valueBytes } from './interfaces.js';
8
+ import type { IInsertOptions, IQueryOptions, ISqlConnectionOptions, ISqlMutationResult, ISqlStatement, TSqlValue } from './interfaces.js';
9
+
10
+ interface ILease {
11
+ connection: SqlPooledConnection;
12
+ operation: Operation;
13
+ close: () => Promise<void>;
14
+ done: Promise<void>;
15
+ }
16
+
17
+ const validateStatement = (statement: ISqlStatement): void => {
18
+ if (!statement || typeof statement.sql !== 'string' || !statement.sql.trim()) {
19
+ throw new LosslessClientError('invalid_argument', 'A SQL statement is required.');
20
+ }
21
+ if (statement.values !== undefined && !Array.isArray(statement.values)) {
22
+ throw new LosslessClientError('invalid_argument', 'SQL values must be an array.');
23
+ }
24
+ for (const value of statement.values ?? []) {
25
+ if (value !== null && !['string', 'number', 'bigint', 'boolean'].includes(typeof value) &&
26
+ !(value instanceof Date) && !(value instanceof Uint8Array)) {
27
+ throw new LosslessClientError('invalid_argument', 'SQL parameters must be supported scalar, date or binary values.');
28
+ }
29
+ if (value instanceof Date && !Number.isFinite(value.getTime())) {
30
+ throw new LosslessClientError('invalid_argument', 'Invalid date parameter.');
31
+ }
32
+ if (typeof value === 'number' && (!Number.isFinite(value) ||
33
+ (Number.isInteger(value) && !Number.isSafeInteger(value)))) {
34
+ throw new LosslessClientError('invalid_argument', 'Use bigint or a decimal string for exact large numbers.');
35
+ }
36
+ }
37
+ };
38
+
39
+ const boundValues = (statement: ISqlStatement): TSqlValue[] =>
40
+ (statement.values ?? []).map(value => value instanceof Date
41
+ ? value.toISOString().replace('T', ' ').replace('Z', '')
42
+ : value instanceof Uint8Array ? Buffer.from(value) : value);
43
+
44
+ const mutationResult = (result: plugins.mariadb.UpsertResult): ISqlMutationResult => ({
45
+ affectedRows: result.affectedRows,
46
+ ...(result.insertId === undefined ? {} : { insertId: BigInt(result.insertId) }),
47
+ completion: 'acknowledged',
48
+ });
49
+
50
+ /** One MariaDB-protocol pool. SQLDB uses the same wire adapter with its own capabilities. */
51
+ export class SqlConnection {
52
+ public readonly backend: 'sqldb' | 'mariadb';
53
+ public readonly capabilities: ICapabilities;
54
+ private pool?: SqlPool;
55
+ private closing?: Promise<void>;
56
+ private connecting?: Promise<void>;
57
+ private closed = false;
58
+ private readonly acquisitions = new Set<Promise<ILease>>();
59
+ private readonly leases = new Set<ILease>();
60
+ private readonly pendingOperations = new Set<Operation>();
61
+
62
+ constructor(public readonly options: ISqlConnectionOptions) {
63
+ if (!['sqldb', 'mariadb'].includes(options.backend) || !options.host || !options.database || !options.user) {
64
+ throw new LosslessClientError('invalid_argument', 'SQL backend, host, user and database are required.');
65
+ }
66
+ if (options.backend === 'sqldb' && options.tls) {
67
+ throw new LosslessClientError('unsupported_capability', 'SQLDB does not yet support TLS.');
68
+ }
69
+ this.backend = options.backend;
70
+ this.capabilities = Object.freeze({ backend: this.backend, transactions: 'available',
71
+ changeStreams: 'unsupported', objectMetrics: 'unsupported',
72
+ diskResidentScans: this.backend === 'sqldb' ? 'unsupported' : 'available' });
73
+ }
74
+
75
+ public connect(options: IOperationOptions = {}): Promise<void> {
76
+ if (this.closed || this.closing) return Promise.reject(new LosslessClientError('closed', 'SQL connection is closed.'));
77
+ if (this.connecting) return this.connecting;
78
+ if (this.pool) return Promise.resolve();
79
+ const limit = this.options.connectionLimit ?? 10;
80
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1024) {
81
+ return Promise.reject(new LosslessClientError('invalid_argument', 'connectionLimit must be between 1 and 1024.'));
82
+ }
83
+ this.pool = new SqlPool({
84
+ host: this.options.host, port: this.options.port ?? 3306, user: this.options.user,
85
+ password: this.options.password, database: this.options.database,
86
+ connectTimeout: this.options.timeoutMs ?? 30_000,
87
+ ssl: this.options.tls, bigIntAsNumber: false, decimalAsNumber: false,
88
+ dateStrings: true, timezone: this.backend === 'mariadb' ? '+00:00' : undefined,
89
+ multipleStatements: false,
90
+ }, limit);
91
+ this.connecting = (async () => {
92
+ try { await this.query({ sql: 'SELECT 1 AS ready' }, { ...options, maxRows: 1 }); }
93
+ catch (error) {
94
+ const pool = this.pool;
95
+ this.pool = undefined;
96
+ await pool?.end();
97
+ throw error;
98
+ } finally { this.connecting = undefined; }
99
+ })();
100
+ return this.connecting;
101
+ }
102
+
103
+ public async ready(options: IOperationOptions = {}): Promise<IReadiness> {
104
+ try { await this.query({ sql: 'SELECT 1 AS ready' }, { ...options, maxRows: 1 }); return { ready: true }; }
105
+ catch (error) { return { ready: false, reason: error instanceof LosslessClientError ? error.code : 'unavailable' }; }
106
+ }
107
+
108
+ public table<Row extends { [Key in keyof Row]: TSqlValue }>(name: string): SqlTable<Row> {
109
+ return new SqlTable<Row>(this, name);
110
+ }
111
+
112
+ private acquire(options: IOperationOptions): Promise<ILease> {
113
+ if (!this.pool || this.closed || this.closing) {
114
+ return Promise.reject(new LosslessClientError(this.closed ? 'closed' : 'not_connected', 'SQL connection is not open.'));
115
+ }
116
+ const pool = this.pool;
117
+ const operation = new Operation(options, this.options.timeoutMs);
118
+ this.pendingOperations.add(operation);
119
+ const acquisition = (async (): Promise<ILease> => {
120
+ let connection: SqlPooledConnection | undefined;
121
+ try {
122
+ operation.check();
123
+ connection = await pool.getConnection(operation);
124
+ operation.check();
125
+ const acquired = connection;
126
+ const settled = Promise.withResolvers<void>();
127
+ let disposal: Promise<void> | undefined;
128
+ const abort = () => acquired.destroy();
129
+ const lease: ILease = { connection: acquired, operation, done: settled.promise,
130
+ close: () => disposal ??= (async () => {
131
+ try { await acquired.release(); }
132
+ finally {
133
+ operation.signal.removeEventListener('abort', abort);
134
+ operation.dispose();
135
+ this.pendingOperations.delete(operation);
136
+ this.leases.delete(lease);
137
+ settled.resolve();
138
+ }
139
+ })(),
140
+ };
141
+ operation.signal.addEventListener('abort', abort, { once: true });
142
+ this.leases.add(lease);
143
+ return lease;
144
+ } catch (error) {
145
+ connection?.destroy();
146
+ await connection?.release();
147
+ operation.dispose();
148
+ this.pendingOperations.delete(operation);
149
+ throw operation.signal.aborted ? operation.signal.reason : backendError(error);
150
+ }
151
+ })();
152
+ this.acquisitions.add(acquisition);
153
+ void acquisition.finally(() => this.acquisitions.delete(acquisition)).catch(() => {});
154
+ return acquisition;
155
+ }
156
+
157
+ public async execute(statement: ISqlStatement, options: IOperationOptions = {}): Promise<ISqlMutationResult> {
158
+ validateStatement(statement);
159
+ const lease = await this.acquire(options);
160
+ let dispatched = false;
161
+ try {
162
+ lease.operation.check();
163
+ dispatched = true;
164
+ const result = await lease.connection.execute<plugins.mariadb.UpsertResult>(statement.sql, boundValues(statement));
165
+ return mutationResult(result);
166
+ } catch (error) {
167
+ if (dispatched && (lease.operation.signal.aborted || !lease.connection.isValid())) {
168
+ throw new LosslessClientError('ambiguous_write', 'SQL write outcome is unknown after connection loss.', { cause: error });
169
+ }
170
+ throw backendError(error);
171
+ } finally { await lease.close(); }
172
+ }
173
+
174
+ public async query<Row = Record<string, unknown>>(statement: ISqlStatement, options: IQueryOptions = {}): Promise<Row[]> {
175
+ const limits = resultLimits(options);
176
+ const rows: Row[] = [];
177
+ let bytes = 0;
178
+ for await (const row of this.stream<Row>(statement, options)) {
179
+ bytes += valueBytes(row);
180
+ if (rows.length >= limits.rows || bytes > limits.bytes) {
181
+ throw new LosslessClientError('result_limit', 'SQL result exceeded its materialization limit. Use stream().');
182
+ }
183
+ rows.push(row);
184
+ }
185
+ return rows;
186
+ }
187
+
188
+ public async *stream<Row = Record<string, unknown>>(statement: ISqlStatement,
189
+ options: IOperationOptions = {}): AsyncGenerator<Row> {
190
+ validateStatement(statement);
191
+ const lease = await this.acquire(options);
192
+ let readable: plugins.stream.Readable | undefined;
193
+ let completed = false;
194
+ try {
195
+ lease.operation.check();
196
+ readable = lease.connection.queryStream(statement.sql, boundValues(statement));
197
+ // A paused consumer still owns a socket. Stream close independently returns its lease.
198
+ readable.on('error', () => {});
199
+ readable.once('close', () => { void lease.close().catch(() => {}); });
200
+ for await (const row of readable) {
201
+ lease.operation.check();
202
+ yield row as Row;
203
+ }
204
+ lease.operation.check();
205
+ completed = true;
206
+ } catch (error) {
207
+ throw lease.operation.signal.aborted ? lease.operation.signal.reason : backendError(error);
208
+ } finally {
209
+ if (!completed) lease.connection.destroy();
210
+ readable?.destroy();
211
+ await lease.close();
212
+ }
213
+ }
214
+
215
+ public async insert(table: string, rows: AsyncIterable<Record<string, TSqlValue>> | Iterable<Record<string, TSqlValue>>,
216
+ options: IInsertOptions = {}): Promise<ISqlMutationResult> {
217
+ const tableName = quoteIdentifier(table);
218
+ const batchSize = options.batchSize ?? 500;
219
+ const maxBytes = options.maxBatchBytes ?? 4 * 1024 * 1024;
220
+ if (!Number.isSafeInteger(batchSize) || batchSize < 1 || !Number.isSafeInteger(maxBytes) || maxBytes < 1) {
221
+ throw new LosslessClientError('invalid_argument', 'Insert batch limits must be positive integers.');
222
+ }
223
+ const operation = new Operation(options, this.options.timeoutMs);
224
+ if (!this.pool || this.closed || this.closing) {
225
+ operation.dispose();
226
+ throw new LosslessClientError(this.closed ? 'closed' : 'not_connected', 'SQL connection is not open.');
227
+ }
228
+ this.pendingOperations.add(operation);
229
+ let columns: string[] | undefined;
230
+ let batch: TSqlValue[][] = [], bytes = 0, affectedRows = 0;
231
+ const flush = async () => {
232
+ if (!batch.length) return;
233
+ const result = await this.execute({ sql: `INSERT INTO ${tableName} (${columns!.map(quoteIdentifier).join(',')}) VALUES ${batch.map(() => '(' + columns!.map(() => '?').join(',') + ')').join(',')}`,
234
+ values: batch.flat() }, { signal: operation.signal, timeoutMs: options.timeoutMs });
235
+ affectedRows += result.affectedRows;
236
+ batch = []; bytes = 0;
237
+ };
238
+ try {
239
+ for await (const row of operation.iterate(rows)) {
240
+ operation.check();
241
+ columns ??= Object.keys(row);
242
+ if (!columns.length || Object.keys(row).length !== columns.length || columns.some(key => !Object.hasOwn(row, key))) {
243
+ throw new LosslessClientError('invalid_argument', 'Every inserted row must have the same columns.');
244
+ }
245
+ const size = valueBytes(row);
246
+ if (size > maxBytes) throw new LosslessClientError('invalid_argument', 'Insert row exceeds maxBatchBytes.');
247
+ if (batch.length && (batch.length >= batchSize || bytes + size > maxBytes)) await flush();
248
+ batch.push(columns.map(key => row[key])); bytes += size;
249
+ }
250
+ await flush();
251
+ return { affectedRows, completion: 'acknowledged' };
252
+ } catch (error) {
253
+ if (affectedRows) throw new LosslessClientError(error instanceof LosslessClientError && error.code === 'ambiguous_write'
254
+ ? 'ambiguous_write' : 'partial_write', 'Bulk insert stopped after acknowledged batches.', { cause: error, acknowledgedRows: affectedRows });
255
+ throw error;
256
+ } finally { operation.dispose(); this.pendingOperations.delete(operation); }
257
+ }
258
+
259
+ public async transaction<Result>(callback: (transaction: SqlTransaction) => Promise<Result>,
260
+ options: IOperationOptions = {}): Promise<Result> {
261
+ const lease = await this.acquire(options);
262
+ const transaction = new SqlTransaction(lease);
263
+ let committing = false;
264
+ try {
265
+ await lease.connection.beginTransaction();
266
+ const value = await lease.operation.wait(callback(transaction));
267
+ await transaction.finish();
268
+ lease.operation.check();
269
+ committing = true;
270
+ await lease.connection.commit();
271
+ return value;
272
+ } catch (error) {
273
+ await transaction.finish().catch(() => {});
274
+ if (committing && !lease.connection.isValid()) {
275
+ throw new LosslessClientError('ambiguous_write', 'Transaction commit outcome is unknown.', { cause: error });
276
+ }
277
+ if (lease.operation.signal.aborted) throw lease.operation.signal.reason;
278
+ try { await lease.connection.rollback(); }
279
+ catch (rollbackError) { throw new AggregateError([error, rollbackError], 'Transaction and rollback failed.'); }
280
+ throw error;
281
+ } finally { await lease.close(); }
282
+ }
283
+
284
+ public close(): Promise<void> {
285
+ return this.closing ??= (async () => {
286
+ this.closed = true;
287
+ for (const operation of this.pendingOperations) operation.cancel();
288
+ await Promise.allSettled([...this.acquisitions]);
289
+ for (const lease of this.leases) lease.operation.cancel();
290
+ await Promise.all([...this.leases].map(lease => lease.close()));
291
+ await this.connecting?.catch(() => {});
292
+ const pool = this.pool;
293
+ this.pool = undefined;
294
+ await pool?.end();
295
+ })();
296
+ }
297
+ }
298
+
299
+ /** Bound to a single transaction callback and physical connection. */
300
+ export class SqlTransaction {
301
+ private active?: Promise<unknown>;
302
+ private failure?: unknown;
303
+ private finished = false;
304
+ constructor(private readonly lease: ILease) {}
305
+
306
+ private run<Result>(action: () => Promise<Result>): Promise<Result> {
307
+ if (this.finished || this.active) return Promise.reject(new LosslessClientError('invalid_argument', 'Transaction is closed or already has an active operation.'));
308
+ if (this.failure) return Promise.reject(this.failure);
309
+ this.lease.operation.check();
310
+ const result = action().catch(error => { this.failure = error; throw error; });
311
+ this.active = result;
312
+ void result.finally(() => { if (this.active === result) this.active = undefined; }).catch(() => {});
313
+ return result;
314
+ }
315
+
316
+ public execute(statement: ISqlStatement): Promise<ISqlMutationResult> {
317
+ validateStatement(statement);
318
+ return this.run(async () => mutationResult(await this.lease.connection.execute(statement.sql, boundValues(statement))));
319
+ }
320
+
321
+ public query<Row = Record<string, unknown>>(statement: ISqlStatement, options: Pick<IQueryOptions, 'maxRows' | 'maxBytes'> = {}): Promise<Row[]> {
322
+ validateStatement(statement);
323
+ const limits = resultLimits(options);
324
+ return this.run(async () => {
325
+ const rows: Row[] = [];
326
+ let bytes = 0;
327
+ const source = this.lease.connection.queryStream(statement.sql, boundValues(statement));
328
+ try {
329
+ for await (const row of source) {
330
+ this.lease.operation.check();
331
+ bytes += valueBytes(row);
332
+ if (rows.length >= limits.rows || bytes > limits.bytes) {
333
+ throw new LosslessClientError('result_limit', 'Transaction result exceeded its materialization limit.');
334
+ }
335
+ rows.push(row as Row);
336
+ }
337
+ return rows;
338
+ } catch (error) {
339
+ this.lease.connection.destroy();
340
+ throw error;
341
+ } finally { source.destroy(); }
342
+ });
343
+ }
344
+
345
+ public async finish(): Promise<void> {
346
+ this.finished = true;
347
+ await this.active;
348
+ if (this.failure) throw this.failure;
349
+ }
350
+ }
@@ -0,0 +1,70 @@
1
+ import type { SqlConnection } from './classes.sqlconnection.js';
2
+ import type { IInsertOptions, IQueryOptions, ISqlStatement, TSqlValue } from './interfaces.js';
3
+ import type { IOperationOptions } from '../core/interfaces.js';
4
+ import { quoteIdentifier } from './interfaces.js';
5
+ import { LosslessClientError } from '../core/classes.error.js';
6
+
7
+ export type TSqlSelector<Row> = Partial<{ [Key in keyof Row]: TSqlValue }>;
8
+ export interface ISqlSelectOptions<Row> extends IQueryOptions {
9
+ columns?: (keyof Row & string)[];
10
+ orderBy?: { column: keyof Row & string; direction?: 'ASC' | 'DESC' }[];
11
+ limit?: number;
12
+ }
13
+
14
+ /** A typed handle to an existing relational table. Schema creation is explicit SQL or a migration. */
15
+ export class SqlTable<Row extends { [Key in keyof Row]: TSqlValue }> {
16
+ constructor(private readonly connection: SqlConnection, public readonly name: string) { quoteIdentifier(name); }
17
+
18
+ private where(selector: TSqlSelector<Row>, required = false): ISqlStatement {
19
+ const entries = Object.entries(selector);
20
+ if (required && !entries.length) throw new LosslessClientError('invalid_argument', 'A nonempty selector is required for table mutations.');
21
+ const values: TSqlValue[] = [];
22
+ const sql = entries.map(([key, value]) => {
23
+ if (value === undefined) throw new LosslessClientError('invalid_argument', 'Undefined selector values are not allowed.');
24
+ if (value === null) return quoteIdentifier(key) + ' IS NULL';
25
+ values.push(value as TSqlValue);
26
+ return quoteIdentifier(key) + ' = ?';
27
+ }).join(' AND ');
28
+ return { sql: sql ? ' WHERE ' + sql : '', values };
29
+ }
30
+
31
+ public selectStatement(selector: TSqlSelector<Row> = {}, options: ISqlSelectOptions<Row> = {}): ISqlStatement {
32
+ const where = this.where(selector);
33
+ if (options.columns?.length === 0) throw new LosslessClientError('invalid_argument', 'Select at least one column.');
34
+ let sql = 'SELECT ' + (options.columns?.map(quoteIdentifier).join(', ') ?? '*') + ' FROM ' + quoteIdentifier(this.name) + where.sql;
35
+ if (options.orderBy?.length) sql += ' ORDER BY ' + options.orderBy.map(order => {
36
+ const direction = order.direction ?? 'ASC';
37
+ if (!['ASC', 'DESC'].includes(direction)) throw new LosslessClientError('invalid_argument', 'Invalid sort direction.');
38
+ return quoteIdentifier(order.column) + ' ' + direction;
39
+ }).join(', ');
40
+ if (options.limit !== undefined) {
41
+ if (!Number.isSafeInteger(options.limit) || options.limit < 0) throw new LosslessClientError('invalid_argument', 'Limit must be a nonnegative safe integer.');
42
+ sql += ' LIMIT ' + options.limit;
43
+ }
44
+ return { sql, values: where.values };
45
+ }
46
+
47
+ public query(selector: TSqlSelector<Row> = {}, options: ISqlSelectOptions<Row> = {}): Promise<Row[]> {
48
+ return this.connection.query<Row>(this.selectStatement(selector, options), options);
49
+ }
50
+ public stream(selector: TSqlSelector<Row> = {}, options: ISqlSelectOptions<Row> = {}): AsyncGenerator<Row> {
51
+ return this.connection.stream<Row>(this.selectStatement(selector, options), options);
52
+ }
53
+ public insert(rows: Iterable<Row> | AsyncIterable<Row>, options: IInsertOptions = {}) {
54
+ return this.connection.insert(this.name, rows, options);
55
+ }
56
+ public update(selector: TSqlSelector<Row>, changes: Partial<Row>, options: IOperationOptions = {}) {
57
+ const where = this.where(selector, true);
58
+ const entries = Object.entries(changes);
59
+ if (!entries.length || entries.some(([, value]) => value === undefined)) {
60
+ throw new LosslessClientError('invalid_argument', 'Supply at least one defined changed value.');
61
+ }
62
+ return this.connection.execute({ sql: 'UPDATE ' + quoteIdentifier(this.name) + ' SET ' +
63
+ entries.map(([key]) => quoteIdentifier(key) + ' = ?').join(', ') + where.sql,
64
+ values: [...entries.map(([, value]) => value as TSqlValue), ...where.values!] }, options);
65
+ }
66
+ public delete(selector: TSqlSelector<Row>, options: IOperationOptions = {}) {
67
+ const where = this.where(selector, true);
68
+ return this.connection.execute({ sql: 'DELETE FROM ' + quoteIdentifier(this.name) + where.sql, values: where.values }, options);
69
+ }
70
+ }
@@ -0,0 +1,72 @@
1
+ import { valueBytes } from '../interfaces.js';
2
+ import { LosslessClientError } from '../../core/classes.error.js';
3
+
4
+ export interface IMetricWriterOptions { batchSize?: number; maxBatchBytes?: number; flushIntervalMs?: number }
5
+
6
+ /** Await write() for backpressure. flush()/close() settle only after server acknowledgement. */
7
+ export class MetricWriter<Row> {
8
+ private batch: Row[] = [];
9
+ private bytes = 0;
10
+ private active?: Promise<void>;
11
+ private failure?: unknown;
12
+ private closed = false;
13
+ private closing?: Promise<void>;
14
+ private timer?: ReturnType<typeof setTimeout>;
15
+ private readonly batchSize: number;
16
+ private readonly maxBytes: number;
17
+
18
+ constructor(private readonly send: (rows: Row[]) => Promise<void>, private readonly options: IMetricWriterOptions = {}) {
19
+ this.batchSize = options.batchSize ?? 500;
20
+ this.maxBytes = options.maxBatchBytes ?? 4 * 1024 * 1024;
21
+ for (const limit of [this.batchSize, this.maxBytes, options.flushIntervalMs ?? 1000]) {
22
+ if (!Number.isSafeInteger(limit) || limit < 1) throw new RangeError('Writer limits must be positive integers.');
23
+ }
24
+ }
25
+ private check(): void {
26
+ if (this.failure) throw this.failure;
27
+ if (this.closed) throw new LosslessClientError('closed', 'Metric writer is closed.');
28
+ if (this.active) throw new LosslessClientError('invalid_argument', 'Await the preceding writer operation before writing again.');
29
+ }
30
+ private perform(action: () => Promise<void>): Promise<void> {
31
+ const task = action().catch(error => { this.failure = error; throw error; });
32
+ this.active = task;
33
+ void task.finally(() => {
34
+ if (this.active === task) this.active = undefined;
35
+ this.schedule();
36
+ }).catch(() => {});
37
+ return task;
38
+ }
39
+ private async sendBatch(): Promise<void> {
40
+ clearTimeout(this.timer); this.timer = undefined;
41
+ if (!this.batch.length) return;
42
+ await this.send(this.batch);
43
+ this.batch = []; this.bytes = 0;
44
+ }
45
+ public write(row: Row): Promise<void> {
46
+ this.check();
47
+ const size = valueBytes(row);
48
+ if (size > this.maxBytes) throw new LosslessClientError('invalid_argument', 'Metric row exceeds maxBatchBytes.');
49
+ return this.perform(async () => {
50
+ if (this.batch.length && this.bytes + size > this.maxBytes) await this.sendBatch();
51
+ this.batch.push(structuredClone(row)); this.bytes += size;
52
+ if (this.batch.length >= this.batchSize) await this.sendBatch();
53
+ });
54
+ }
55
+ private schedule(): void {
56
+ if (this.timer || this.closed || this.failure || !this.batch.length) return;
57
+ this.timer = setTimeout(() => {
58
+ this.timer = undefined;
59
+ if (!this.closed && !this.failure && !this.active) void this.flush().catch(() => {});
60
+ }, this.options.flushIntervalMs ?? 1000);
61
+ this.timer.unref();
62
+ }
63
+ public flush(): Promise<void> { this.check(); return this.perform(() => this.sendBatch()); }
64
+ public close(): Promise<void> {
65
+ return this.closing ??= (async () => {
66
+ this.closed = true; clearTimeout(this.timer);
67
+ await this.active;
68
+ if (this.failure) throw this.failure;
69
+ await this.sendBatch();
70
+ })();
71
+ }
72
+ }
@@ -0,0 +1,16 @@
1
+ // Core
2
+ export * from './smartclickhouse.classes.smartclickhouse.js';
3
+ export * from './smartclickhouse.classes.clickhousetable.js';
4
+ export * from './smartclickhouse.classes.httpclient.js';
5
+
6
+ // Query & Results
7
+ export * from './smartclickhouse.classes.querybuilder.js';
8
+ export * from './smartclickhouse.classes.resultset.js';
9
+
10
+ // Time Data Table (backward compat)
11
+ export * from './smartclickhouse.classes.timedatatable.js';
12
+
13
+ // Types
14
+ export * from './smartclickhouse.types.js';
15
+ export { MetricWriter } from './classes.metricwriter.js';
16
+ export type { IMetricWriterOptions } from './classes.metricwriter.js';