adminforth 1.2.27 → 1.2.29

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.
@@ -10,16 +10,25 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon
10
10
  }
11
11
  }
12
12
 
13
- getRecordByPrimaryKeyWithOriginalTypes(resource: AdminForthResource, id: string): Promise<any> {
14
- throw new Error('Method not implemented.');
13
+ async getRecordByPrimaryKeyWithOriginalTypes(resource: AdminForthResource, id: string): Promise<any> {
14
+ const data = await this.getDataWithOriginalTypes({
15
+ resource,
16
+ limit: 1,
17
+ offset: 0,
18
+ sort: [],
19
+ filters: [{ field: this.getPrimaryKey(resource), operator: AdminForthFilterOperators.EQ, value: id }],
20
+ getTotals: false
21
+ });
22
+ return data.data.length > 0 ? data.data[0] : null;
15
23
  }
16
24
 
17
- getDataWithOriginalTypes({ resource, limit, offset, sort, filters }: {
25
+ getDataWithOriginalTypes({ resource, limit, offset, sort, filters, getTotals }: {
18
26
  resource: AdminForthResource,
19
27
  limit: number,
20
28
  offset: number,
21
29
  sort: { field: string, direction: AdminForthSortDirections }[],
22
- filters: { field: string, operator: AdminForthFilterOperators, value: any }[]
30
+ filters: { field: string, operator: AdminForthFilterOperators, value: any }[],
31
+ getTotals?: boolean
23
32
  }): Promise<{ data: any[], total: number }> {
24
33
  throw new Error('Method not implemented.');
25
34
  }
@@ -68,7 +77,7 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon
68
77
  limit: number,
69
78
  offset: number,
70
79
  sort: { field: string, direction: AdminForthSortDirections }[],
71
- filters: { field: string, operator: AdminForthFilterOperators, value: any }[]
80
+ filters: { field: string, operator: AdminForthFilterOperators, value: any }[]
72
81
  }): Promise<{ data: any[], total: number }> {
73
82
  if (filters) {
74
83
  filters.map((f) => {
@@ -80,7 +89,7 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon
80
89
  });
81
90
  }
82
91
 
83
- const d = await this.getDataWithOriginalTypes({ resource, limit, offset, sort, filters });
92
+ const d = await this.getDataWithOriginalTypes({ resource, limit, offset, sort, filters, getTotals: true });
84
93
  // call getFieldValue for each field
85
94
  d.data.map((record) => {
86
95
  for (const col of resource.dataSourceColumns) {
@@ -2,7 +2,9 @@ import betterSqlite3 from 'better-sqlite3';
2
2
  import { AdminForthDataTypes, AdminForthFilterOperators, AdminForthSortDirections, IAdminForthDataSourceConnector, AdminForthResource, AdminForthResourceColumn } from '../types/AdminForthConfig.js';
3
3
  import AdminForthBaseConnector from './baseConnector.js';
4
4
  import dayjs from 'dayjs';
5
- import { createClient } from '@clickhouse/client' // or '@clickhouse/client-web'
5
+ import { createClient, ClickHouseLogLevel } from '@clickhouse/client' // or '@clickhouse/client-web'
6
+ import { isObject } from 'util';
7
+ import { date } from 'zod';
6
8
 
7
9
 
8
10
 
@@ -21,6 +23,18 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
21
23
  // create connection here
22
24
  this.client = createClient({
23
25
  url: url.replace('clickhouse://', 'http://'),
26
+ clickhouse_settings: {
27
+ // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
28
+ date_time_input_format: 'best_effort',
29
+
30
+ // Recommended for cluster usage to avoid situations where a query processing error occurred after the response code,
31
+ // and HTTP headers were already sent to the client.
32
+ // See https://clickhouse.com/docs/en/interfaces/http/#response-buffering
33
+ wait_end_of_query: 1,
34
+ },
35
+ // log:{
36
+ // level: ClickHouseLogLevel.TRACE,
37
+ // }
24
38
  });
25
39
  }
26
40
 
@@ -83,7 +97,8 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
83
97
  } else if (field._underlineType.startsWith('DateTime')
84
98
  || field._underlineType.startsWith('String')
85
99
  || field._underlineType.startsWith('FixedString')) {
86
- return dayjs(value).toISOString();
100
+ const v = dayjs(value).toISOString();
101
+ return v;
87
102
  } else {
88
103
  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
104
  }
@@ -104,21 +119,6 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
104
119
  return value;
105
120
  }
106
121
 
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
122
  setFieldValue(field: AdminForthResourceColumn, value: any): any {
123
123
  if (field.type == AdminForthDataTypes.DATETIME) {
124
124
  if (!value) {
@@ -131,7 +131,8 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
131
131
  || field._underlineType.startsWith('String')
132
132
  || field._underlineType.startsWith('FixedString')) {
133
133
  // value is iso string now, convert to unix timestamp
134
- return dayjs(value).toISOString();
134
+ const iso = dayjs(value).toISOString();
135
+ return iso;
135
136
  }
136
137
  } else if (field.type == AdminForthDataTypes.BOOLEAN) {
137
138
  return value ? 1 : 0;
@@ -166,18 +167,20 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
166
167
  };
167
168
 
168
169
 
169
- async getDataWithOriginalTypes({ resource, limit, offset, sort, filters }: {
170
+ async getDataWithOriginalTypes({ resource, limit, offset, sort, filters, getTotals }: {
170
171
  resource: AdminForthResource,
171
172
  limit: number,
172
173
  offset: number,
173
174
  sort: { field: string, direction: AdminForthSortDirections }[],
174
- filters: { field: string, operator: AdminForthFilterOperators, value: any }[]
175
+ filters: { field: string, operator: AdminForthFilterOperators, value: any }[],
176
+ getTotals?: boolean,
175
177
  }): Promise<{ data: any[], total: number }> {
176
178
  const columns = resource.dataSourceColumns.map((col) => col.name).join(', ');
177
179
  const tableName = resource.table;
178
180
 
179
181
  const where = filters.length ? `WHERE ${filters.map((f, i) => {
180
- let placeholder = `{f${i}}`;
182
+ const column = resource.dataSourceColumns.find((col) => col.name == f.field);
183
+ let placeholder = `{f${i}:${column._underlineType}}`;
181
184
  let field = f.field;
182
185
  let operator = this.OperatorsMap[f.operator];
183
186
  if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
@@ -208,7 +211,6 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
208
211
 
209
212
 
210
213
  const q = `SELECT ${columns} FROM ${tableName} ${where} ${orderBy} LIMIT {limit:Int} OFFSET {offset:Int}`;
211
-
212
214
  const d = {
213
215
  ...params,
214
216
  limit,
@@ -227,15 +229,17 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
227
229
  }
228
230
  const rows = await stmt.json();
229
231
 
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(*)'];
232
+ let total = 0;
233
+ if (getTotals) {
234
+ const countQ = await this.client.query({
235
+ query: `SELECT COUNT(*) FROM ${tableName} ${where}`,
236
+ format: 'JSONEachRow',
237
+ query_params: d,
238
+ });
239
+ const countResp = await countQ.json()
240
+ console.log('🪲 Clickhouse Query count', countResp);
241
+ total = countResp[0]['COUNT()'];
242
+ }
239
243
 
240
244
  return {
241
245
  data: rows.map((row) => {
@@ -261,17 +265,34 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
261
265
  }))
262
266
  return result;
263
267
  }
264
-
265
268
  async createRecordOriginalValues({ resource, record }: { resource: AdminForthResource, record: any }) {
266
269
  const tableName = resource.table;
267
270
  const columns = Object.keys(record);
271
+
272
+ // const q = `INSERT INTO ${tableName} (${columns.join(', ')}) VALUES (${columns.map((colName) => {
273
+ // const colType = resource.dataSourceColumns.find((col) => col.name == colName)._underlineType;
274
+ // if (colType.startsWith('Int') || colType.startsWith('UInt') || colType.startsWith('Float')) {
275
+ // return record[colName];
276
+ // } else {
277
+ // return "'"+record[colName]+"'"
278
+ // }
279
+ // }).join(', ') })`;
280
+ // console.log('🪲 Clickhouse Query', q) ;
281
+ // await this.client.command({
282
+ // query: q,
283
+
284
+ // });
285
+
286
+ console.log('🪲 Clickhouse Insert', record);
287
+
268
288
  await this.client.insert({
289
+ database: this.dbName,
269
290
  table: tableName,
270
- values: columns.map((colName) => {
271
- return {[colName]: record[colName]};
272
- }),
273
- format: 'JSONEachRow',
274
- })
291
+ columns: columns,
292
+ values: [Object.values(record)],
293
+ });
294
+
295
+ console.log('🪲 Clickhouse Query done');
275
296
 
276
297
  }
277
298
 
@@ -286,8 +307,15 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
286
307
  }
287
308
 
288
309
  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);
310
+ const pkColumn = resource.dataSourceColumns.find((col) => col.primaryKey);
311
+ await this.client.command(
312
+ {
313
+ query: `ALTER TABLE ${this.dbName}.${resource.table} DELETE WHERE ${
314
+ pkColumn.name
315
+ } = {recordId:${pkColumn._underlineType}}`,
316
+ query_params: { recordId },
317
+ }
318
+ );
291
319
  }
292
320
 
293
321
  close() {
@@ -83,28 +83,9 @@ class MongoConnector extends AdminForthBaseConnector implements IAdminForthDataS
83
83
  }
84
84
 
85
85
  return value;
86
- }
87
-
88
- async getRecordByPrimaryKeyWithOriginalTypes(resource, key) {
89
- const tableName = resource.table;
90
- const collection = this.db.db().collection(tableName);
91
- const row = await collection.findOne({ [this.getPrimaryKey(resource)]: key });
92
- if (!row) {
93
- return null;
94
- }
95
- const newRow = {};
96
- for (const [key_1, value] of Object.entries(row)) {
97
- const dbKey = resource.dataSourceColumns.find((col) => col.name == key_1);
98
- if (!dbKey) {
99
- continue; // should I continue or throw an error?
100
- throw new Error(`Resource '${resource.table}' has no column '${key_1}' defined`);
101
- }
102
- newRow[key_1] = value;
103
- }
104
- console.log('newRow', newRow);
105
- return newRow;
106
86
  }
107
87
 
88
+
108
89
  setFieldValue(field, value) {
109
90
  if (field.type == AdminForthDataTypes.DATETIME) {
110
91
  if (!value) {
@@ -123,7 +104,7 @@ class MongoConnector extends AdminForthBaseConnector implements IAdminForthDataS
123
104
  return value;
124
105
  }
125
106
 
126
- async getDataWithOriginalTypes({ resource, limit, offset, sort, filters }) {
107
+ async getDataWithOriginalTypes({ resource, limit, offset, sort, filters, getTotals }) {
127
108
  // const columns = resource.dataSourceColumns.filter(c=> !c.virtual).map((col) => col.name).join(', ');
128
109
  const tableName = resource.table;
129
110
 
@@ -132,7 +113,10 @@ class MongoConnector extends AdminForthBaseConnector implements IAdminForthDataS
132
113
  for (const filter of filters) {
133
114
  query[filter.field] = this.OperatorsMap[filter.operator](filter.value);
134
115
  }
135
- const total = await collection.countDocuments(query);
116
+ let total = 0;
117
+ if (getTotals) {
118
+ total = await collection.countDocuments(query);
119
+ }
136
120
  const result = await collection.find(query)
137
121
  .sort(sort)
138
122
  .skip(offset)
@@ -164,24 +164,9 @@ class PostgresConnector extends AdminForthBaseConnector implements IAdminForthDa
164
164
  }
165
165
 
166
166
  return value;
167
- }
168
-
169
-
170
- async getRecordByPrimaryKeyWithOriginalTypes(resource, key) {
171
- const tableName = resource.table;
172
- const columns = resource.dataSourceColumns.map((col) => `"${col.name}"`).join(', ');
173
- const stmt = await this.db.query(`SELECT ${columns} FROM ${tableName} WHERE ${this.getPrimaryKey(resource)} = $1`, [key]);
174
- const row = stmt.rows[0];
175
- if (!row) {
176
- return null;
177
- }
178
- const newRow = {};
179
- for (const [key_1, value] of Object.entries(row)) {
180
- newRow[key_1] = value;
181
- }
182
- return newRow;
183
167
  }
184
168
 
169
+
185
170
  setFieldValue(field, value) {
186
171
  if (field.type == AdminForthDataTypes.DATETIME) {
187
172
  if (!value) {
@@ -198,7 +183,7 @@ class PostgresConnector extends AdminForthBaseConnector implements IAdminForthDa
198
183
  return value;
199
184
  }
200
185
 
201
- async getDataWithOriginalTypes({ resource, limit, offset, sort, filters }) {
186
+ async getDataWithOriginalTypes({ resource, limit, offset, sort, filters, getTotals }) {
202
187
  const columns = resource.dataSourceColumns.map((col) => `"${col.name}"`).join(', ');
203
188
  const tableName = resource.table;
204
189
 
@@ -246,8 +231,10 @@ class PostgresConnector extends AdminForthBaseConnector implements IAdminForthDa
246
231
  }
247
232
  const stmt = await this.db.query(selectQuery, d);
248
233
  const rows = stmt.rows;
249
-
250
- const total = (await this.db.query(`SELECT COUNT(*) FROM ${tableName} ${where}`, filterValues)).rows[0].count;
234
+ let total = 0;
235
+ if (getTotals) {
236
+ total = (await this.db.query(`SELECT COUNT(*) FROM ${tableName} ${where}`, filterValues)).rows[0].count;
237
+ }
251
238
  return {
252
239
  data: rows.map((row) => {
253
240
  const newRow = {};
@@ -91,21 +91,6 @@ class SQLiteConnector extends AdminForthBaseConnector implements IAdminForthData
91
91
  return value;
92
92
  }
93
93
 
94
- async getRecordByPrimaryKeyWithOriginalTypes(resource: AdminForthResource, key: any): Promise<any> {
95
- const columns = resource.dataSourceColumns.map((col) => col.name).join(', ');
96
- const tableName = resource.table;
97
- const stmt = this.db.prepare(`SELECT ${columns} FROM ${tableName} WHERE ${this.getPrimaryKey(resource)} = ?`);
98
- const row = stmt.get(key);
99
- if (!row) {
100
- return null;
101
- }
102
- const newRow = {};
103
- for (const [key, value] of Object.entries(row)) {
104
- newRow[key] = value;
105
- }
106
- return newRow;
107
- }
108
-
109
94
  setFieldValue(field: AdminForthResourceColumn, value: any): any {
110
95
  if (field.type == AdminForthDataTypes.DATETIME) {
111
96
  if (!value) {
@@ -151,13 +136,7 @@ class SQLiteConnector extends AdminForthBaseConnector implements IAdminForthData
151
136
  };
152
137
 
153
138
 
154
- async getDataWithOriginalTypes({ resource, limit, offset, sort, filters }: {
155
- resource: AdminForthResource,
156
- limit: number,
157
- offset: number,
158
- sort: { field: string, direction: AdminForthSortDirections }[],
159
- filters: { field: string, operator: AdminForthFilterOperators, value: any }[]
160
- }): Promise<{ data: any[], total: number }> {
139
+ async getDataWithOriginalTypes({ resource, limit, offset, sort, filters, getTotals }): Promise<{ data: any[], total: number }> {
161
140
  const columns = resource.dataSourceColumns.map((col) => col.name).join(', ');
162
141
  const tableName = resource.table;
163
142
 
@@ -203,9 +182,11 @@ class SQLiteConnector extends AdminForthBaseConnector implements IAdminForthData
203
182
  }
204
183
  const rows = await stmt.all(d);
205
184
 
206
- const total = (
207
- await this.db.prepare(`SELECT COUNT(*) FROM ${tableName} ${where}`).get([...filterValues])
208
- )['COUNT(*)'];
185
+ let total = 0;
186
+ if (getTotals) {
187
+ const totalStmt = this.db.prepare(`SELECT COUNT(*) FROM ${tableName} ${where}`);
188
+ total = totalStmt.get([...filterValues])['COUNT(*)'];
189
+ }
209
190
 
210
191
  return {
211
192
  data: rows.map((row) => {
@@ -17,9 +17,19 @@ export default class AdminForthBaseConnector {
17
17
  }
18
18
  }
19
19
  getRecordByPrimaryKeyWithOriginalTypes(resource, id) {
20
- throw new Error('Method not implemented.');
20
+ return __awaiter(this, void 0, void 0, function* () {
21
+ const data = yield this.getDataWithOriginalTypes({
22
+ resource,
23
+ limit: 1,
24
+ offset: 0,
25
+ sort: [],
26
+ filters: [{ field: this.getPrimaryKey(resource), operator: AdminForthFilterOperators.EQ, value: id }],
27
+ getTotals: false
28
+ });
29
+ return data.data.length > 0 ? data.data[0] : null;
30
+ });
21
31
  }
22
- getDataWithOriginalTypes({ resource, limit, offset, sort, filters }) {
32
+ getDataWithOriginalTypes({ resource, limit, offset, sort, filters, getTotals }) {
23
33
  throw new Error('Method not implemented.');
24
34
  }
25
35
  discoverFields(resource) {
@@ -64,7 +74,7 @@ export default class AdminForthBaseConnector {
64
74
  }
65
75
  });
66
76
  }
67
- const d = yield this.getDataWithOriginalTypes({ resource, limit, offset, sort, filters });
77
+ const d = yield this.getDataWithOriginalTypes({ resource, limit, offset, sort, filters, getTotals: true });
68
78
  // call getFieldValue for each field
69
79
  d.data.map((record) => {
70
80
  for (const col of resource.dataSourceColumns) {
@@ -38,6 +38,17 @@ class ClickhouseConnector extends AdminForthBaseConnector {
38
38
  // create connection here
39
39
  this.client = createClient({
40
40
  url: url.replace('clickhouse://', 'http://'),
41
+ clickhouse_settings: {
42
+ // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
43
+ date_time_input_format: 'best_effort',
44
+ // Recommended for cluster usage to avoid situations where a query processing error occurred after the response code,
45
+ // and HTTP headers were already sent to the client.
46
+ // See https://clickhouse.com/docs/en/interfaces/http/#response-buffering
47
+ wait_end_of_query: 1,
48
+ },
49
+ // log:{
50
+ // level: ClickHouseLogLevel.TRACE,
51
+ // }
41
52
  });
42
53
  }
43
54
  discoverFields(resource) {
@@ -108,7 +119,8 @@ class ClickhouseConnector extends AdminForthBaseConnector {
108
119
  else if (field._underlineType.startsWith('DateTime')
109
120
  || field._underlineType.startsWith('String')
110
121
  || field._underlineType.startsWith('FixedString')) {
111
- return dayjs(value).toISOString();
122
+ const v = dayjs(value).toISOString();
123
+ return v;
112
124
  }
113
125
  else {
114
126
  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}"`);
@@ -133,22 +145,6 @@ class ClickhouseConnector extends AdminForthBaseConnector {
133
145
  }
134
146
  return value;
135
147
  }
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
148
  setFieldValue(field, value) {
153
149
  if (field.type == AdminForthDataTypes.DATETIME) {
154
150
  if (!value) {
@@ -162,7 +158,8 @@ class ClickhouseConnector extends AdminForthBaseConnector {
162
158
  || field._underlineType.startsWith('String')
163
159
  || field._underlineType.startsWith('FixedString')) {
164
160
  // value is iso string now, convert to unix timestamp
165
- return dayjs(value).toISOString();
161
+ const iso = dayjs(value).toISOString();
162
+ return iso;
166
163
  }
167
164
  }
168
165
  else if (field.type == AdminForthDataTypes.BOOLEAN) {
@@ -180,11 +177,12 @@ class ClickhouseConnector extends AdminForthBaseConnector {
180
177
  return value;
181
178
  }
182
179
  getDataWithOriginalTypes(_a) {
183
- return __awaiter(this, arguments, void 0, function* ({ resource, limit, offset, sort, filters }) {
180
+ return __awaiter(this, arguments, void 0, function* ({ resource, limit, offset, sort, filters, getTotals }) {
184
181
  const columns = resource.dataSourceColumns.map((col) => col.name).join(', ');
185
182
  const tableName = resource.table;
186
183
  const where = filters.length ? `WHERE ${filters.map((f, i) => {
187
- let placeholder = `{f${i}}`;
184
+ const column = resource.dataSourceColumns.find((col) => col.name == f.field);
185
+ let placeholder = `{f${i}:${column._underlineType}}`;
188
186
  let field = f.field;
189
187
  let operator = this.OperatorsMap[f.operator];
190
188
  if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
@@ -222,12 +220,17 @@ class ClickhouseConnector extends AdminForthBaseConnector {
222
220
  console.log('🪲 Clickhouse Query', q, 'params:', d);
223
221
  }
224
222
  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(*)'];
223
+ let total = 0;
224
+ if (getTotals) {
225
+ const countQ = yield this.client.query({
226
+ query: `SELECT COUNT(*) FROM ${tableName} ${where}`,
227
+ format: 'JSONEachRow',
228
+ query_params: d,
229
+ });
230
+ const countResp = yield countQ.json();
231
+ console.log('🪲 Clickhouse Query count', countResp);
232
+ total = countResp[0]['COUNT()'];
233
+ }
231
234
  return {
232
235
  data: rows.map((row) => {
233
236
  const newRow = {};
@@ -258,13 +261,26 @@ class ClickhouseConnector extends AdminForthBaseConnector {
258
261
  return __awaiter(this, arguments, void 0, function* ({ resource, record }) {
259
262
  const tableName = resource.table;
260
263
  const columns = Object.keys(record);
264
+ // const q = `INSERT INTO ${tableName} (${columns.join(', ')}) VALUES (${columns.map((colName) => {
265
+ // const colType = resource.dataSourceColumns.find((col) => col.name == colName)._underlineType;
266
+ // if (colType.startsWith('Int') || colType.startsWith('UInt') || colType.startsWith('Float')) {
267
+ // return record[colName];
268
+ // } else {
269
+ // return "'"+record[colName]+"'"
270
+ // }
271
+ // }).join(', ') })`;
272
+ // console.log('🪲 Clickhouse Query', q) ;
273
+ // await this.client.command({
274
+ // query: q,
275
+ // });
276
+ console.log('🪲 Clickhouse Insert', record);
261
277
  yield this.client.insert({
278
+ database: this.dbName,
262
279
  table: tableName,
263
- values: columns.map((colName) => {
264
- return { [colName]: record[colName] };
265
- }),
266
- format: 'JSONEachRow',
280
+ columns: columns,
281
+ values: [Object.values(record)],
267
282
  });
283
+ console.log('🪲 Clickhouse Query done');
268
284
  });
269
285
  }
270
286
  updateRecord(_a) {
@@ -279,8 +295,11 @@ class ClickhouseConnector extends AdminForthBaseConnector {
279
295
  }
280
296
  deleteRecord(_a) {
281
297
  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);
298
+ const pkColumn = resource.dataSourceColumns.find((col) => col.primaryKey);
299
+ yield this.client.command({
300
+ query: `ALTER TABLE ${this.dbName}.${resource.table} DELETE WHERE ${pkColumn.name} = {recordId:${pkColumn._underlineType}}`,
301
+ query_params: { recordId },
302
+ });
284
303
  });
285
304
  }
286
305
  close() {
@@ -86,27 +86,6 @@ class MongoConnector extends AdminForthBaseConnector {
86
86
  }
87
87
  return value;
88
88
  }
89
- getRecordByPrimaryKeyWithOriginalTypes(resource, key) {
90
- return __awaiter(this, void 0, void 0, function* () {
91
- const tableName = resource.table;
92
- const collection = this.db.db().collection(tableName);
93
- const row = yield collection.findOne({ [this.getPrimaryKey(resource)]: key });
94
- if (!row) {
95
- return null;
96
- }
97
- const newRow = {};
98
- for (const [key_1, value] of Object.entries(row)) {
99
- const dbKey = resource.dataSourceColumns.find((col) => col.name == key_1);
100
- if (!dbKey) {
101
- continue; // should I continue or throw an error?
102
- throw new Error(`Resource '${resource.table}' has no column '${key_1}' defined`);
103
- }
104
- newRow[key_1] = value;
105
- }
106
- console.log('newRow', newRow);
107
- return newRow;
108
- });
109
- }
110
89
  setFieldValue(field, value) {
111
90
  if (field.type == AdminForthDataTypes.DATETIME) {
112
91
  if (!value) {
@@ -127,7 +106,7 @@ class MongoConnector extends AdminForthBaseConnector {
127
106
  return value;
128
107
  }
129
108
  getDataWithOriginalTypes(_a) {
130
- return __awaiter(this, arguments, void 0, function* ({ resource, limit, offset, sort, filters }) {
109
+ return __awaiter(this, arguments, void 0, function* ({ resource, limit, offset, sort, filters, getTotals }) {
131
110
  // const columns = resource.dataSourceColumns.filter(c=> !c.virtual).map((col) => col.name).join(', ');
132
111
  const tableName = resource.table;
133
112
  const collection = this.db.db().collection(tableName);
@@ -135,7 +114,10 @@ class MongoConnector extends AdminForthBaseConnector {
135
114
  for (const filter of filters) {
136
115
  query[filter.field] = this.OperatorsMap[filter.operator](filter.value);
137
116
  }
138
- const total = yield collection.countDocuments(query);
117
+ let total = 0;
118
+ if (getTotals) {
119
+ total = yield collection.countDocuments(query);
120
+ }
139
121
  const result = yield collection.find(query)
140
122
  .sort(sort)
141
123
  .skip(offset)
@@ -167,22 +167,6 @@ class PostgresConnector extends AdminForthBaseConnector {
167
167
  }
168
168
  return value;
169
169
  }
170
- getRecordByPrimaryKeyWithOriginalTypes(resource, key) {
171
- return __awaiter(this, void 0, void 0, function* () {
172
- const tableName = resource.table;
173
- const columns = resource.dataSourceColumns.map((col) => `"${col.name}"`).join(', ');
174
- const stmt = yield this.db.query(`SELECT ${columns} FROM ${tableName} WHERE ${this.getPrimaryKey(resource)} = $1`, [key]);
175
- const row = stmt.rows[0];
176
- if (!row) {
177
- return null;
178
- }
179
- const newRow = {};
180
- for (const [key_1, value] of Object.entries(row)) {
181
- newRow[key_1] = value;
182
- }
183
- return newRow;
184
- });
185
- }
186
170
  setFieldValue(field, value) {
187
171
  if (field.type == AdminForthDataTypes.DATETIME) {
188
172
  if (!value) {
@@ -201,7 +185,7 @@ class PostgresConnector extends AdminForthBaseConnector {
201
185
  return value;
202
186
  }
203
187
  getDataWithOriginalTypes(_a) {
204
- return __awaiter(this, arguments, void 0, function* ({ resource, limit, offset, sort, filters }) {
188
+ return __awaiter(this, arguments, void 0, function* ({ resource, limit, offset, sort, filters, getTotals }) {
205
189
  const columns = resource.dataSourceColumns.map((col) => `"${col.name}"`).join(', ');
206
190
  const tableName = resource.table;
207
191
  let totalCounter = 1;
@@ -248,7 +232,10 @@ class PostgresConnector extends AdminForthBaseConnector {
248
232
  }
249
233
  const stmt = yield this.db.query(selectQuery, d);
250
234
  const rows = stmt.rows;
251
- const total = (yield this.db.query(`SELECT COUNT(*) FROM ${tableName} ${where}`, filterValues)).rows[0].count;
235
+ let total = 0;
236
+ if (getTotals) {
237
+ total = (yield this.db.query(`SELECT COUNT(*) FROM ${tableName} ${where}`, filterValues)).rows[0].count;
238
+ }
252
239
  return {
253
240
  data: rows.map((row) => {
254
241
  const newRow = {};
@@ -121,22 +121,6 @@ class SQLiteConnector extends AdminForthBaseConnector {
121
121
  }
122
122
  return value;
123
123
  }
124
- getRecordByPrimaryKeyWithOriginalTypes(resource, key) {
125
- return __awaiter(this, void 0, void 0, function* () {
126
- const columns = resource.dataSourceColumns.map((col) => col.name).join(', ');
127
- const tableName = resource.table;
128
- const stmt = this.db.prepare(`SELECT ${columns} FROM ${tableName} WHERE ${this.getPrimaryKey(resource)} = ?`);
129
- const row = stmt.get(key);
130
- if (!row) {
131
- return null;
132
- }
133
- const newRow = {};
134
- for (const [key, value] of Object.entries(row)) {
135
- newRow[key] = value;
136
- }
137
- return newRow;
138
- });
139
- }
140
124
  setFieldValue(field, value) {
141
125
  if (field.type == AdminForthDataTypes.DATETIME) {
142
126
  if (!value) {
@@ -166,7 +150,7 @@ class SQLiteConnector extends AdminForthBaseConnector {
166
150
  return value;
167
151
  }
168
152
  getDataWithOriginalTypes(_a) {
169
- return __awaiter(this, arguments, void 0, function* ({ resource, limit, offset, sort, filters }) {
153
+ return __awaiter(this, arguments, void 0, function* ({ resource, limit, offset, sort, filters, getTotals }) {
170
154
  const columns = resource.dataSourceColumns.map((col) => col.name).join(', ');
171
155
  const tableName = resource.table;
172
156
  const where = filters.length ? `WHERE ${filters.map((f, i) => {
@@ -205,7 +189,11 @@ class SQLiteConnector extends AdminForthBaseConnector {
205
189
  console.log('🪲 SQLITE Query', q, 'params:', d);
206
190
  }
207
191
  const rows = yield stmt.all(d);
208
- const total = (yield this.db.prepare(`SELECT COUNT(*) FROM ${tableName} ${where}`).get([...filterValues]))['COUNT(*)'];
192
+ let total = 0;
193
+ if (getTotals) {
194
+ const totalStmt = this.db.prepare(`SELECT COUNT(*) FROM ${tableName} ${where}`);
195
+ total = totalStmt.get([...filterValues])['COUNT(*)'];
196
+ }
209
197
  return {
210
198
  data: rows.map((row) => {
211
199
  const newRow = {};
@@ -607,9 +607,8 @@ export default class AdminForthRestAPI {
607
607
  server.endpoint({
608
608
  method: 'POST',
609
609
  path: '/start_bulk_action',
610
- handler: (_11) => __awaiter(this, [_11], void 0, function* ({ body }) {
611
- const { resourceId, actionId, recordIds, adminUser } = body;
612
- process.env.HEAVY_DEBUG && console.log('🪲 starting bulk action', body);
610
+ handler: (_11) => __awaiter(this, [_11], void 0, function* ({ body, adminUser }) {
611
+ const { resourceId, actionId, recordIds } = body;
613
612
  const resource = this.adminforth.config.resources.find((res) => res.resourceId == resourceId);
614
613
  if (!resource) {
615
614
  return { error: `Resource '${resourceId}' not found` };
@@ -232,8 +232,12 @@ const columnError = (column) => {
232
232
 
233
233
 
234
234
  const setCurrentValue = (key, value) => {
235
- currentValues.value[key] = value;
236
-
235
+ const col = props.resource.columns.find((column) => column.name === key);
236
+ if (['integer', 'float'].includes(col.type) && (value || value === 0)) {
237
+ currentValues.value[key] = +value;
238
+ } else {
239
+ currentValues.value[key] = value;
240
+ }
237
241
  emit('update:record', currentValues.value);
238
242
  };
239
243
 
@@ -242,8 +246,7 @@ onMounted(() => {
242
246
  currentValues.value[key] = props.record[key];
243
247
  });
244
248
  initFlowbite();
245
-
246
-
249
+ emit('update:isValid', isValid.value);
247
250
  });
248
251
 
249
252
  const columnOptions = computedAsync(async () => {
@@ -267,7 +270,6 @@ const columnOptions = computedAsync(async () => {
267
270
 
268
271
  }, {});
269
272
 
270
- const coreStore = useCoreStore();
271
273
 
272
274
  const editableColumns = computed(() => {
273
275
  const mode = props.record ? 'edit' : 'create';
@@ -119,6 +119,7 @@ onMounted(async () => {
119
119
  });
120
120
 
121
121
  async function saveRecord() {
122
+ console.log('saveRecord isValid', isValid.value);
122
123
  if (!isValid.value) {
123
124
  validating.value = true;
124
125
  return;
@@ -695,9 +695,8 @@ export default class AdminForthRestAPI {
695
695
  server.endpoint({
696
696
  method: 'POST',
697
697
  path: '/start_bulk_action',
698
- handler: async ({ body }) => {
699
- const { resourceId, actionId, recordIds, adminUser } = body;
700
- process.env.HEAVY_DEBUG && console.log('🪲 starting bulk action', body);
698
+ handler: async ({ body, adminUser }) => {
699
+ const { resourceId, actionId, recordIds } = body;
701
700
  const resource = this.adminforth.config.resources.find((res) => res.resourceId == resourceId);
702
701
  if (!resource) {
703
702
  return { error: `Resource '${resourceId}' not found` };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adminforth",
3
- "version": "1.2.27",
3
+ "version": "1.2.29",
4
4
  "description": "OpenSource Vue3 powered forth-generation admin panel",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -15,6 +15,7 @@
15
15
  "license": "ISC",
16
16
  "type": "module",
17
17
  "dependencies": {
18
+ "@clickhouse/client": "^1.4.0",
18
19
  "better-sqlite3": "^10.0.0",
19
20
  "crypto": "^1.0.1",
20
21
  "dayjs": "^1.11.11",
@@ -25,8 +26,7 @@
25
26
  "mongodb": "6.6",
26
27
  "node-fetch": "^3.3.2",
27
28
  "pg": "^8.11.5",
28
- "uuid": "^9.0.1",
29
- "@clickhouse/client": "^1.4.0"
29
+ "uuid": "^9.0.1"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/node": "^20.14.2",
@@ -232,8 +232,12 @@ const columnError = (column) => {
232
232
 
233
233
 
234
234
  const setCurrentValue = (key, value) => {
235
- currentValues.value[key] = value;
236
-
235
+ const col = props.resource.columns.find((column) => column.name === key);
236
+ if (['integer', 'float'].includes(col.type) && (value || value === 0)) {
237
+ currentValues.value[key] = +value;
238
+ } else {
239
+ currentValues.value[key] = value;
240
+ }
237
241
  emit('update:record', currentValues.value);
238
242
  };
239
243
 
@@ -242,8 +246,7 @@ onMounted(() => {
242
246
  currentValues.value[key] = props.record[key];
243
247
  });
244
248
  initFlowbite();
245
-
246
-
249
+ emit('update:isValid', isValid.value);
247
250
  });
248
251
 
249
252
  const columnOptions = computedAsync(async () => {
@@ -267,7 +270,6 @@ const columnOptions = computedAsync(async () => {
267
270
 
268
271
  }, {});
269
272
 
270
- const coreStore = useCoreStore();
271
273
 
272
274
  const editableColumns = computed(() => {
273
275
  const mode = props.record ? 'edit' : 'create';
@@ -119,6 +119,7 @@ onMounted(async () => {
119
119
  });
120
120
 
121
121
  async function saveRecord() {
122
+ console.log('saveRecord isValid', isValid.value);
122
123
  if (!isValid.value) {
123
124
  validating.value = true;
124
125
  return;
@@ -93,7 +93,10 @@ export interface IExpressHttpServer extends IHttpServer {
93
93
  export interface IAdminForthDataSourceConnector {
94
94
 
95
95
  /**
96
- * Function which will be called to fetch record from database.
96
+ * Optional.
97
+ * You an redefine this function to define how one record should be fetched from database.
98
+ * You you will not redefine it, AdminForth will use {@link IAdminForthDataSourceConnector.getData} with limit 1 and offset 0 and
99
+ * filter by primary key.
97
100
  */
98
101
  getRecordByPrimaryKeyWithOriginalTypes(resource: AdminForthResource, recordId: string): Promise<any>;
99
102
 
@@ -142,12 +145,13 @@ export interface IAdminForthDataSourceConnector {
142
145
  *
143
146
  * Fields are returned from db "as is" then {@link AdminForthBaseConnector.getData} will transform each field using {@link IAdminForthDataSourceConnector.getFieldValue}
144
147
  */
145
- getDataWithOriginalTypes({ resource, limit, offset, sort, filters }: {
148
+ getDataWithOriginalTypes({ resource, limit, offset, sort, filters, getTotals }: {
146
149
  resource: AdminForthResource,
147
150
  limit: number,
148
151
  offset: number,
149
152
  sort: { field: string, direction: AdminForthSortDirections }[],
150
- filters: { field: string, operator: AdminForthFilterOperators, value: any }[]
153
+ filters: { field: string, operator: AdminForthFilterOperators, value: any }[],
154
+ getTotals?: boolean
151
155
  }): Promise<{data: Array<any>, total: number}>;
152
156
 
153
157