adminforth 1.2.25 → 1.2.26

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.
@@ -0,0 +1,298 @@
1
+ import betterSqlite3 from 'better-sqlite3';
2
+ import { AdminForthDataTypes, AdminForthFilterOperators, AdminForthSortDirections, IAdminForthDataSourceConnector, AdminForthResource, AdminForthResourceColumn } from '../types/AdminForthConfig.js';
3
+ import AdminForthBaseConnector from './baseConnector.js';
4
+ import dayjs from 'dayjs';
5
+ import { createClient } from '@clickhouse/client' // or '@clickhouse/client-web'
6
+
7
+
8
+
9
+ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForthDataSourceConnector {
10
+
11
+ client: any;
12
+ dbName: string;
13
+
14
+ /**
15
+ * url: http[s]://[username:password@]hostname:port[/database][?param1=value1&param2=value2]
16
+ * @param param0
17
+ */
18
+ constructor({ url }: { url: string }) {
19
+ super();
20
+ this.dbName = new URL(url).pathname.replace('/', '');
21
+ // create connection here
22
+ this.client = createClient({
23
+ url: url.replace('clickhouse://', 'http://'),
24
+ });
25
+ }
26
+
27
+ async discoverFields(resource: AdminForthResource): Promise<{[key: string]: AdminForthResourceColumn}> {
28
+ const tableName = resource.table;
29
+ const q = await this.client.query({
30
+ query: `SELECT * FROM system.columns WHERE table = '${tableName}' and database = '${this.dbName}'`,
31
+ format: 'JSONEachRow',
32
+ });
33
+ const rows = await q.json();
34
+
35
+ const fieldTypes = {};
36
+ rows.forEach((row) => {
37
+ const field: any = {};
38
+ const baseType = row.type;
39
+ if (baseType.startsWith('Int') || baseType.startsWith('UInt')) {
40
+ field.type = AdminForthDataTypes.INTEGER;
41
+ } else if (baseType === 'FixedString' || baseType === 'String') {
42
+ field.type = AdminForthDataTypes.STRING;
43
+ // TODO
44
+ // const length = baseType.match(/\d+/g);
45
+ // field.maxLength = length ? parseInt(length[0]) : null;
46
+ } else if (baseType == 'UUID') {
47
+ field.type = AdminForthDataTypes.STRING;
48
+ } else if (baseType.startsWith('Decimal')) {
49
+ field.type = AdminForthDataTypes.DECIMAL;
50
+ // const [precision, scale] = baseType.match(/\d+/g);
51
+ // TODO
52
+ // field.precision = parseInt(precision);
53
+ // field.scale = parseInt(scale);
54
+ } else if (baseType.startsWith('Float')) {
55
+ field.type = AdminForthDataTypes.FLOAT;
56
+ } else if (baseType == 'DateTime64' || baseType == 'DateTime') {
57
+ field.type = AdminForthDataTypes.DATETIME;
58
+ } else if (baseType == 'Date' || baseType == 'Date64') {
59
+ field.type = AdminForthDataTypes.DATE;
60
+ } else if (baseType == 'Boolean') {
61
+ field.type = AdminForthDataTypes.BOOLEAN;
62
+ field._underlineType = 'boolean';
63
+ } else {
64
+ field.type = 'unknown'
65
+ }
66
+ field._underlineType = baseType;
67
+ field._baseTypeDebug = baseType;
68
+ field.required = row.notnull == 1;
69
+ field.primaryKey = row.pk == 1;
70
+ field.default = row.dflt_value;
71
+ fieldTypes[row.name] = field
72
+ });
73
+ return fieldTypes;
74
+ }
75
+
76
+ getFieldValue(field: AdminForthResourceColumn, value: any): any {
77
+ if (field.type == AdminForthDataTypes.DATETIME) {
78
+ if (!value) {
79
+ return null;
80
+ }
81
+ if (field._underlineType.startsWith('Int') || field._underlineType.startsWith('UInt')) {
82
+ return dayjs.unix(+value).toISOString();
83
+ } else if (field._underlineType.startsWith('DateTime')
84
+ || field._underlineType.startsWith('String')
85
+ || field._underlineType.startsWith('FixedString')) {
86
+ return dayjs(value).toISOString();
87
+ } else {
88
+ throw new Error(`AdminForth does not support row type: ${field._underlineType} for timestamps, use VARCHAR (with iso strings) or TIMESTAMP/INT (with unix timestamps). Issue in field "${field.name}"`);
89
+ }
90
+ } else if (field.type == AdminForthDataTypes.DATE) {
91
+ if (!value) {
92
+ return null;
93
+ }
94
+ return dayjs(value).toISOString().split('T')[0];
95
+ } else if (field.type == AdminForthDataTypes.BOOLEAN) {
96
+ return !!value;
97
+ } else if (field.type == AdminForthDataTypes.JSON) {
98
+ if (field._underlineType.startsWith('String') || field._underlineType.startsWith('FixedString')) {
99
+ return JSON.parse(value);
100
+ } else {
101
+ console.error(`AdminForth: JSON field is not a string but ${field._underlineType}, this is not supported yet`);
102
+ }
103
+ }
104
+ return value;
105
+ }
106
+
107
+ async getRecordByPrimaryKeyWithOriginalTypes(resource: AdminForthResource, key: any): Promise<any> {
108
+ const columns = resource.dataSourceColumns.map((col) => col.name).join(', ');
109
+ const tableName = resource.table;
110
+ const stmt = await this.client.query({
111
+ query: `SELECT ${columns} FROM ${tableName} WHERE ${this.getPrimaryKey(resource)} = ?`,
112
+ format: 'JSONEachRow',
113
+ query_params: [key],
114
+ });
115
+ const row = await stmt.json();
116
+ if (!row.length) {
117
+ return null;
118
+ }
119
+ return row[0];
120
+ }
121
+
122
+ setFieldValue(field: AdminForthResourceColumn, value: any): any {
123
+ if (field.type == AdminForthDataTypes.DATETIME) {
124
+ if (!value) {
125
+ return null;
126
+ }
127
+ if (field._underlineType.startsWith('Int') || field._underlineType.startsWith('UInt')) {
128
+ // value is iso string now, convert to unix timestamp
129
+ return dayjs(value).unix();
130
+ } else if (field._underlineType.startsWith('DateTime')
131
+ || field._underlineType.startsWith('String')
132
+ || field._underlineType.startsWith('FixedString')) {
133
+ // value is iso string now, convert to unix timestamp
134
+ return dayjs(value).toISOString();
135
+ }
136
+ } else if (field.type == AdminForthDataTypes.BOOLEAN) {
137
+ return value ? 1 : 0;
138
+ } else if (field.type == AdminForthDataTypes.JSON) {
139
+ // check underline type is text or string
140
+ if (field._underlineType.startsWith('String') || field._underlineType.startsWith('FixedString')) {
141
+ return JSON.stringify(value);
142
+ } else {
143
+ console.error(`AdminForth: JSON field is not a string/text but ${field._underlineType}, this is not supported yet`);
144
+ }
145
+ }
146
+
147
+ return value;
148
+ }
149
+
150
+ OperatorsMap = {
151
+ [AdminForthFilterOperators.EQ]: '=',
152
+ [AdminForthFilterOperators.NE]: '!=',
153
+ [AdminForthFilterOperators.GT]: '>',
154
+ [AdminForthFilterOperators.LT]: '<',
155
+ [AdminForthFilterOperators.GTE]: '>=',
156
+ [AdminForthFilterOperators.LTE]: '<=',
157
+ [AdminForthFilterOperators.LIKE]: 'LIKE',
158
+ [AdminForthFilterOperators.ILIKE]: 'ILIKE',
159
+ [AdminForthFilterOperators.IN]: 'IN',
160
+ [AdminForthFilterOperators.NIN]: 'NOT IN',
161
+ };
162
+
163
+ SortDirectionsMap = {
164
+ [AdminForthSortDirections.asc]: 'ASC',
165
+ [AdminForthSortDirections.desc]: 'DESC',
166
+ };
167
+
168
+
169
+ async getDataWithOriginalTypes({ resource, limit, offset, sort, filters }: {
170
+ resource: AdminForthResource,
171
+ limit: number,
172
+ offset: number,
173
+ sort: { field: string, direction: AdminForthSortDirections }[],
174
+ filters: { field: string, operator: AdminForthFilterOperators, value: any }[]
175
+ }): Promise<{ data: any[], total: number }> {
176
+ const columns = resource.dataSourceColumns.map((col) => col.name).join(', ');
177
+ const tableName = resource.table;
178
+
179
+ const where = filters.length ? `WHERE ${filters.map((f, i) => {
180
+ let placeholder = `{f${i}}`;
181
+ let field = f.field;
182
+ let operator = this.OperatorsMap[f.operator];
183
+ if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
184
+ placeholder = `(${f.value.map((_, j) => `p${i}_${j}`).join(', ')})`;
185
+ }
186
+
187
+ return `${field} ${operator} ${placeholder}`
188
+ }).join(' AND ')}` : '';
189
+
190
+ const params = {};
191
+
192
+ filters.length ? filters.forEach((f, i) => {
193
+ // for arrays do set in map
194
+ const v = f.value;
195
+
196
+ if (f.operator == AdminForthFilterOperators.LIKE || f.operator == AdminForthFilterOperators.ILIKE) {
197
+ params[`f${i}`] = `%${v}%`;
198
+ } else if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
199
+ v.forEach((_, j) => {
200
+ params[`p${i}_${j}`] = v[j];
201
+ });
202
+ } else {
203
+ params[`f${i}`] = v;
204
+ }
205
+ }) : [];
206
+
207
+ const orderBy = sort.length ? `ORDER BY ${sort.map((s) => `${s.field} ${this.SortDirectionsMap[s.direction]}`).join(', ')}` : '';
208
+
209
+
210
+ const q = `SELECT ${columns} FROM ${tableName} ${where} ${orderBy} LIMIT {limit:Int} OFFSET {offset:Int}`;
211
+
212
+ const d = {
213
+ ...params,
214
+ limit,
215
+ offset,
216
+ };
217
+ console.log('🪲 Clickhouse Query', q, 'params:', d);
218
+
219
+ const stmt = await this.client.query({
220
+ query: q,
221
+ format: 'JSONEachRow',
222
+ query_params: d,
223
+ });
224
+
225
+ if (process.env.HEAVY_DEBUG) {
226
+ console.log('🪲 Clickhouse Query', q, 'params:', d);
227
+ }
228
+ const rows = await stmt.json();
229
+
230
+
231
+ const countQ = await this.client.query({
232
+ query: `SELECT COUNT(*) FROM ${tableName} ${where}`,
233
+ format: 'JSONEachRow',
234
+ query_params: d,
235
+ });
236
+ const total = (
237
+ await countQ.json()
238
+ )['COUNT(*)'];
239
+
240
+ return {
241
+ data: rows.map((row) => {
242
+ const newRow = {};
243
+ for (const [key, value] of Object.entries(row)) {
244
+ newRow[key] = value;
245
+ }
246
+ return newRow;
247
+ }),
248
+ total,
249
+ };
250
+ }
251
+
252
+ async getMinMaxForColumnsWithOriginalTypes({ resource, columns }: { resource: AdminForthResource, columns: AdminForthResourceColumn[] }): Promise<{ [key: string]: { min: any, max: any } }> {
253
+ const tableName = resource.table;
254
+ const result = {};
255
+ await Promise.all(columns.map(async (col) => {
256
+ // const stmt = await this.db.prepare(`SELECT MIN(${col.name}) as min, MAX(${col.name}) as max FROM ${tableName}`);
257
+ // const { min, max } = stmt.get();
258
+ // result[col.name] = {
259
+ // min, max,
260
+ // };
261
+ }))
262
+ return result;
263
+ }
264
+
265
+ async createRecordOriginalValues({ resource, record }: { resource: AdminForthResource, record: any }) {
266
+ const tableName = resource.table;
267
+ const columns = Object.keys(record);
268
+ await this.client.insert({
269
+ table: tableName,
270
+ values: columns.map((colName) => {
271
+ return {[colName]: record[colName]};
272
+ }),
273
+ format: 'JSONEachRow',
274
+ })
275
+
276
+ }
277
+
278
+ async updateRecord({ resource, recordId, newValues }: { resource: AdminForthResource, recordId: any, newValues: any }) {
279
+ const columnsWithPlaceholders = Object.keys(newValues).map((col) => `${col} = ?`);
280
+ const values = [...Object.values(newValues), recordId];
281
+
282
+ // const q = this.db.prepare(
283
+ // `UPDATE ${resource.table} SET ${columnsWithPlaceholders} WHERE ${this.getPrimaryKey(resource)} = ?`
284
+ // )
285
+ // await q.run(values);
286
+ }
287
+
288
+ async deleteRecord({ resource, recordId }: { resource: AdminForthResource, recordId: any }) {
289
+ // const q = this.db.prepare(`DELETE FROM ${resource.table} WHERE ${this.getPrimaryKey(resource)} = ?`);
290
+ // await q.run(recordId);
291
+ }
292
+
293
+ close() {
294
+ this.client.disconnect();
295
+ }
296
+ }
297
+
298
+ export default ClickhouseConnector;
@@ -0,0 +1,290 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { AdminForthDataTypes, AdminForthFilterOperators, AdminForthSortDirections } from '../types/AdminForthConfig.js';
11
+ import AdminForthBaseConnector from './baseConnector.js';
12
+ import dayjs from 'dayjs';
13
+ import { createClient } from '@clickhouse/client'; // or '@clickhouse/client-web'
14
+ class ClickhouseConnector extends AdminForthBaseConnector {
15
+ /**
16
+ * url: http[s]://[username:password@]hostname:port[/database][?param1=value1&param2=value2]
17
+ * @param param0
18
+ */
19
+ constructor({ url }) {
20
+ super();
21
+ this.OperatorsMap = {
22
+ [AdminForthFilterOperators.EQ]: '=',
23
+ [AdminForthFilterOperators.NE]: '!=',
24
+ [AdminForthFilterOperators.GT]: '>',
25
+ [AdminForthFilterOperators.LT]: '<',
26
+ [AdminForthFilterOperators.GTE]: '>=',
27
+ [AdminForthFilterOperators.LTE]: '<=',
28
+ [AdminForthFilterOperators.LIKE]: 'LIKE',
29
+ [AdminForthFilterOperators.ILIKE]: 'ILIKE',
30
+ [AdminForthFilterOperators.IN]: 'IN',
31
+ [AdminForthFilterOperators.NIN]: 'NOT IN',
32
+ };
33
+ this.SortDirectionsMap = {
34
+ [AdminForthSortDirections.asc]: 'ASC',
35
+ [AdminForthSortDirections.desc]: 'DESC',
36
+ };
37
+ this.dbName = new URL(url).pathname.replace('/', '');
38
+ // create connection here
39
+ this.client = createClient({
40
+ url: url.replace('clickhouse://', 'http://'),
41
+ });
42
+ }
43
+ discoverFields(resource) {
44
+ return __awaiter(this, void 0, void 0, function* () {
45
+ const tableName = resource.table;
46
+ const q = yield this.client.query({
47
+ query: `SELECT * FROM system.columns WHERE table = '${tableName}' and database = '${this.dbName}'`,
48
+ format: 'JSONEachRow',
49
+ });
50
+ const rows = yield q.json();
51
+ const fieldTypes = {};
52
+ rows.forEach((row) => {
53
+ const field = {};
54
+ const baseType = row.type;
55
+ if (baseType.startsWith('Int') || baseType.startsWith('UInt')) {
56
+ field.type = AdminForthDataTypes.INTEGER;
57
+ }
58
+ else if (baseType === 'FixedString' || baseType === 'String') {
59
+ field.type = AdminForthDataTypes.STRING;
60
+ // TODO
61
+ // const length = baseType.match(/\d+/g);
62
+ // field.maxLength = length ? parseInt(length[0]) : null;
63
+ }
64
+ else if (baseType == 'UUID') {
65
+ field.type = AdminForthDataTypes.STRING;
66
+ }
67
+ else if (baseType.startsWith('Decimal')) {
68
+ field.type = AdminForthDataTypes.DECIMAL;
69
+ // const [precision, scale] = baseType.match(/\d+/g);
70
+ // TODO
71
+ // field.precision = parseInt(precision);
72
+ // field.scale = parseInt(scale);
73
+ }
74
+ else if (baseType.startsWith('Float')) {
75
+ field.type = AdminForthDataTypes.FLOAT;
76
+ }
77
+ else if (baseType == 'DateTime64' || baseType == 'DateTime') {
78
+ field.type = AdminForthDataTypes.DATETIME;
79
+ }
80
+ else if (baseType == 'Date' || baseType == 'Date64') {
81
+ field.type = AdminForthDataTypes.DATE;
82
+ }
83
+ else if (baseType == 'Boolean') {
84
+ field.type = AdminForthDataTypes.BOOLEAN;
85
+ field._underlineType = 'boolean';
86
+ }
87
+ else {
88
+ field.type = 'unknown';
89
+ }
90
+ field._underlineType = baseType;
91
+ field._baseTypeDebug = baseType;
92
+ field.required = row.notnull == 1;
93
+ field.primaryKey = row.pk == 1;
94
+ field.default = row.dflt_value;
95
+ fieldTypes[row.name] = field;
96
+ });
97
+ return fieldTypes;
98
+ });
99
+ }
100
+ getFieldValue(field, value) {
101
+ if (field.type == AdminForthDataTypes.DATETIME) {
102
+ if (!value) {
103
+ return null;
104
+ }
105
+ if (field._underlineType.startsWith('Int') || field._underlineType.startsWith('UInt')) {
106
+ return dayjs.unix(+value).toISOString();
107
+ }
108
+ else if (field._underlineType.startsWith('DateTime')
109
+ || field._underlineType.startsWith('String')
110
+ || field._underlineType.startsWith('FixedString')) {
111
+ return dayjs(value).toISOString();
112
+ }
113
+ else {
114
+ throw new Error(`AdminForth does not support row type: ${field._underlineType} for timestamps, use VARCHAR (with iso strings) or TIMESTAMP/INT (with unix timestamps). Issue in field "${field.name}"`);
115
+ }
116
+ }
117
+ else if (field.type == AdminForthDataTypes.DATE) {
118
+ if (!value) {
119
+ return null;
120
+ }
121
+ return dayjs(value).toISOString().split('T')[0];
122
+ }
123
+ else if (field.type == AdminForthDataTypes.BOOLEAN) {
124
+ return !!value;
125
+ }
126
+ else if (field.type == AdminForthDataTypes.JSON) {
127
+ if (field._underlineType.startsWith('String') || field._underlineType.startsWith('FixedString')) {
128
+ return JSON.parse(value);
129
+ }
130
+ else {
131
+ console.error(`AdminForth: JSON field is not a string but ${field._underlineType}, this is not supported yet`);
132
+ }
133
+ }
134
+ return value;
135
+ }
136
+ getRecordByPrimaryKeyWithOriginalTypes(resource, key) {
137
+ return __awaiter(this, void 0, void 0, function* () {
138
+ const columns = resource.dataSourceColumns.map((col) => col.name).join(', ');
139
+ const tableName = resource.table;
140
+ const stmt = yield this.client.query({
141
+ query: `SELECT ${columns} FROM ${tableName} WHERE ${this.getPrimaryKey(resource)} = ?`,
142
+ format: 'JSONEachRow',
143
+ query_params: [key],
144
+ });
145
+ const row = yield stmt.json();
146
+ if (!row.length) {
147
+ return null;
148
+ }
149
+ return row[0];
150
+ });
151
+ }
152
+ setFieldValue(field, value) {
153
+ if (field.type == AdminForthDataTypes.DATETIME) {
154
+ if (!value) {
155
+ return null;
156
+ }
157
+ if (field._underlineType.startsWith('Int') || field._underlineType.startsWith('UInt')) {
158
+ // value is iso string now, convert to unix timestamp
159
+ return dayjs(value).unix();
160
+ }
161
+ else if (field._underlineType.startsWith('DateTime')
162
+ || field._underlineType.startsWith('String')
163
+ || field._underlineType.startsWith('FixedString')) {
164
+ // value is iso string now, convert to unix timestamp
165
+ return dayjs(value).toISOString();
166
+ }
167
+ }
168
+ else if (field.type == AdminForthDataTypes.BOOLEAN) {
169
+ return value ? 1 : 0;
170
+ }
171
+ else if (field.type == AdminForthDataTypes.JSON) {
172
+ // check underline type is text or string
173
+ if (field._underlineType.startsWith('String') || field._underlineType.startsWith('FixedString')) {
174
+ return JSON.stringify(value);
175
+ }
176
+ else {
177
+ console.error(`AdminForth: JSON field is not a string/text but ${field._underlineType}, this is not supported yet`);
178
+ }
179
+ }
180
+ return value;
181
+ }
182
+ getDataWithOriginalTypes(_a) {
183
+ return __awaiter(this, arguments, void 0, function* ({ resource, limit, offset, sort, filters }) {
184
+ const columns = resource.dataSourceColumns.map((col) => col.name).join(', ');
185
+ const tableName = resource.table;
186
+ const where = filters.length ? `WHERE ${filters.map((f, i) => {
187
+ let placeholder = `{f${i}}`;
188
+ let field = f.field;
189
+ let operator = this.OperatorsMap[f.operator];
190
+ if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
191
+ placeholder = `(${f.value.map((_, j) => `p${i}_${j}`).join(', ')})`;
192
+ }
193
+ return `${field} ${operator} ${placeholder}`;
194
+ }).join(' AND ')}` : '';
195
+ const params = {};
196
+ filters.length ? filters.forEach((f, i) => {
197
+ // for arrays do set in map
198
+ const v = f.value;
199
+ if (f.operator == AdminForthFilterOperators.LIKE || f.operator == AdminForthFilterOperators.ILIKE) {
200
+ params[`f${i}`] = `%${v}%`;
201
+ }
202
+ else if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
203
+ v.forEach((_, j) => {
204
+ params[`p${i}_${j}`] = v[j];
205
+ });
206
+ }
207
+ else {
208
+ params[`f${i}`] = v;
209
+ }
210
+ }) : [];
211
+ const orderBy = sort.length ? `ORDER BY ${sort.map((s) => `${s.field} ${this.SortDirectionsMap[s.direction]}`).join(', ')}` : '';
212
+ const q = `SELECT ${columns} FROM ${tableName} ${where} ${orderBy} LIMIT {limit:Int} OFFSET {offset:Int}`;
213
+ const d = Object.assign(Object.assign({}, params), { limit,
214
+ offset });
215
+ console.log('🪲 Clickhouse Query', q, 'params:', d);
216
+ const stmt = yield this.client.query({
217
+ query: q,
218
+ format: 'JSONEachRow',
219
+ query_params: d,
220
+ });
221
+ if (process.env.HEAVY_DEBUG) {
222
+ console.log('🪲 Clickhouse Query', q, 'params:', d);
223
+ }
224
+ const rows = yield stmt.json();
225
+ const countQ = yield this.client.query({
226
+ query: `SELECT COUNT(*) FROM ${tableName} ${where}`,
227
+ format: 'JSONEachRow',
228
+ query_params: d,
229
+ });
230
+ const total = (yield countQ.json())['COUNT(*)'];
231
+ return {
232
+ data: rows.map((row) => {
233
+ const newRow = {};
234
+ for (const [key, value] of Object.entries(row)) {
235
+ newRow[key] = value;
236
+ }
237
+ return newRow;
238
+ }),
239
+ total,
240
+ };
241
+ });
242
+ }
243
+ getMinMaxForColumnsWithOriginalTypes(_a) {
244
+ return __awaiter(this, arguments, void 0, function* ({ resource, columns }) {
245
+ const tableName = resource.table;
246
+ const result = {};
247
+ yield Promise.all(columns.map((col) => __awaiter(this, void 0, void 0, function* () {
248
+ // const stmt = await this.db.prepare(`SELECT MIN(${col.name}) as min, MAX(${col.name}) as max FROM ${tableName}`);
249
+ // const { min, max } = stmt.get();
250
+ // result[col.name] = {
251
+ // min, max,
252
+ // };
253
+ })));
254
+ return result;
255
+ });
256
+ }
257
+ createRecordOriginalValues(_a) {
258
+ return __awaiter(this, arguments, void 0, function* ({ resource, record }) {
259
+ const tableName = resource.table;
260
+ const columns = Object.keys(record);
261
+ yield this.client.insert({
262
+ table: tableName,
263
+ values: columns.map((colName) => {
264
+ return { [colName]: record[colName] };
265
+ }),
266
+ format: 'JSONEachRow',
267
+ });
268
+ });
269
+ }
270
+ updateRecord(_a) {
271
+ return __awaiter(this, arguments, void 0, function* ({ resource, recordId, newValues }) {
272
+ const columnsWithPlaceholders = Object.keys(newValues).map((col) => `${col} = ?`);
273
+ const values = [...Object.values(newValues), recordId];
274
+ // const q = this.db.prepare(
275
+ // `UPDATE ${resource.table} SET ${columnsWithPlaceholders} WHERE ${this.getPrimaryKey(resource)} = ?`
276
+ // )
277
+ // await q.run(values);
278
+ });
279
+ }
280
+ deleteRecord(_a) {
281
+ return __awaiter(this, arguments, void 0, function* ({ resource, recordId }) {
282
+ // const q = this.db.prepare(`DELETE FROM ${resource.table} WHERE ${this.getPrimaryKey(resource)} = ?`);
283
+ // await q.run(recordId);
284
+ });
285
+ }
286
+ close() {
287
+ this.client.disconnect();
288
+ }
289
+ }
290
+ export default ClickhouseConnector;
package/dist/index.js CHANGED
@@ -24,6 +24,7 @@ import { AdminForthFilterOperators, AdminForthDataTypes, AdminForthResourcePages
24
24
  import AdminForthPlugin from './basePlugin.js';
25
25
  import ConfigValidator from './modules/configValidator.js';
26
26
  import AdminForthRestAPI from './modules/restApi.js';
27
+ import ClickhouseConnector from './dataConnectors/clickhouse.js';
27
28
  // exports
28
29
  export * from './types/AdminForthConfig.js';
29
30
  export { AdminForthPlugin };
@@ -65,6 +66,7 @@ class AdminForth {
65
66
  'sqlite': SQLiteConnector,
66
67
  'postgres': PostgresConnector,
67
68
  'mongodb': MongoConnector,
69
+ 'clickhouse': ClickhouseConnector,
68
70
  };
69
71
  if (!this.config.databaseConnectors) {
70
72
  this.config.databaseConnectors = Object.assign({}, this.connectorClasses);
@@ -201,7 +201,7 @@ export default class AdminForthRestAPI {
201
201
  return __awaiter(this, void 0, void 0, function* () {
202
202
  var _a;
203
203
  if (process.env.HEAVY_DEBUG) {
204
- console.log('🪲Interpreting resource', resource.resourceId, source);
204
+ console.log('🪲Interpreting resource', resource.resourceId, source, 'adminUser', adminUser);
205
205
  }
206
206
  const allowedActions = {};
207
207
  yield Promise.all(Object.entries(((_a = resource.options) === null || _a === void 0 ? void 0 : _a.allowedActions) || {}).map((_b) => __awaiter(this, [_b], void 0, function* ([key, value]) {
@@ -609,6 +609,7 @@ export default class AdminForthRestAPI {
609
609
  path: '/start_bulk_action',
610
610
  handler: (_11) => __awaiter(this, [_11], void 0, function* ({ body }) {
611
611
  const { resourceId, actionId, recordIds, adminUser } = body;
612
+ process.env.HEAVY_DEBUG && console.log('🪲 starting bulk action', body);
612
613
  const resource = this.adminforth.config.resources.find((res) => res.resourceId == resourceId);
613
614
  if (!resource) {
614
615
  return { error: `Resource '${resourceId}' not found` };
package/index.ts CHANGED
@@ -19,6 +19,7 @@ import {
19
19
  import AdminForthPlugin from './basePlugin.js';
20
20
  import ConfigValidator from './modules/configValidator.js';
21
21
  import AdminForthRestAPI from './modules/restApi.js';
22
+ import ClickhouseConnector from './dataConnectors/clickhouse.js';
22
23
 
23
24
  // exports
24
25
  export * from './types/AdminForthConfig.js';
@@ -92,6 +93,7 @@ class AdminForth implements IAdminForth {
92
93
  'sqlite': SQLiteConnector,
93
94
  'postgres': PostgresConnector,
94
95
  'mongodb': MongoConnector,
96
+ 'clickhouse': ClickhouseConnector,
95
97
  };
96
98
  if (!this.config.databaseConnectors) {
97
99
  this.config.databaseConnectors = {...this.connectorClasses};
@@ -226,7 +226,7 @@ export default class AdminForthRestAPI {
226
226
 
227
227
  async function interpretResource(adminUser: AdminUser, resource: AdminForthResource, meta: any, source: ActionCheckSource): Promise<{allowedActions: AllowedActionsResolved}> {
228
228
  if (process.env.HEAVY_DEBUG) {
229
- console.log('🪲Interpreting resource', resource.resourceId, source);
229
+ console.log('🪲Interpreting resource', resource.resourceId, source, 'adminUser', adminUser);
230
230
  }
231
231
  const allowedActions = {};
232
232
 
@@ -697,6 +697,7 @@ export default class AdminForthRestAPI {
697
697
  path: '/start_bulk_action',
698
698
  handler: async ({ body }) => {
699
699
  const { resourceId, actionId, recordIds, adminUser } = body;
700
+ process.env.HEAVY_DEBUG && console.log('🪲 starting bulk action', body);
700
701
  const resource = this.adminforth.config.resources.find((res) => res.resourceId == resourceId);
701
702
  if (!resource) {
702
703
  return { error: `Resource '${resourceId}' not found` };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adminforth",
3
- "version": "1.2.25",
3
+ "version": "1.2.26",
4
4
  "description": "OpenSource Vue3 powered forth-generation admin panel",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -28,6 +28,7 @@
28
28
  "uuid": "^9.0.1"
29
29
  },
30
30
  "devDependencies": {
31
+ "@clickhouse/client": "^1.4.0",
31
32
  "@types/node": "^20.14.2",
32
33
  "typescript": "^5.4.5"
33
34
  }