@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.
- package/.smartconfig.json +37 -0
- package/dist_ts/00_commitinfo_data.d.ts +8 -0
- package/dist_ts/00_commitinfo_data.js +9 -0
- package/dist_ts/classes.losslessorgclient.d.ts +32 -0
- package/dist_ts/classes.losslessorgclient.js +103 -0
- package/dist_ts/core/classes.error.d.ts +16 -0
- package/dist_ts/core/classes.error.js +29 -0
- package/dist_ts/core/classes.operation.d.ts +17 -0
- package/dist_ts/core/classes.operation.js +70 -0
- package/dist_ts/core/interfaces.d.ts +18 -0
- package/dist_ts/core/interfaces.js +2 -0
- package/dist_ts/index.d.ts +4 -0
- package/dist_ts/index.js +3 -0
- package/dist_ts/nosqldb/classes.atomicdelete.d.ts +26 -0
- package/dist_ts/nosqldb/classes.atomicdelete.js +44 -0
- package/dist_ts/nosqldb/classes.atomicfindoneandupdate.d.ts +27 -0
- package/dist_ts/nosqldb/classes.atomicfindoneandupdate.js +86 -0
- package/dist_ts/nosqldb/classes.atomicupdate.d.ts +26 -0
- package/dist_ts/nosqldb/classes.atomicupdate.js +55 -0
- package/dist_ts/nosqldb/classes.bootstraprecord.d.ts +14 -0
- package/dist_ts/nosqldb/classes.bootstraprecord.js +80 -0
- package/dist_ts/nosqldb/classes.bsonassertion.d.ts +2 -0
- package/dist_ts/nosqldb/classes.bsonassertion.js +29 -0
- package/dist_ts/nosqldb/classes.bsonobjectid.d.ts +2 -0
- package/dist_ts/nosqldb/classes.bsonobjectid.js +50 -0
- package/dist_ts/nosqldb/classes.bsonsnapshot.d.ts +3 -0
- package/dist_ts/nosqldb/classes.bsonsnapshot.js +126 -0
- package/dist_ts/nosqldb/classes.cached-document.d.ts +27 -0
- package/dist_ts/nosqldb/classes.cached-document.js +112 -0
- package/dist_ts/nosqldb/classes.collection.d.ts +302 -0
- package/dist_ts/nosqldb/classes.collection.js +1568 -0
- package/dist_ts/nosqldb/classes.collectionfactory.d.ts +30 -0
- package/dist_ts/nosqldb/classes.collectionfactory.js +65 -0
- package/dist_ts/nosqldb/classes.collectionlifecycle.d.ts +4 -0
- package/dist_ts/nosqldb/classes.collectionlifecycle.js +15 -0
- package/dist_ts/nosqldb/classes.collectionpreparation.d.ts +34 -0
- package/dist_ts/nosqldb/classes.collectionpreparation.js +134 -0
- package/dist_ts/nosqldb/classes.collectiontopology.d.ts +47 -0
- package/dist_ts/nosqldb/classes.collectiontopology.js +388 -0
- package/dist_ts/nosqldb/classes.connection.d.ts +18 -0
- package/dist_ts/nosqldb/classes.connection.js +23 -0
- package/dist_ts/nosqldb/classes.convenience.d.ts +1 -0
- package/dist_ts/nosqldb/classes.convenience.js +5 -0
- package/dist_ts/nosqldb/classes.cursor.d.ts +15 -0
- package/dist_ts/nosqldb/classes.cursor.js +82 -0
- package/dist_ts/nosqldb/classes.cursoroperation.d.ts +7 -0
- package/dist_ts/nosqldb/classes.cursoroperation.js +36 -0
- package/dist_ts/nosqldb/classes.db.d.ts +98 -0
- package/dist_ts/nosqldb/classes.db.js +434 -0
- package/dist_ts/nosqldb/classes.distributedcoordinator.d.ts +52 -0
- package/dist_ts/nosqldb/classes.distributedcoordinator.js +314 -0
- package/dist_ts/nosqldb/classes.doc.d.ts +722 -0
- package/dist_ts/nosqldb/classes.doc.js +2976 -0
- package/dist_ts/nosqldb/classes.easystore.d.ts +42 -0
- package/dist_ts/nosqldb/classes.easystore.js +199 -0
- package/dist_ts/nosqldb/classes.exactpersistence.d.ts +141 -0
- package/dist_ts/nosqldb/classes.exactpersistence.js +1110 -0
- package/dist_ts/nosqldb/classes.lucene.adapter.d.ts +173 -0
- package/dist_ts/nosqldb/classes.lucene.adapter.js +623 -0
- package/dist_ts/nosqldb/classes.mongoadministration.d.ts +73 -0
- package/dist_ts/nosqldb/classes.mongoadministration.js +497 -0
- package/dist_ts/nosqldb/classes.namespaceinspection.d.ts +42 -0
- package/dist_ts/nosqldb/classes.namespaceinspection.js +138 -0
- package/dist_ts/nosqldb/classes.operationbudget.d.ts +22 -0
- package/dist_ts/nosqldb/classes.operationbudget.js +65 -0
- package/dist_ts/nosqldb/classes.ordinarypersistence.d.ts +18 -0
- package/dist_ts/nosqldb/classes.ordinarypersistence.js +71 -0
- package/dist_ts/nosqldb/classes.persistence.d.ts +14 -0
- package/dist_ts/nosqldb/classes.persistence.js +34 -0
- package/dist_ts/nosqldb/classes.session.d.ts +28 -0
- package/dist_ts/nosqldb/classes.session.js +246 -0
- package/dist_ts/nosqldb/classes.storedinspection.d.ts +44 -0
- package/dist_ts/nosqldb/classes.storedinspection.js +100 -0
- package/dist_ts/nosqldb/classes.testdatabase.d.ts +56 -0
- package/dist_ts/nosqldb/classes.testdatabase.js +250 -0
- package/dist_ts/nosqldb/classes.watcher.d.ts +37 -0
- package/dist_ts/nosqldb/classes.watcher.js +98 -0
- package/dist_ts/nosqldb/index.d.ts +27 -0
- package/dist_ts/nosqldb/index.js +18 -0
- package/dist_ts/nosqldb/logging.d.ts +2 -0
- package/dist_ts/nosqldb/logging.js +3 -0
- package/dist_ts/nosqldb/plugins.d.ts +17 -0
- package/dist_ts/nosqldb/plugins.js +20 -0
- package/dist_ts/nosqldb/shim.d.ts +1 -0
- package/dist_ts/nosqldb/shim.js +8 -0
- package/dist_ts/nosqldb/testsupport.d.ts +3 -0
- package/dist_ts/nosqldb/testsupport.js +3 -0
- package/dist_ts/objectstorage/classes.bucket.d.ts +227 -0
- package/dist_ts/objectstorage/classes.bucket.js +939 -0
- package/dist_ts/objectstorage/classes.connection.d.ts +21 -0
- package/dist_ts/objectstorage/classes.connection.js +27 -0
- package/dist_ts/objectstorage/classes.directory.d.ts +124 -0
- package/dist_ts/objectstorage/classes.directory.js +335 -0
- package/dist_ts/objectstorage/classes.exactpathpurgeerror.d.ts +5 -0
- package/dist_ts/objectstorage/classes.exactpathpurgeerror.js +18 -0
- package/dist_ts/objectstorage/classes.exactuploaderror.d.ts +5 -0
- package/dist_ts/objectstorage/classes.exactuploaderror.js +19 -0
- package/dist_ts/objectstorage/classes.file.d.ts +91 -0
- package/dist_ts/objectstorage/classes.file.js +255 -0
- package/dist_ts/objectstorage/classes.listcursor.d.ts +41 -0
- package/dist_ts/objectstorage/classes.listcursor.js +67 -0
- package/dist_ts/objectstorage/classes.metadata.d.ts +57 -0
- package/dist_ts/objectstorage/classes.metadata.js +113 -0
- package/dist_ts/objectstorage/classes.smartbucket.d.ts +57 -0
- package/dist_ts/objectstorage/classes.smartbucket.js +357 -0
- package/dist_ts/objectstorage/classes.trash.d.ts +11 -0
- package/dist_ts/objectstorage/classes.trash.js +22 -0
- package/dist_ts/objectstorage/classes.watcher.d.ts +79 -0
- package/dist_ts/objectstorage/classes.watcher.js +249 -0
- package/dist_ts/objectstorage/helpers.d.ts +25 -0
- package/dist_ts/objectstorage/helpers.js +215 -0
- package/dist_ts/objectstorage/index.d.ts +13 -0
- package/dist_ts/objectstorage/index.js +13 -0
- package/dist_ts/objectstorage/interfaces.d.ts +276 -0
- package/dist_ts/objectstorage/interfaces.js +2 -0
- package/dist_ts/objectstorage/internal.exactpathpurge.operations.d.ts +11 -0
- package/dist_ts/objectstorage/internal.exactpathpurge.operations.js +1069 -0
- package/dist_ts/objectstorage/internal.exactupload.capability.d.ts +40 -0
- package/dist_ts/objectstorage/internal.exactupload.capability.js +382 -0
- package/dist_ts/objectstorage/internal.exactupload.operations.d.ts +11 -0
- package/dist_ts/objectstorage/internal.exactupload.operations.js +1104 -0
- package/dist_ts/objectstorage/internal.multipart.planner.d.ts +8 -0
- package/dist_ts/objectstorage/internal.multipart.planner.js +63 -0
- package/dist_ts/objectstorage/internal.multipart.stream.d.ts +17 -0
- package/dist_ts/objectstorage/internal.multipart.stream.js +440 -0
- package/dist_ts/objectstorage/internal.multipart.types.d.ts +82 -0
- package/dist_ts/objectstorage/internal.multipart.types.js +2 -0
- package/dist_ts/objectstorage/plugins.d.ts +17 -0
- package/dist_ts/objectstorage/plugins.js +23 -0
- package/dist_ts/plugins.d.ts +4 -0
- package/dist_ts/plugins.js +5 -0
- package/dist_ts/sqldb/classes.clickhouseconnection.d.ts +39 -0
- package/dist_ts/sqldb/classes.clickhouseconnection.js +225 -0
- package/dist_ts/sqldb/classes.sqlconnection.d.ts +50 -0
- package/dist_ts/sqldb/classes.sqlconnection.js +376 -0
- package/dist_ts/sqldb/classes.sqltable.d.ts +29 -0
- package/dist_ts/sqldb/classes.sqltable.js +70 -0
- package/dist_ts/sqldb/clickhouse/classes.metricwriter.d.ts +27 -0
- package/dist_ts/sqldb/clickhouse/classes.metricwriter.js +89 -0
- package/dist_ts/sqldb/clickhouse/index.d.ts +9 -0
- package/dist_ts/sqldb/clickhouse/index.js +13 -0
- package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.clickhousetable.d.ts +88 -0
- package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.clickhousetable.js +304 -0
- package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.httpclient.d.ts +28 -0
- package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.httpclient.js +46 -0
- package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.querybuilder.d.ts +43 -0
- package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.querybuilder.js +155 -0
- package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.resultset.d.ts +13 -0
- package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.resultset.js +36 -0
- package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.smartclickhouse.d.ts +49 -0
- package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.smartclickhouse.js +106 -0
- package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.timedatatable.d.ts +51 -0
- package/dist_ts/sqldb/clickhouse/smartclickhouse.classes.timedatatable.js +96 -0
- package/dist_ts/sqldb/clickhouse/smartclickhouse.plugins.d.ts +5 -0
- package/dist_ts/sqldb/clickhouse/smartclickhouse.plugins.js +6 -0
- package/dist_ts/sqldb/clickhouse/smartclickhouse.types.d.ts +53 -0
- package/dist_ts/sqldb/clickhouse/smartclickhouse.types.js +41 -0
- package/dist_ts/sqldb/index.d.ts +6 -0
- package/dist_ts/sqldb/index.js +6 -0
- package/dist_ts/sqldb/interfaces.d.ts +53 -0
- package/dist_ts/sqldb/interfaces.js +19 -0
- package/dist_ts/sqldb/internal.sqlpool.d.ts +51 -0
- package/dist_ts/sqldb/internal.sqlpool.js +214 -0
- package/dist_ts/sqldb/plugins.clickhouse.d.ts +4 -0
- package/dist_ts/sqldb/plugins.clickhouse.js +5 -0
- package/dist_ts/sqldb/plugins.mariadb.d.ts +4 -0
- package/dist_ts/sqldb/plugins.mariadb.js +5 -0
- package/docs/source/smartbucket/license.md +19 -0
- package/docs/source/smartclickhouse/license.md +19 -0
- package/license.md +21 -0
- package/package.json +101 -0
- package/readme.md +157 -0
- package/ts/00_commitinfo_data.ts +8 -0
- package/ts/classes.losslessorgclient.ts +104 -0
- package/ts/core/classes.error.ts +31 -0
- package/ts/core/classes.operation.ts +60 -0
- package/ts/core/interfaces.ts +24 -0
- package/ts/index.ts +4 -0
- package/ts/nosqldb/classes.atomicdelete.ts +81 -0
- package/ts/nosqldb/classes.atomicfindoneandupdate.ts +152 -0
- package/ts/nosqldb/classes.atomicupdate.ts +99 -0
- package/ts/nosqldb/classes.bootstraprecord.ts +107 -0
- package/ts/nosqldb/classes.bsonassertion.ts +25 -0
- package/ts/nosqldb/classes.bsonobjectid.ts +45 -0
- package/ts/nosqldb/classes.bsonsnapshot.ts +105 -0
- package/ts/nosqldb/classes.cached-document.ts +67 -0
- package/ts/nosqldb/classes.collection.ts +2399 -0
- package/ts/nosqldb/classes.collectionfactory.ts +87 -0
- package/ts/nosqldb/classes.collectionlifecycle.ts +24 -0
- package/ts/nosqldb/classes.collectionpreparation.ts +164 -0
- package/ts/nosqldb/classes.collectiontopology.ts +645 -0
- package/ts/nosqldb/classes.connection.ts +31 -0
- package/ts/nosqldb/classes.convenience.ts +5 -0
- package/ts/nosqldb/classes.cursor.ts +88 -0
- package/ts/nosqldb/classes.cursoroperation.ts +38 -0
- package/ts/nosqldb/classes.db.ts +637 -0
- package/ts/nosqldb/classes.distributedcoordinator.ts +305 -0
- package/ts/nosqldb/classes.doc.ts +4928 -0
- package/ts/nosqldb/classes.easystore.ts +154 -0
- package/ts/nosqldb/classes.exactpersistence.ts +1974 -0
- package/ts/nosqldb/classes.lucene.adapter.ts +780 -0
- package/ts/nosqldb/classes.mongoadministration.ts +867 -0
- package/ts/nosqldb/classes.namespaceinspection.ts +199 -0
- package/ts/nosqldb/classes.operationbudget.ts +75 -0
- package/ts/nosqldb/classes.ordinarypersistence.ts +84 -0
- package/ts/nosqldb/classes.persistence.ts +62 -0
- package/ts/nosqldb/classes.session.ts +389 -0
- package/ts/nosqldb/classes.storedinspection.ts +151 -0
- package/ts/nosqldb/classes.testdatabase.ts +272 -0
- package/ts/nosqldb/classes.watcher.ts +96 -0
- package/ts/nosqldb/index.ts +51 -0
- package/ts/nosqldb/logging.ts +3 -0
- package/ts/nosqldb/plugins.ts +37 -0
- package/ts/nosqldb/shim.ts +6 -0
- package/ts/nosqldb/testsupport.ts +13 -0
- package/ts/objectstorage/classes.bucket.ts +1114 -0
- package/ts/objectstorage/classes.connection.ts +36 -0
- package/ts/objectstorage/classes.directory.ts +425 -0
- package/ts/objectstorage/classes.exactpathpurgeerror.ts +24 -0
- package/ts/objectstorage/classes.exactuploaderror.ts +25 -0
- package/ts/objectstorage/classes.file.ts +303 -0
- package/ts/objectstorage/classes.listcursor.ts +89 -0
- package/ts/objectstorage/classes.metadata.ts +134 -0
- package/ts/objectstorage/classes.smartbucket.ts +445 -0
- package/ts/objectstorage/classes.trash.ts +30 -0
- package/ts/objectstorage/classes.watcher.ts +289 -0
- package/ts/objectstorage/helpers.ts +254 -0
- package/ts/objectstorage/index.ts +13 -0
- package/ts/objectstorage/interfaces.ts +389 -0
- package/ts/objectstorage/internal.exactpathpurge.operations.ts +1936 -0
- package/ts/objectstorage/internal.exactupload.capability.ts +580 -0
- package/ts/objectstorage/internal.exactupload.operations.ts +1715 -0
- package/ts/objectstorage/internal.multipart.planner.ts +90 -0
- package/ts/objectstorage/internal.multipart.stream.ts +606 -0
- package/ts/objectstorage/internal.multipart.types.ts +106 -0
- package/ts/objectstorage/plugins.ts +35 -0
- package/ts/plugins.ts +4 -0
- package/ts/sqldb/classes.clickhouseconnection.ts +189 -0
- package/ts/sqldb/classes.sqlconnection.ts +350 -0
- package/ts/sqldb/classes.sqltable.ts +70 -0
- package/ts/sqldb/clickhouse/classes.metricwriter.ts +72 -0
- package/ts/sqldb/clickhouse/index.ts +16 -0
- package/ts/sqldb/clickhouse/smartclickhouse.classes.clickhousetable.ts +372 -0
- package/ts/sqldb/clickhouse/smartclickhouse.classes.httpclient.ts +52 -0
- package/ts/sqldb/clickhouse/smartclickhouse.classes.querybuilder.ts +120 -0
- package/ts/sqldb/clickhouse/smartclickhouse.classes.resultset.ts +44 -0
- package/ts/sqldb/clickhouse/smartclickhouse.classes.smartclickhouse.ts +123 -0
- package/ts/sqldb/clickhouse/smartclickhouse.classes.timedatatable.ts +114 -0
- package/ts/sqldb/clickhouse/smartclickhouse.plugins.ts +5 -0
- package/ts/sqldb/clickhouse/smartclickhouse.types.ts +134 -0
- package/ts/sqldb/index.ts +6 -0
- package/ts/sqldb/interfaces.ts +61 -0
- package/ts/sqldb/internal.sqlpool.ts +179 -0
- package/ts/sqldb/plugins.clickhouse.ts +4 -0
- package/ts/sqldb/plugins.mariadb.ts +4 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import * as plugins from './smartclickhouse.plugins.js';
|
|
2
|
+
import { ClickhouseTable } from './smartclickhouse.classes.clickhousetable.js';
|
|
3
|
+
import { TimeDataTable } from './smartclickhouse.classes.timedatatable.js';
|
|
4
|
+
import { ClickhouseHttpClient } from './smartclickhouse.classes.httpclient.js';
|
|
5
|
+
import type { IClickhouseTableOptions } from './smartclickhouse.types.js';
|
|
6
|
+
import type { ClickHouseConnection } from '../classes.clickhouseconnection.js';
|
|
7
|
+
import { quoteClickHouseIdentifier as quoteIdentifier } from '../interfaces.js';
|
|
8
|
+
import { Operation } from '../../core/classes.operation.js';
|
|
9
|
+
import type { MetricWriter } from './classes.metricwriter.js';
|
|
10
|
+
import { LosslessClientError } from '../../core/classes.error.js';
|
|
11
|
+
|
|
12
|
+
export interface IClickhouseConstructorOptions {
|
|
13
|
+
url: string;
|
|
14
|
+
database: string;
|
|
15
|
+
username?: string;
|
|
16
|
+
password?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Allow services to exit when waiting for clickhouse startup.
|
|
19
|
+
* This allows to leave the lifecycle flow to other processes
|
|
20
|
+
* like a listening server.
|
|
21
|
+
*/
|
|
22
|
+
unref?: boolean;
|
|
23
|
+
timeoutMs?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class SmartClickHouseDb {
|
|
27
|
+
public options: IClickhouseConstructorOptions;
|
|
28
|
+
public clickhouseHttpClient!: ClickhouseHttpClient;
|
|
29
|
+
private readonly writers = new Set<MetricWriter<unknown>>();
|
|
30
|
+
private closing?: Promise<void>;
|
|
31
|
+
private stopping = false;
|
|
32
|
+
private readonly canonical: boolean;
|
|
33
|
+
|
|
34
|
+
constructor(optionsArg: IClickhouseConstructorOptions, connection?: ClickHouseConnection) {
|
|
35
|
+
this.options = optionsArg;
|
|
36
|
+
this.canonical = Boolean(connection);
|
|
37
|
+
if (connection) this.clickhouseHttpClient = new ClickhouseHttpClient(optionsArg, connection);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Starts the connection to the Clickhouse db
|
|
42
|
+
*/
|
|
43
|
+
public async start(dropOld = false) {
|
|
44
|
+
if (this.clickhouseHttpClient) return;
|
|
45
|
+
this.clickhouseHttpClient = await ClickhouseHttpClient.createAndStart(this.options);
|
|
46
|
+
try { await this.createDatabase(dropOld); }
|
|
47
|
+
catch (error) { await this.clickhouseHttpClient.close(); throw error; }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public ownWriter<Row>(writer: MetricWriter<Row>): MetricWriter<Row> {
|
|
51
|
+
if (this.stopping) throw new LosslessClientError('closed', 'Metrics connection is closing.');
|
|
52
|
+
this.writers.add(writer as MetricWriter<unknown>);
|
|
53
|
+
return writer;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public closeWriters(): Promise<void> {
|
|
57
|
+
return this.closing ??= (async () => {
|
|
58
|
+
this.stopping = true;
|
|
59
|
+
const results = await Promise.allSettled([...this.writers].map(writer => writer.close()));
|
|
60
|
+
this.writers.clear();
|
|
61
|
+
const errors = results.flatMap(result => result.status === 'rejected' ? [result.reason] : []);
|
|
62
|
+
if (errors.length) throw new AggregateError(errors, 'Metrics writer flush failed.');
|
|
63
|
+
})();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public async close(): Promise<void> {
|
|
67
|
+
try { await this.closeWriters(); }
|
|
68
|
+
finally { await this.clickhouseHttpClient?.close(); }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public async createDatabase(dropOld: boolean = false) {
|
|
72
|
+
if (dropOld) {
|
|
73
|
+
await this.clickhouseHttpClient.queryPromise(`DROP DATABASE IF EXISTS ${quoteIdentifier(this.options.database)}`);
|
|
74
|
+
}
|
|
75
|
+
await this.clickhouseHttpClient.queryPromise(
|
|
76
|
+
`CREATE DATABASE IF NOT EXISTS ${quoteIdentifier(this.options.database)}`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public async pingDatabaseUntilAvailable() {
|
|
81
|
+
const operation = new Operation({ timeoutMs: this.options.timeoutMs });
|
|
82
|
+
try {
|
|
83
|
+
while (true) {
|
|
84
|
+
operation.check();
|
|
85
|
+
if (await this.clickhouseHttpClient.ping({ signal: operation.signal })) return;
|
|
86
|
+
await plugins.smartdelay.delayFor(100, null, this.options.unref);
|
|
87
|
+
}
|
|
88
|
+
} finally { operation.dispose(); }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ---- NEW: Generic typed table factory ----
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Create a typed ClickHouse table with full configuration
|
|
95
|
+
*/
|
|
96
|
+
public async createTable<T extends Record<string, any>>(
|
|
97
|
+
options: IClickhouseTableOptions<T>,
|
|
98
|
+
): Promise<ClickhouseTable<T>> {
|
|
99
|
+
return ClickhouseTable.create<T>(this, {
|
|
100
|
+
autoSchemaEvolution: !this.canonical,
|
|
101
|
+
...options,
|
|
102
|
+
database: options.database || this.options.database,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ---- BACKWARD COMPAT: TimeDataTable factory ----
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get a TimeDataTable (backward compatible)
|
|
110
|
+
*/
|
|
111
|
+
public async getTable(tableName: string): Promise<TimeDataTable> {
|
|
112
|
+
return TimeDataTable.getTable(this, tableName);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ---- RAW QUERY ----
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Execute a raw SQL query and return typed results
|
|
119
|
+
*/
|
|
120
|
+
public async query<T = any>(sql: string): Promise<T[]> {
|
|
121
|
+
return this.clickhouseHttpClient.queryTyped<T>(sql);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import * as plugins from './smartclickhouse.plugins.js';
|
|
2
|
+
import type { SmartClickHouseDb } from './smartclickhouse.classes.smartclickhouse.js';
|
|
3
|
+
import { ClickhouseTable } from './smartclickhouse.classes.clickhousetable.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Creates a pre-configured ClickhouseTable for time-series data.
|
|
7
|
+
* This is the backward-compatible equivalent of the old TimeDataTable class.
|
|
8
|
+
*
|
|
9
|
+
* The table uses MergeTree engine, orders by timestamp, and has auto-schema evolution enabled.
|
|
10
|
+
*/
|
|
11
|
+
export async function createTimeDataTable(
|
|
12
|
+
db: SmartClickHouseDb,
|
|
13
|
+
tableName: string,
|
|
14
|
+
retainDataForDays: number = 30,
|
|
15
|
+
): Promise<TimeDataTable> {
|
|
16
|
+
const table = new TimeDataTable(db, tableName, retainDataForDays);
|
|
17
|
+
await table.setup();
|
|
18
|
+
return table;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* TimeDataTable — a ClickhouseTable pre-configured for time-series data.
|
|
23
|
+
* Provides backward-compatible convenience methods (addData, getLastEntries, etc.).
|
|
24
|
+
*/
|
|
25
|
+
export class TimeDataTable extends ClickhouseTable<any> {
|
|
26
|
+
/**
|
|
27
|
+
* Static factory for backward compatibility
|
|
28
|
+
*/
|
|
29
|
+
public static async getTable(
|
|
30
|
+
smartClickHouseDbRefArg: SmartClickHouseDb,
|
|
31
|
+
tableNameArg: string,
|
|
32
|
+
retainDataForDays: number = 30,
|
|
33
|
+
): Promise<TimeDataTable> {
|
|
34
|
+
return createTimeDataTable(smartClickHouseDbRefArg, tableNameArg, retainDataForDays);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
constructor(db: SmartClickHouseDb, tableName: string, retainDataForDays: number = 30) {
|
|
38
|
+
super(db, {
|
|
39
|
+
tableName,
|
|
40
|
+
engine: { engine: 'MergeTree' },
|
|
41
|
+
orderBy: 'timestamp' as any,
|
|
42
|
+
retainDataForDays,
|
|
43
|
+
autoSchemaEvolution: true,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Insert data with auto-schema evolution and object flattening.
|
|
49
|
+
* Backward-compatible: accepts arbitrary JSON objects with a timestamp field.
|
|
50
|
+
*/
|
|
51
|
+
public async addData(dataArg: any): Promise<any> {
|
|
52
|
+
if (!dataArg.timestamp || typeof dataArg.timestamp !== 'number') {
|
|
53
|
+
throw new Error('timestamp must be of type number');
|
|
54
|
+
}
|
|
55
|
+
return this.insert(dataArg);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get the last N entries ordered by timestamp DESC
|
|
60
|
+
*/
|
|
61
|
+
public async getLastEntries(count: number): Promise<any[]> {
|
|
62
|
+
return this.query()
|
|
63
|
+
.orderBy('timestamp' as any, 'DESC')
|
|
64
|
+
.limit(count)
|
|
65
|
+
.toArray();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get entries newer than a unix timestamp (in milliseconds)
|
|
70
|
+
*/
|
|
71
|
+
public async getEntriesNewerThan(unixTimestamp: number): Promise<any[]> {
|
|
72
|
+
return this.db.clickhouseHttpClient.queryPromise(`
|
|
73
|
+
SELECT * FROM ${this.qualifiedName}
|
|
74
|
+
WHERE timestamp > fromUnixTimestamp64Milli({timestamp:Int64})
|
|
75
|
+
`, { timestamp: this.validateTimestamp(unixTimestamp) });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get entries between two unix timestamps (in milliseconds)
|
|
80
|
+
*/
|
|
81
|
+
public async getEntriesBetween(unixTimestampStart: number, unixTimestampEnd: number): Promise<any[]> {
|
|
82
|
+
return this.db.clickhouseHttpClient.queryPromise(`
|
|
83
|
+
SELECT * FROM ${this.qualifiedName}
|
|
84
|
+
WHERE timestamp > fromUnixTimestamp64Milli({start:Int64})
|
|
85
|
+
AND timestamp < fromUnixTimestamp64Milli({end:Int64})
|
|
86
|
+
`, { start: this.validateTimestamp(unixTimestampStart), end: this.validateTimestamp(unixTimestampEnd) });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Delete entries older than N days
|
|
91
|
+
*/
|
|
92
|
+
public async deleteOldEntries(days: number): Promise<void> {
|
|
93
|
+
await this.deleteOlderThan('timestamp', days + ' DAY');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private validateTimestamp(value: number): number {
|
|
97
|
+
if (!Number.isSafeInteger(value)) throw new RangeError('Timestamp must be an integer in milliseconds.');
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Drop the table (backward-compatible alias for drop())
|
|
103
|
+
*/
|
|
104
|
+
public async delete(): Promise<void> {
|
|
105
|
+
return this.drop();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Watch for new entries via polling (backward-compatible wrapper)
|
|
110
|
+
*/
|
|
111
|
+
public watchNewEntries(): plugins.smartrx.rxjs.Observable<any> {
|
|
112
|
+
return this.watch();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import * as smartdelay from '@push.rocks/smartdelay';
|
|
2
|
+
import * as smartobject from '@push.rocks/smartobject';
|
|
3
|
+
import * as smartpromise from '@push.rocks/smartpromise';
|
|
4
|
+
import * as smartrx from '@push.rocks/smartrx';
|
|
5
|
+
export { smartdelay, smartobject, smartpromise, smartrx };
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// Column Data Types
|
|
3
|
+
// ============================================================
|
|
4
|
+
export type TClickhouseColumnType =
|
|
5
|
+
| 'String'
|
|
6
|
+
| 'UInt8' | 'UInt16' | 'UInt32' | 'UInt64'
|
|
7
|
+
| 'Int8' | 'Int16' | 'Int32' | 'Int64'
|
|
8
|
+
| 'Float32' | 'Float64'
|
|
9
|
+
| 'Bool'
|
|
10
|
+
| 'Date' | 'Date32'
|
|
11
|
+
| 'DateTime' | 'DateTime64'
|
|
12
|
+
| 'UUID'
|
|
13
|
+
| 'IPv4' | 'IPv6'
|
|
14
|
+
| (string & {}); // allow arbitrary ClickHouse types like "DateTime64(3, 'Europe/Berlin')"
|
|
15
|
+
|
|
16
|
+
// ============================================================
|
|
17
|
+
// Engine Configuration
|
|
18
|
+
// ============================================================
|
|
19
|
+
export type TClickhouseEngine =
|
|
20
|
+
| 'MergeTree'
|
|
21
|
+
| 'ReplacingMergeTree'
|
|
22
|
+
| 'SummingMergeTree'
|
|
23
|
+
| 'AggregatingMergeTree'
|
|
24
|
+
| 'CollapsingMergeTree'
|
|
25
|
+
| 'VersionedCollapsingMergeTree';
|
|
26
|
+
|
|
27
|
+
export interface IEngineConfig {
|
|
28
|
+
engine: TClickhouseEngine;
|
|
29
|
+
/** For ReplacingMergeTree: the version column name */
|
|
30
|
+
versionColumn?: string;
|
|
31
|
+
/** For CollapsingMergeTree: the sign column name */
|
|
32
|
+
signColumn?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ============================================================
|
|
36
|
+
// Column Definition
|
|
37
|
+
// ============================================================
|
|
38
|
+
export interface IColumnDefinition {
|
|
39
|
+
name: string;
|
|
40
|
+
type: TClickhouseColumnType;
|
|
41
|
+
defaultExpression?: string;
|
|
42
|
+
codec?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ============================================================
|
|
46
|
+
// Table Options
|
|
47
|
+
// ============================================================
|
|
48
|
+
export interface IClickhouseTableOptions<T = any> {
|
|
49
|
+
tableName: string;
|
|
50
|
+
database?: string;
|
|
51
|
+
engine?: IEngineConfig;
|
|
52
|
+
orderBy: (keyof T & string) | (keyof T & string)[];
|
|
53
|
+
partitionBy?: string;
|
|
54
|
+
primaryKey?: (keyof T & string) | (keyof T & string)[];
|
|
55
|
+
ttl?: {
|
|
56
|
+
column: keyof T & string;
|
|
57
|
+
interval: string; // e.g., '30 DAY', '1 MONTH'
|
|
58
|
+
};
|
|
59
|
+
columns?: IColumnDefinition[];
|
|
60
|
+
/** Enable auto-schema evolution (add columns from data). Default: true */
|
|
61
|
+
autoSchemaEvolution?: boolean;
|
|
62
|
+
/** Data retention in days (shorthand for ttl). If ttl is set, this is ignored. */
|
|
63
|
+
retainDataForDays?: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ============================================================
|
|
67
|
+
// Column Info from system.columns
|
|
68
|
+
// ============================================================
|
|
69
|
+
export interface IColumnInfo {
|
|
70
|
+
database: string;
|
|
71
|
+
table: string;
|
|
72
|
+
name: string;
|
|
73
|
+
type: string;
|
|
74
|
+
position: string;
|
|
75
|
+
default_kind: string;
|
|
76
|
+
default_expression: string;
|
|
77
|
+
data_compressed_bytes: string;
|
|
78
|
+
data_uncompressed_bytes: string;
|
|
79
|
+
marks_bytes: string;
|
|
80
|
+
comment: string;
|
|
81
|
+
is_in_partition_key: 0 | 1;
|
|
82
|
+
is_in_sorting_key: 0 | 1;
|
|
83
|
+
is_in_primary_key: 0 | 1;
|
|
84
|
+
is_in_sampling_key: 0 | 1;
|
|
85
|
+
compression_codec: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ============================================================
|
|
89
|
+
// Comparison Operators for Query Builder
|
|
90
|
+
// ============================================================
|
|
91
|
+
export type TComparisonOperator =
|
|
92
|
+
| '='
|
|
93
|
+
| '!='
|
|
94
|
+
| '>'
|
|
95
|
+
| '>='
|
|
96
|
+
| '<'
|
|
97
|
+
| '<='
|
|
98
|
+
| 'LIKE'
|
|
99
|
+
| 'NOT LIKE'
|
|
100
|
+
| 'IN'
|
|
101
|
+
| 'NOT IN'
|
|
102
|
+
| 'BETWEEN';
|
|
103
|
+
|
|
104
|
+
// ============================================================
|
|
105
|
+
// Value Escaping (SQL Injection Prevention)
|
|
106
|
+
// ============================================================
|
|
107
|
+
export function escapeClickhouseValue(value: any): string {
|
|
108
|
+
if (value === null || value === undefined) return 'NULL';
|
|
109
|
+
if (typeof value === 'number') return String(value);
|
|
110
|
+
if (typeof value === 'boolean') return value ? '1' : '0';
|
|
111
|
+
if (value instanceof Date) return `'${value.toISOString().replace('T', ' ').replace('Z', '')}'`;
|
|
112
|
+
if (Array.isArray(value)) {
|
|
113
|
+
return `(${value.map(escapeClickhouseValue).join(', ')})`;
|
|
114
|
+
}
|
|
115
|
+
// String: escape single quotes and backslashes
|
|
116
|
+
return `'${String(value).replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ============================================================
|
|
120
|
+
// ClickHouse Type Detection from JS Values
|
|
121
|
+
// ============================================================
|
|
122
|
+
export function detectClickhouseType(value: any): TClickhouseColumnType | null {
|
|
123
|
+
if (value === null || value === undefined) return null;
|
|
124
|
+
if (typeof value === 'string') return 'String';
|
|
125
|
+
if (typeof value === 'number') return 'Float64';
|
|
126
|
+
if (typeof value === 'boolean') return 'UInt8';
|
|
127
|
+
if (value instanceof Array) {
|
|
128
|
+
if (value.length === 0) return null;
|
|
129
|
+
const elementType = detectClickhouseType(value[0]);
|
|
130
|
+
if (!elementType) return null;
|
|
131
|
+
return `Array(${elementType})` as TClickhouseColumnType;
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { SqlConnection, SqlTransaction } from './classes.sqlconnection.js';
|
|
2
|
+
export { ClickHouseConnection } from './classes.clickhouseconnection.js';
|
|
3
|
+
export { SqlTable } from './classes.sqltable.js';
|
|
4
|
+
export type { ISqlSelectOptions, TSqlSelector } from './classes.sqltable.js';
|
|
5
|
+
export * from './interfaces.js';
|
|
6
|
+
export * from './clickhouse/index.js';
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type * as plugins from './plugins.mariadb.js';
|
|
2
|
+
import type { IOperationOptions } from '../core/interfaces.js';
|
|
3
|
+
import { LosslessClientError } from '../core/classes.error.js';
|
|
4
|
+
|
|
5
|
+
export type TSqlValue = null | string | number | bigint | boolean | Date | Uint8Array;
|
|
6
|
+
export interface ISqlStatement { sql: string; values?: readonly TSqlValue[] }
|
|
7
|
+
export interface IQueryOptions extends IOperationOptions { maxRows?: number; maxBytes?: number }
|
|
8
|
+
export interface ISqlConnectionOptions {
|
|
9
|
+
backend: 'sqldb' | 'mariadb';
|
|
10
|
+
host: string;
|
|
11
|
+
port?: number;
|
|
12
|
+
user: string;
|
|
13
|
+
password: string;
|
|
14
|
+
database: string;
|
|
15
|
+
connectionLimit?: number;
|
|
16
|
+
timeoutMs?: number;
|
|
17
|
+
tls?: plugins.mariadb.ConnectionConfig['ssl'];
|
|
18
|
+
}
|
|
19
|
+
export interface ISqlMutationResult {
|
|
20
|
+
affectedRows: number;
|
|
21
|
+
insertId?: bigint;
|
|
22
|
+
completion: 'acknowledged';
|
|
23
|
+
}
|
|
24
|
+
export interface IInsertOptions extends IOperationOptions { batchSize?: number; maxBatchBytes?: number }
|
|
25
|
+
export interface IClickHouseConnectionOptions {
|
|
26
|
+
backend: 'clickhouse';
|
|
27
|
+
url: string;
|
|
28
|
+
database: string;
|
|
29
|
+
username?: string;
|
|
30
|
+
password?: string;
|
|
31
|
+
connectionLimit?: number;
|
|
32
|
+
timeoutMs?: number;
|
|
33
|
+
}
|
|
34
|
+
export interface IClickHouseStatement {
|
|
35
|
+
sql: string;
|
|
36
|
+
/** ClickHouse {name:Type} parameters; identifiers are never interpolated as values. */
|
|
37
|
+
values?: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const resultLimits = (options: IQueryOptions): { rows: number; bytes: number } => {
|
|
41
|
+
const rows = options.maxRows ?? 10_000;
|
|
42
|
+
const bytes = options.maxBytes ?? 16 * 1024 * 1024;
|
|
43
|
+
if (!Number.isSafeInteger(rows) || rows < 1 || !Number.isSafeInteger(bytes) || bytes < 1) {
|
|
44
|
+
throw new LosslessClientError('invalid_argument', 'Result limits must be positive safe integers.');
|
|
45
|
+
}
|
|
46
|
+
return { rows, bytes };
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const valueBytes = (value: unknown): number => Buffer.byteLength(JSON.stringify(value,
|
|
50
|
+
(_key, item) => typeof item === 'bigint' ? item.toString() : item));
|
|
51
|
+
|
|
52
|
+
export const quoteIdentifier = (value: string): string => {
|
|
53
|
+
if (typeof value !== 'string' || !value.length || Buffer.byteLength(value) > 256 || /[\0\r\n]/.test(value)) {
|
|
54
|
+
throw new LosslessClientError('invalid_argument', 'Invalid SQL identifier.');
|
|
55
|
+
}
|
|
56
|
+
return '`' + value.replace(/`/g, '``') + '`';
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** ClickHouse also interprets backslash escapes inside backtick identifiers. */
|
|
60
|
+
export const quoteClickHouseIdentifier = (value: string): string =>
|
|
61
|
+
quoteIdentifier(value).replace(/\\/g, '\\\\');
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import * as plugins from './plugins.mariadb.js';
|
|
2
|
+
import type { Operation } from '../core/classes.operation.js';
|
|
3
|
+
import { LosslessClientError } from '../core/classes.error.js';
|
|
4
|
+
|
|
5
|
+
interface IWaiter { operation: Operation; resolve: () => void; reject: (error: unknown) => void; abort: () => void }
|
|
6
|
+
interface IPhysicalConnection { raw: plugins.mariadb.Connection; socket: plugins.net.Socket }
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Owns admission and connector handles from creation onward. The connector's promise-pool
|
|
10
|
+
* acquisition cannot be cancelled. Its supported stream factory lets this pool own the
|
|
11
|
+
* TCP socket before authentication, while the connector owns the wire protocol and TLS.
|
|
12
|
+
*/
|
|
13
|
+
export class SqlPool {
|
|
14
|
+
private readonly connections = new Set<IPhysicalConnection>();
|
|
15
|
+
private readonly idle: IPhysicalConnection[] = [];
|
|
16
|
+
private readonly waiters = new Set<IWaiter>();
|
|
17
|
+
private readonly active = new Set<SqlPooledConnection>();
|
|
18
|
+
private occupied = 0;
|
|
19
|
+
private closed = false;
|
|
20
|
+
private closing?: Promise<void>;
|
|
21
|
+
|
|
22
|
+
constructor(private readonly config: plugins.mariadb.ConnectionConfig, private readonly limit: number) {}
|
|
23
|
+
|
|
24
|
+
private async take(operation: Operation): Promise<() => void> {
|
|
25
|
+
operation.check();
|
|
26
|
+
if (this.closed) throw new LosslessClientError('closed', 'SQL pool is closed.');
|
|
27
|
+
if (this.occupied >= this.limit) {
|
|
28
|
+
await new Promise<void>((resolve, reject) => {
|
|
29
|
+
const waiter: IWaiter = { operation, resolve, reject, abort: () => {
|
|
30
|
+
this.waiters.delete(waiter); reject(operation.signal.reason);
|
|
31
|
+
} };
|
|
32
|
+
this.waiters.add(waiter);
|
|
33
|
+
operation.signal.addEventListener('abort', waiter.abort, { once: true });
|
|
34
|
+
});
|
|
35
|
+
} else this.occupied++;
|
|
36
|
+
let returned = false;
|
|
37
|
+
return () => {
|
|
38
|
+
if (returned) return;
|
|
39
|
+
returned = true;
|
|
40
|
+
const next = this.waiters.values().next().value as IWaiter | undefined;
|
|
41
|
+
if (next) {
|
|
42
|
+
this.waiters.delete(next);
|
|
43
|
+
next.operation.signal.removeEventListener('abort', next.abort);
|
|
44
|
+
next.resolve();
|
|
45
|
+
} else this.occupied--;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private async create(operation: Operation): Promise<IPhysicalConnection> {
|
|
50
|
+
const socket = plugins.net.createConnection({ host: this.config.host!, port: this.config.port! });
|
|
51
|
+
socket.setNoDelay(true);
|
|
52
|
+
socket.on('error', () => {});
|
|
53
|
+
const abort = () => socket.destroy(new LosslessClientError('closed', 'SQL connection cancelled during authentication.'));
|
|
54
|
+
operation.signal.addEventListener('abort', abort, { once: true });
|
|
55
|
+
try {
|
|
56
|
+
operation.check();
|
|
57
|
+
const raw = await plugins.mariadb.createConnection({ ...this.config, stream: callback => callback(undefined, socket) });
|
|
58
|
+
raw.on('error', () => {});
|
|
59
|
+
operation.check();
|
|
60
|
+
const physical = { raw, socket };
|
|
61
|
+
this.connections.add(physical);
|
|
62
|
+
return physical;
|
|
63
|
+
} catch (error) { socket.destroy(); throw error; }
|
|
64
|
+
finally { operation.signal.removeEventListener('abort', abort); }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public async getConnection(operation: Operation): Promise<SqlPooledConnection> {
|
|
68
|
+
const giveBack = await this.take(operation);
|
|
69
|
+
let lease: SqlPooledConnection | undefined;
|
|
70
|
+
try {
|
|
71
|
+
operation.check();
|
|
72
|
+
let current = this.idle.pop();
|
|
73
|
+
while (current && !current.raw.isValid()) {
|
|
74
|
+
current.socket.destroy(); this.connections.delete(current); current = this.idle.pop();
|
|
75
|
+
}
|
|
76
|
+
current ??= await this.create(operation);
|
|
77
|
+
const physical = current;
|
|
78
|
+
lease = new SqlPooledConnection(current, operation, async reusable => {
|
|
79
|
+
this.active.delete(lease!);
|
|
80
|
+
if (reusable && !this.closed && physical.raw.isValid()) this.idle.push(physical);
|
|
81
|
+
else { physical.socket.destroy(); this.connections.delete(physical); }
|
|
82
|
+
giveBack();
|
|
83
|
+
});
|
|
84
|
+
this.active.add(lease);
|
|
85
|
+
operation.check();
|
|
86
|
+
return lease;
|
|
87
|
+
} catch (error) {
|
|
88
|
+
if (lease) { lease.destroy(); await lease.release(); }
|
|
89
|
+
else giveBack();
|
|
90
|
+
throw operation.signal.aborted ? operation.signal.reason : error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
public end(): Promise<void> {
|
|
95
|
+
return this.closing ??= (async () => {
|
|
96
|
+
this.closed = true;
|
|
97
|
+
for (const waiter of this.waiters) {
|
|
98
|
+
waiter.operation.signal.removeEventListener('abort', waiter.abort);
|
|
99
|
+
waiter.reject(new LosslessClientError('closed', 'SQL pool closed during admission.'));
|
|
100
|
+
}
|
|
101
|
+
this.waiters.clear();
|
|
102
|
+
for (const lease of this.active) lease.destroy();
|
|
103
|
+
for (const connection of this.connections) {
|
|
104
|
+
connection.socket.destroy(new LosslessClientError('closed', 'SQL connection closed.'));
|
|
105
|
+
}
|
|
106
|
+
const results = await Promise.allSettled([...this.active].map(lease => lease.release()));
|
|
107
|
+
this.idle.length = 0; this.connections.clear();
|
|
108
|
+
const errors = results.flatMap(result => result.status === 'rejected' ? [result.reason] : []);
|
|
109
|
+
if (errors.length) throw new AggregateError(errors, 'SQL pool cleanup failed.');
|
|
110
|
+
})();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** A lease can never affect a physical connection after returning ownership to the pool. */
|
|
115
|
+
export class SqlPooledConnection {
|
|
116
|
+
private readonly tasks = new Set<Promise<unknown>>();
|
|
117
|
+
private readonly streams = new Set<plugins.stream.Readable>();
|
|
118
|
+
private released = false;
|
|
119
|
+
private releasing?: Promise<void>;
|
|
120
|
+
private readonly abort = () => this.destroy();
|
|
121
|
+
constructor(private readonly physical: IPhysicalConnection, private readonly operation: Operation,
|
|
122
|
+
private readonly giveBack: (reusable: boolean) => Promise<void>) {
|
|
123
|
+
operation.signal.addEventListener('abort', this.abort, { once: true });
|
|
124
|
+
}
|
|
125
|
+
private get raw(): plugins.mariadb.Connection { return this.physical.raw; }
|
|
126
|
+
private check(): void {
|
|
127
|
+
this.operation.check();
|
|
128
|
+
if (this.released || this.releasing) throw new LosslessClientError('closed', 'SQL lease is closed.');
|
|
129
|
+
}
|
|
130
|
+
private run<Result>(dispatch: () => Promise<Result>): Promise<Result> {
|
|
131
|
+
this.check();
|
|
132
|
+
const task = dispatch();
|
|
133
|
+
this.tasks.add(task);
|
|
134
|
+
void task.finally(() => this.tasks.delete(task)).catch(() => {});
|
|
135
|
+
return task;
|
|
136
|
+
}
|
|
137
|
+
public execute<Result = plugins.mariadb.UpsertResult>(sql: string, values: readonly unknown[] = []): Promise<Result> {
|
|
138
|
+
return this.run<Result>(() => this.raw.execute(sql, values));
|
|
139
|
+
}
|
|
140
|
+
public beginTransaction(): Promise<void> { return this.run(() => this.raw.beginTransaction()); }
|
|
141
|
+
public commit(): Promise<void> { return this.run(() => this.raw.commit()); }
|
|
142
|
+
public rollback(): Promise<void> { return this.run(() => this.raw.rollback()); }
|
|
143
|
+
public queryStream(sql: string, values: readonly unknown[] = []): plugins.stream.Readable {
|
|
144
|
+
this.check();
|
|
145
|
+
const stream = this.raw.queryStream(sql, values);
|
|
146
|
+
stream.on('error', () => {});
|
|
147
|
+
this.streams.add(stream);
|
|
148
|
+
const done = new Promise<void>(resolve => stream.once('close', () => { this.streams.delete(stream); resolve(); }));
|
|
149
|
+
this.tasks.add(done);
|
|
150
|
+
void done.finally(() => this.tasks.delete(done));
|
|
151
|
+
return stream;
|
|
152
|
+
}
|
|
153
|
+
public isValid(): boolean { return !this.released && this.raw.isValid(); }
|
|
154
|
+
public destroy(): void {
|
|
155
|
+
if (!this.released) {
|
|
156
|
+
const error = new LosslessClientError('closed', 'SQL connection closed.');
|
|
157
|
+
for (const stream of this.streams) stream.destroy(error);
|
|
158
|
+
this.physical.socket.destroy(error);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
public release(): Promise<void> {
|
|
162
|
+
return this.releasing ??= (async () => {
|
|
163
|
+
let resetError: unknown;
|
|
164
|
+
try {
|
|
165
|
+
await Promise.allSettled([...this.tasks]);
|
|
166
|
+
if (this.raw.isValid() && !this.operation.signal.aborted) {
|
|
167
|
+
try {
|
|
168
|
+
await this.raw.reset();
|
|
169
|
+
} catch (error) { resetError = error; this.destroy(); }
|
|
170
|
+
}
|
|
171
|
+
} finally {
|
|
172
|
+
this.released = true;
|
|
173
|
+
this.operation.signal.removeEventListener('abort', this.abort);
|
|
174
|
+
await this.giveBack(!resetError && !this.operation.signal.aborted);
|
|
175
|
+
}
|
|
176
|
+
if (resetError) throw resetError;
|
|
177
|
+
})();
|
|
178
|
+
}
|
|
179
|
+
}
|