adminforth 1.3.54-next.9 → 1.3.55-next.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/dist/auth.d.ts +31 -0
  2. package/dist/auth.d.ts.map +1 -0
  3. package/dist/auth.js +42 -56
  4. package/dist/auth.js.map +1 -0
  5. package/dist/basePlugin.d.ts +18 -0
  6. package/dist/basePlugin.d.ts.map +1 -0
  7. package/dist/basePlugin.js +1 -0
  8. package/dist/basePlugin.js.map +1 -0
  9. package/dist/dataConnectors/baseConnector.d.ts +94 -0
  10. package/dist/dataConnectors/baseConnector.d.ts.map +1 -0
  11. package/dist/dataConnectors/baseConnector.js +108 -122
  12. package/dist/dataConnectors/baseConnector.js.map +1 -0
  13. package/dist/dataConnectors/clickhouse.d.ts +92 -0
  14. package/dist/dataConnectors/clickhouse.d.ts.map +1 -0
  15. package/dist/dataConnectors/clickhouse.js +132 -149
  16. package/dist/dataConnectors/clickhouse.js.map +1 -0
  17. package/dist/dataConnectors/mongo.d.ts +93 -0
  18. package/dist/dataConnectors/mongo.d.ts.map +1 -0
  19. package/dist/dataConnectors/mongo.js +75 -101
  20. package/dist/dataConnectors/mongo.js.map +1 -0
  21. package/dist/dataConnectors/postgres.d.ts +71 -0
  22. package/dist/dataConnectors/postgres.d.ts.map +1 -0
  23. package/dist/dataConnectors/postgres.js +124 -143
  24. package/dist/dataConnectors/postgres.js.map +1 -0
  25. package/dist/dataConnectors/sqlite.d.ts +67 -0
  26. package/dist/dataConnectors/sqlite.d.ts.map +1 -0
  27. package/dist/dataConnectors/sqlite.js +113 -130
  28. package/dist/dataConnectors/sqlite.js.map +1 -0
  29. package/dist/index.d.ts +92 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +197 -217
  32. package/dist/index.js.map +1 -0
  33. package/dist/modules/codeInjector.d.ts +35 -0
  34. package/dist/modules/codeInjector.d.ts.map +1 -0
  35. package/dist/modules/codeInjector.js +480 -486
  36. package/dist/modules/codeInjector.js.map +1 -0
  37. package/dist/modules/configValidator.d.ts +12 -0
  38. package/dist/modules/configValidator.d.ts.map +1 -0
  39. package/dist/modules/configValidator.js +31 -22
  40. package/dist/modules/configValidator.js.map +1 -0
  41. package/dist/modules/operationalResource.d.ts +17 -0
  42. package/dist/modules/operationalResource.d.ts.map +1 -0
  43. package/dist/modules/operationalResource.js +50 -70
  44. package/dist/modules/operationalResource.js.map +1 -0
  45. package/dist/modules/restApi.d.ts +10 -0
  46. package/dist/modules/restApi.d.ts.map +1 -0
  47. package/dist/modules/restApi.js +104 -116
  48. package/dist/modules/restApi.js.map +1 -0
  49. package/dist/modules/styleGenerator.d.ts +9 -0
  50. package/dist/modules/styleGenerator.d.ts.map +1 -0
  51. package/dist/modules/styleGenerator.js +1 -0
  52. package/dist/modules/styleGenerator.js.map +1 -0
  53. package/dist/modules/styles.d.ts +96 -0
  54. package/dist/modules/styles.d.ts.map +1 -0
  55. package/dist/modules/styles.js +1 -0
  56. package/dist/modules/styles.js.map +1 -0
  57. package/dist/modules/utils.d.ts +39 -0
  58. package/dist/modules/utils.d.ts.map +1 -0
  59. package/dist/modules/utils.js +1 -0
  60. package/dist/modules/utils.js.map +1 -0
  61. package/dist/plugins/audit-log/types.d.ts +35 -0
  62. package/dist/plugins/audit-log/types.d.ts.map +1 -0
  63. package/dist/plugins/audit-log/types.js +2 -0
  64. package/dist/plugins/audit-log/types.js.map +1 -0
  65. package/dist/plugins/chat-gpt/types.d.ts +82 -0
  66. package/dist/plugins/chat-gpt/types.d.ts.map +1 -0
  67. package/dist/plugins/chat-gpt/types.js +2 -0
  68. package/dist/plugins/chat-gpt/types.js.map +1 -0
  69. package/dist/plugins/email-password-reset/types.d.ts +28 -0
  70. package/dist/plugins/email-password-reset/types.d.ts.map +1 -0
  71. package/dist/plugins/email-password-reset/types.js +2 -0
  72. package/dist/plugins/email-password-reset/types.js.map +1 -0
  73. package/dist/plugins/foreign-inline-list/types.d.ts +19 -0
  74. package/dist/plugins/foreign-inline-list/types.d.ts.map +1 -0
  75. package/dist/plugins/foreign-inline-list/types.js +2 -0
  76. package/dist/plugins/foreign-inline-list/types.js.map +1 -0
  77. package/dist/plugins/import-export/types.d.ts +3 -0
  78. package/dist/plugins/import-export/types.d.ts.map +1 -0
  79. package/dist/plugins/import-export/types.js +2 -0
  80. package/dist/plugins/import-export/types.js.map +1 -0
  81. package/dist/plugins/rich-editor/custom/async-queue.d.ts +8 -0
  82. package/dist/plugins/rich-editor/custom/async-queue.d.ts.map +1 -0
  83. package/dist/plugins/rich-editor/custom/async-queue.js +29 -0
  84. package/dist/plugins/rich-editor/custom/async-queue.js.map +1 -0
  85. package/dist/plugins/rich-editor/dist/custom/async-queue.d.ts +8 -0
  86. package/dist/plugins/rich-editor/dist/custom/async-queue.d.ts.map +1 -0
  87. package/dist/plugins/rich-editor/dist/custom/async-queue.js +29 -0
  88. package/dist/plugins/rich-editor/dist/custom/async-queue.js.map +1 -0
  89. package/dist/plugins/rich-editor/types.d.ts +153 -0
  90. package/dist/plugins/rich-editor/types.d.ts.map +1 -0
  91. package/dist/plugins/rich-editor/types.js +16 -0
  92. package/dist/plugins/rich-editor/types.js.map +1 -0
  93. package/dist/plugins/two-factors-auth/types.d.ts +18 -0
  94. package/dist/plugins/two-factors-auth/types.d.ts.map +1 -0
  95. package/dist/plugins/two-factors-auth/types.js +2 -0
  96. package/dist/plugins/two-factors-auth/types.js.map +1 -0
  97. package/dist/plugins/upload/types.d.ts +132 -0
  98. package/dist/plugins/upload/types.d.ts.map +1 -0
  99. package/dist/plugins/upload/types.js +2 -0
  100. package/dist/plugins/upload/types.js.map +1 -0
  101. package/dist/servers/express.d.ts +18 -0
  102. package/dist/servers/express.d.ts.map +1 -0
  103. package/dist/servers/express.js +30 -42
  104. package/dist/servers/express.js.map +1 -0
  105. package/dist/spa/index.html +2 -2
  106. package/dist/spa/package-lock.json +87 -1
  107. package/dist/spa/package.json +4 -1
  108. package/dist/spa/src/App.vue +154 -50
  109. package/dist/spa/src/components/AcceptModal.vue +1 -1
  110. package/dist/spa/src/components/Breadcrumbs.vue +1 -1
  111. package/dist/spa/src/components/CustomDatePicker.vue +1 -1
  112. package/dist/spa/src/components/CustomDateRangePicker.vue +1 -1
  113. package/dist/spa/src/components/CustomRangePicker.vue +9 -5
  114. package/dist/spa/src/components/Dropdown.vue +4 -4
  115. package/dist/spa/src/components/Filters.vue +2 -2
  116. package/dist/spa/src/components/MenuLink.vue +3 -0
  117. package/dist/spa/src/components/ResourceForm.vue +67 -36
  118. package/dist/spa/src/components/ResourceListTable.vue +216 -144
  119. package/dist/spa/src/components/SkeleteLoader.vue +4 -4
  120. package/dist/spa/src/components/Toast.vue +3 -2
  121. package/dist/spa/src/components/ValueRenderer.vue +81 -6
  122. package/dist/spa/src/composables/useFrontendApi.ts +1 -1
  123. package/dist/spa/src/composables/useStores.ts +18 -14
  124. package/dist/spa/src/index.scss +4 -0
  125. package/{spa → dist/spa}/src/renderers/CompactUUID.vue +4 -4
  126. package/{spa → dist/spa}/src/renderers/CountryFlag.vue +2 -2
  127. package/dist/spa/src/router/index.ts +4 -8
  128. package/dist/spa/src/spa_types/core.ts +2 -0
  129. package/dist/spa/src/stores/core.ts +6 -2
  130. package/dist/spa/src/stores/filters.ts +15 -10
  131. package/dist/spa/src/stores/toast.ts +22 -6
  132. package/dist/spa/src/types/AdminForthConfig.ts +340 -55
  133. package/dist/spa/src/types/FrontendAPI.ts +52 -30
  134. package/dist/spa/src/utils.ts +59 -2
  135. package/dist/spa/src/views/CreateView.vue +15 -4
  136. package/dist/spa/src/views/EditView.vue +20 -7
  137. package/dist/spa/src/views/ListView.vue +132 -38
  138. package/dist/spa/src/views/LoginView.vue +50 -18
  139. package/dist/spa/src/views/ShowView.vue +25 -15
  140. package/dist/types/AdminForthConfig.d.ts +1619 -0
  141. package/dist/types/AdminForthConfig.d.ts.map +1 -0
  142. package/dist/types/AdminForthConfig.js +1 -0
  143. package/dist/types/AdminForthConfig.js.map +1 -0
  144. package/{types/FrontendAPI.ts → dist/types/FrontendAPI.d.ts} +27 -52
  145. package/dist/types/FrontendAPI.d.ts.map +1 -0
  146. package/dist/types/FrontendAPI.js +1 -0
  147. package/dist/types/FrontendAPI.js.map +1 -0
  148. package/package.json +16 -6
  149. package/auth.ts +0 -140
  150. package/basePlugin.ts +0 -70
  151. package/dataConnectors/baseConnector.ts +0 -216
  152. package/dataConnectors/clickhouse.ts +0 -338
  153. package/dataConnectors/mongo.ts +0 -202
  154. package/dataConnectors/postgres.ts +0 -306
  155. package/dataConnectors/sqlite.ts +0 -254
  156. package/index.ts +0 -428
  157. package/modules/codeInjector.ts +0 -736
  158. package/modules/configValidator.ts +0 -571
  159. package/modules/operationalResource.ts +0 -98
  160. package/modules/restApi.ts +0 -718
  161. package/modules/styleGenerator.ts +0 -55
  162. package/modules/styles.ts +0 -126
  163. package/modules/utils.ts +0 -472
  164. package/servers/express.ts +0 -259
  165. package/spa/.eslintrc.cjs +0 -14
  166. package/spa/README.md +0 -39
  167. package/spa/env.d.ts +0 -1
  168. package/spa/index.html +0 -23
  169. package/spa/package-lock.json +0 -4602
  170. package/spa/package.json +0 -51
  171. package/spa/postcss.config.js +0 -6
  172. package/spa/public/assets/favicon.png +0 -0
  173. package/spa/src/App.vue +0 -418
  174. package/spa/src/assets/base.css +0 -2
  175. package/spa/src/assets/logo.svg +0 -19
  176. package/spa/src/components/AcceptModal.vue +0 -45
  177. package/spa/src/components/Breadcrumbs.vue +0 -41
  178. package/spa/src/components/BreadcrumbsWithButtons.vue +0 -26
  179. package/spa/src/components/CustomDatePicker.vue +0 -176
  180. package/spa/src/components/CustomDateRangePicker.vue +0 -218
  181. package/spa/src/components/CustomRangePicker.vue +0 -156
  182. package/spa/src/components/Dropdown.vue +0 -168
  183. package/spa/src/components/Filters.vue +0 -222
  184. package/spa/src/components/HelloWorld.vue +0 -17
  185. package/spa/src/components/MenuLink.vue +0 -27
  186. package/spa/src/components/ResourceForm.vue +0 -290
  187. package/spa/src/components/ResourceListTable.vue +0 -466
  188. package/spa/src/components/SingleSkeletLoader.vue +0 -13
  189. package/spa/src/components/SkeleteLoader.vue +0 -23
  190. package/spa/src/components/Toast.vue +0 -78
  191. package/spa/src/components/ValueRenderer.vue +0 -114
  192. package/spa/src/components/icons/IconCalendar.vue +0 -5
  193. package/spa/src/components/icons/IconCommunity.vue +0 -7
  194. package/spa/src/components/icons/IconDocumentation.vue +0 -7
  195. package/spa/src/components/icons/IconEcosystem.vue +0 -7
  196. package/spa/src/components/icons/IconSupport.vue +0 -7
  197. package/spa/src/components/icons/IconTime.vue +0 -5
  198. package/spa/src/components/icons/IconTooling.vue +0 -19
  199. package/spa/src/composables/useFrontendApi.ts +0 -26
  200. package/spa/src/composables/useStores.ts +0 -131
  201. package/spa/src/index.scss +0 -31
  202. package/spa/src/main.ts +0 -18
  203. package/spa/src/router/index.ts +0 -59
  204. package/spa/src/spa_types/core.ts +0 -53
  205. package/spa/src/stores/core.ts +0 -148
  206. package/spa/src/stores/filters.ts +0 -27
  207. package/spa/src/stores/modal.ts +0 -48
  208. package/spa/src/stores/toast.ts +0 -31
  209. package/spa/src/stores/user.ts +0 -72
  210. package/spa/src/utils.ts +0 -160
  211. package/spa/src/views/CreateView.vue +0 -167
  212. package/spa/src/views/EditView.vue +0 -170
  213. package/spa/src/views/ListView.vue +0 -352
  214. package/spa/src/views/LoginView.vue +0 -192
  215. package/spa/src/views/ResourceParent.vue +0 -17
  216. package/spa/src/views/ShowView.vue +0 -186
  217. package/spa/tailwind.config.js +0 -17
  218. package/spa/tsconfig.app.json +0 -14
  219. package/spa/tsconfig.json +0 -11
  220. package/spa/tsconfig.node.json +0 -19
  221. package/spa/vite.config.ts +0 -56
  222. package/tsconfig.json +0 -112
  223. package/types/AdminForthConfig.ts +0 -1762
  224. /package/{spa → dist/spa}/src/components/ThreeDotsMenu.vue +0 -0
@@ -1,216 +0,0 @@
1
- import { get } from "http";
2
- import { AdminForthResource, IAdminForthDataSourceConnectorBase, AdminForthSortDirections, AdminForthFilterOperators, AdminForthResourceColumn, IAdminForthSort, IAdminForthFilter } from "../types/AdminForthConfig.js";
3
- import { suggestIfTypo } from "../modules/utils.js";
4
-
5
-
6
- export default class AdminForthBaseConnector implements IAdminForthDataSourceConnectorBase {
7
- getPrimaryKey(resource: AdminForthResource): string {
8
- for (const col of resource.dataSourceColumns) {
9
- if (col.primaryKey) {
10
- return col.name;
11
- }
12
- }
13
- }
14
-
15
- async getRecordByPrimaryKeyWithOriginalTypes(resource: AdminForthResource, id: string): Promise<any> {
16
- const data = await this.getDataWithOriginalTypes({
17
- resource,
18
- limit: 1,
19
- offset: 0,
20
- sort: [],
21
- filters: [{ field: this.getPrimaryKey(resource), operator: AdminForthFilterOperators.EQ, value: id }],
22
- });
23
- return data.length > 0 ? data[0] : null;
24
- }
25
-
26
- getDataWithOriginalTypes({ resource, limit, offset, sort, filters }: {
27
- resource: AdminForthResource,
28
- limit: number,
29
- offset: number,
30
- sort: IAdminForthSort[],
31
- filters: IAdminForthFilter[],
32
- }): Promise<any[]> {
33
- throw new Error('Method not implemented.');
34
- }
35
-
36
- getCount({ resource, filters }: { resource: AdminForthResource; filters: { field: string; operator: AdminForthFilterOperators; value: any; }[]; }): Promise<number> {
37
- throw new Error('Method not implemented.');
38
- }
39
-
40
- discoverFields(resource: AdminForthResource): Promise<{ [key: string]: AdminForthResourceColumn; }> {
41
- throw new Error('Method not implemented.');
42
- }
43
-
44
- getFieldValue(field: AdminForthResourceColumn, value: any) {
45
- throw new Error('Method not implemented.');
46
- }
47
-
48
- setFieldValue(field: AdminForthResourceColumn, value: any) {
49
- throw new Error('Method not implemented.');
50
- }
51
-
52
- getMinMaxForColumnsWithOriginalTypes({ resource, columns }: { resource: AdminForthResource; columns: AdminForthResourceColumn[]; }): Promise<{ [key: string]: { min: any; max: any; }; }> {
53
- throw new Error('Method not implemented.');
54
- }
55
-
56
- createRecordOriginalValues({ resource, record }: { resource: AdminForthResource; record: any; }): Promise<void> {
57
- throw new Error('Method not implemented.');
58
- }
59
-
60
- async checkUnique(resource: AdminForthResource, column: AdminForthResourceColumn, value: any) {
61
- process.env.HEAVY_DEBUG && console.log('☝️🪲🪲🪲🪲 checkUnique|||', column, value);
62
- const existingRecord = await this.getData({
63
- resource,
64
- filters: [{ field: column.name, operator: AdminForthFilterOperators.EQ, value }],
65
- limit: 1,
66
- sort: [],
67
- offset: 0,
68
- getTotals: false
69
- });
70
-
71
- return existingRecord.data.length > 0;
72
- }
73
-
74
- async createRecord({ resource, record, adminUser }: {
75
- resource: AdminForthResource; record: any; adminUser: any;
76
- }): Promise<{ error?: string; ok: boolean; createdRecord?: any; }> {
77
- // transform value using setFieldValue and call createRecordOriginalValues
78
- const filledRecord = {...record};
79
- const recordWithOriginalValues = {...record};
80
-
81
- for (const col of resource.dataSourceColumns) {
82
- if (col.fillOnCreate) {
83
- if (filledRecord[col.name] === undefined) {
84
- filledRecord[col.name] = col.fillOnCreate({
85
- initialRecord: record,
86
- adminUser
87
- });
88
- }
89
- }
90
- recordWithOriginalValues[col.name] = this.setFieldValue(col, filledRecord[col.name]);
91
- }
92
-
93
- let error: string | null = null;
94
- await Promise.all(
95
- resource.dataSourceColumns.map(async (col) => {
96
- if (col.isUnique && !col.virtual && !error) {
97
- const exists = await this.checkUnique(resource, col, recordWithOriginalValues[col.name]);
98
- if (exists) {
99
- error = `Record with ${col.name} ${recordWithOriginalValues[col.name]} already exists`;
100
- }
101
- }
102
- })
103
- );
104
- if (error) {
105
- process.env.HEAVY_DEBUG && console.log('🪲🆕 check unique error', error);
106
- return { error, ok: false };
107
- }
108
-
109
- process.env.HEAVY_DEBUG && console.log('🪲🆕 creating record', recordWithOriginalValues);
110
- await this.createRecordOriginalValues({ resource, record: recordWithOriginalValues });
111
-
112
- return {
113
- ok: true,
114
- createdRecord: recordWithOriginalValues,
115
- }
116
- }
117
-
118
- updateRecordOriginalValues({ resource, recordId, newValues }: { resource: AdminForthResource; recordId: string; newValues: any; }): Promise<void> {
119
- throw new Error('Method not implemented.');
120
- }
121
-
122
- async updateRecord({ resource, recordId, newValues }: { resource: AdminForthResource; recordId: string; newValues: any; }): Promise<{ error?: string; ok: boolean; }> {
123
- // transform value using setFieldValue and call updateRecordOriginalValues
124
- const recordWithOriginalValues = {...newValues};
125
-
126
- for (const field of Object.keys(newValues)) {
127
- const col = resource.dataSourceColumns.find((col) => col.name == field);
128
- // todo instead of throwing error, we can just not use setFieldValue here, and pass original value to updateRecordOriginalValues
129
- // we might consider this because some users might not want to define all columns in resource with showIn:[], but still want to use them in hooks
130
- if (!col) {
131
- const similar = suggestIfTypo(resource.dataSourceColumns.map((col) => col.name), field);
132
- throw new Error(`
133
- Update record received field '${field}' (with value ${newValues[field]}), but such column not found in resource '${resource.resourceId}'. ${similar ? `Did you mean '${similar}'?` : ''}
134
- `);
135
- }
136
- recordWithOriginalValues[col.name] = this.setFieldValue(col, newValues[col.name]);
137
- }
138
-
139
- process.env.HEAVY_DEBUG && console.log(`🪲✏️ updating record id:${recordId}, values: ${JSON.stringify(recordWithOriginalValues)}`);
140
-
141
- await this.updateRecordOriginalValues({ resource, recordId, newValues: recordWithOriginalValues });
142
-
143
- return { ok: true };
144
- }
145
-
146
- deleteRecord({ resource, recordId }: { resource: AdminForthResource; recordId: string; }): Promise<boolean> {
147
- throw new Error('Method not implemented.');
148
- }
149
-
150
-
151
- async getData({ resource, limit, offset, sort, filters, getTotals }: {
152
- resource: AdminForthResource,
153
- limit: number,
154
- offset: number,
155
- sort: { field: string, direction: AdminForthSortDirections }[],
156
- filters: { field: string, operator: AdminForthFilterOperators, value: any }[],
157
- getTotals: boolean,
158
- }): Promise<{ data: any[], total: number }> {
159
- if (filters) {
160
- filters.map((f) => {
161
- const fieldObj = resource.dataSourceColumns.find((col) => col.name == f.field);
162
- if (!fieldObj) {
163
- const similar = suggestIfTypo(resource.dataSourceColumns.map((col) => col.name), f.field);
164
- throw new Error(`Field '${f.field}' not found in resource '${resource.resourceId}'. ${similar ? `Did you mean '${similar}'?` : ''}`);
165
- }
166
- if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
167
- f.value = f.value.map((val) => this.setFieldValue(fieldObj, val));
168
- } else {
169
- f.value = this.setFieldValue(fieldObj, f.value);
170
- }
171
- });
172
- }
173
-
174
- const promises: Promise<any>[] = [this.getDataWithOriginalTypes({ resource, limit, offset, sort, filters })];
175
- if (getTotals) {
176
- promises.push(this.getCount({ resource, filters }));
177
- } else {
178
- promises.push(Promise.resolve(undefined));
179
- }
180
-
181
- const [data, total] = await Promise.all(promises);
182
-
183
- // call getFieldValue for each field
184
- data.map((record) => {
185
- for (const col of resource.dataSourceColumns) {
186
- record[col.name] = this.getFieldValue(col, record[col.name]);
187
- }
188
- });
189
-
190
- return { data, total };
191
- }
192
-
193
- async getMinMaxForColumns({ resource, columns }: { resource: AdminForthResource; columns: AdminForthResourceColumn[]; }): Promise<{ [key: string]: { min: any; max: any; }; }> {
194
- const mm = await this.getMinMaxForColumnsWithOriginalTypes({ resource, columns });
195
- const result = {};
196
- for (const col of columns) {
197
- result[col.name] = {
198
- min: this.getFieldValue(col, mm[col.name].min),
199
- max: this.getFieldValue(col, mm[col.name].max),
200
- };
201
- }
202
- return result;
203
- }
204
-
205
- getRecordByPrimaryKey(resource: AdminForthResource, recordId: string): Promise<any> {
206
- return this.getRecordByPrimaryKeyWithOriginalTypes(resource, recordId).then((record) => {
207
- const newRecord = {};
208
- for (const col of resource.dataSourceColumns) {
209
- newRecord[col.name] = this.getFieldValue(col, record[col.name]);
210
- }
211
- return newRecord;
212
- });
213
- }
214
-
215
-
216
- }
@@ -1,338 +0,0 @@
1
- import { AdminForthDataTypes, AdminForthFilterOperators, AdminForthSortDirections, IAdminForthDataSourceConnector, AdminForthResource, AdminForthResourceColumn } from '../types/AdminForthConfig.js';
2
- import AdminForthBaseConnector from './baseConnector.js';
3
- import dayjs from 'dayjs';
4
- import { createClient } from '@clickhouse/client'
5
-
6
-
7
-
8
- class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForthDataSourceConnector {
9
-
10
- client: any;
11
- dbName: string;
12
- url: 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
- this.url = url;;
22
- // create connection here
23
- this.client = createClient({
24
- url: url.replace('clickhouse://', 'http://'),
25
- clickhouse_settings: {
26
- // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
27
- date_time_input_format: 'best_effort',
28
-
29
- // Recommended for cluster usage to avoid situations where a query processing error occurred after the response code,
30
- // and HTTP headers were already sent to the client.
31
- // See https://clickhouse.com/docs/en/interfaces/http/#response-buffering
32
- wait_end_of_query: 1,
33
- },
34
- // log:{
35
- // level: ClickHouseLogLevel.TRACE,
36
- // }
37
- });
38
-
39
- }
40
-
41
- async discoverFields(resource: AdminForthResource): Promise<{[key: string]: AdminForthResourceColumn}> {
42
- const tableName = resource.table;
43
-
44
- let rows;
45
- try {
46
- const q = await this.client.query({
47
- query: `SELECT * FROM system.columns WHERE table = '${tableName}' and database = '${this.dbName}'`,
48
- format: 'JSONEachRow',
49
- });
50
- rows = await q.json();
51
- } catch (e) {
52
- console.error(` 🛑Error connecting to datasource URL ${this.url}:`, e);
53
- return null;
54
- }
55
-
56
- const fieldTypes = {};
57
- rows.forEach((row) => {
58
- const field: any = {};
59
- const baseType = row.type;
60
- if (baseType.startsWith('Int') || baseType.startsWith('UInt')) {
61
- field.type = AdminForthDataTypes.INTEGER;
62
- } else if (baseType === 'FixedString' || baseType === 'String') {
63
- field.type = AdminForthDataTypes.STRING;
64
- // TODO
65
- // const length = baseType.match(/\d+/g);
66
- // field.maxLength = length ? parseInt(length[0]) : null;
67
- } else if (baseType == 'UUID') {
68
- field.type = AdminForthDataTypes.STRING;
69
- } else if (baseType.startsWith('Decimal')) {
70
- field.type = AdminForthDataTypes.DECIMAL;
71
- const [precision, scale] = baseType.match(/\d+/g);
72
- field.precision = parseInt(precision);
73
- field.scale = parseInt(scale);
74
- } else if (baseType.startsWith('Float')) {
75
- field.type = AdminForthDataTypes.FLOAT;
76
- } else if (baseType == 'DateTime64' || baseType == 'DateTime') {
77
- field.type = AdminForthDataTypes.DATETIME;
78
- } else if (baseType == 'Date' || baseType == 'Date64') {
79
- field.type = AdminForthDataTypes.DATE;
80
- } else if (baseType == 'Boolean') {
81
- field.type = AdminForthDataTypes.BOOLEAN;
82
- field._underlineType = 'boolean';
83
- } else {
84
- field.type = 'unknown'
85
- }
86
- field._underlineType = baseType;
87
- field._baseTypeDebug = baseType;
88
- field.required = row.notnull == 1;
89
- field.primaryKey = row.pk == 1;
90
- field.default = row.dflt_value;
91
- fieldTypes[row.name] = field
92
- });
93
- return fieldTypes;
94
- }
95
-
96
- getFieldValue(field: AdminForthResourceColumn, value: any): any {
97
- if (field.type == AdminForthDataTypes.DATETIME) {
98
- if (!value) {
99
- return null;
100
- }
101
- if (field._underlineType.startsWith('Int') || field._underlineType.startsWith('UInt')) {
102
- return dayjs.unix(+value).toISOString();
103
- } else if (field._underlineType.startsWith('DateTime')
104
- || field._underlineType.startsWith('String')
105
- || field._underlineType.startsWith('FixedString')) {
106
- const v = dayjs(value).toISOString();
107
- return v;
108
- } else {
109
- 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}"`);
110
- }
111
- } else if (field.type == AdminForthDataTypes.DATE) {
112
- if (!value) {
113
- return null;
114
- }
115
- return dayjs(value).toISOString().split('T')[0];
116
- } else if (field.type == AdminForthDataTypes.BOOLEAN) {
117
- return !!value;
118
- } else if (field.type == AdminForthDataTypes.JSON) {
119
- if (field._underlineType.startsWith('String') || field._underlineType.startsWith('FixedString')) {
120
- return JSON.parse(value);
121
- } else {
122
- console.error(`AdminForth: JSON field is not a string but ${field._underlineType}, this is not supported yet`);
123
- }
124
- }
125
- return value;
126
- }
127
-
128
- setFieldValue(field: AdminForthResourceColumn, value: any): any {
129
- if (field.type == AdminForthDataTypes.DATETIME) {
130
- if (!value) {
131
- return null;
132
- }
133
- if (field._underlineType.startsWith('Int') || field._underlineType.startsWith('UInt')) {
134
- // value is iso string now, convert to unix timestamp
135
- return dayjs(value).unix();
136
- } else if (field._underlineType.startsWith('DateTime')
137
- || field._underlineType.startsWith('String')
138
- || field._underlineType.startsWith('FixedString')) {
139
- // value is iso string now, convert to unix timestamp
140
- const iso = dayjs(value).toISOString();
141
- return iso;
142
- }
143
- } else if (field.type == AdminForthDataTypes.BOOLEAN) {
144
- return value ? 1 : 0;
145
- } else if (field.type == AdminForthDataTypes.JSON) {
146
- // check underline type is text or string
147
- if (field._underlineType.startsWith('String') || field._underlineType.startsWith('FixedString')) {
148
- return JSON.stringify(value);
149
- } else {
150
- console.error(`AdminForth: JSON field is not a string/text but ${field._underlineType}, this is not supported yet`);
151
- }
152
- }
153
-
154
- return value;
155
- }
156
-
157
- OperatorsMap = {
158
- [AdminForthFilterOperators.EQ]: '=',
159
- [AdminForthFilterOperators.NE]: '!=',
160
- [AdminForthFilterOperators.GT]: '>',
161
- [AdminForthFilterOperators.LT]: '<',
162
- [AdminForthFilterOperators.GTE]: '>=',
163
- [AdminForthFilterOperators.LTE]: '<=',
164
- [AdminForthFilterOperators.LIKE]: 'LIKE',
165
- [AdminForthFilterOperators.ILIKE]: 'ILIKE',
166
- [AdminForthFilterOperators.IN]: 'IN',
167
- [AdminForthFilterOperators.NIN]: 'NOT IN',
168
- };
169
-
170
- SortDirectionsMap = {
171
- [AdminForthSortDirections.asc]: 'ASC',
172
- [AdminForthSortDirections.desc]: 'DESC',
173
- };
174
-
175
- whereClause(
176
- resource: AdminForthResource,
177
- filters: { field: string, operator: AdminForthFilterOperators, value: any }[]
178
- ): string {
179
- return filters.length ? `WHERE ${filters.map((f, i) => {
180
- const column = resource.dataSourceColumns.find((col) => col.name == f.field);
181
- let placeholder = `{f${i}:${column._underlineType}}`;
182
- let field = f.field;
183
- let operator = this.OperatorsMap[f.operator];
184
- if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
185
- placeholder = `(${f.value.map((_, j) => `{p${i}_${j}:${
186
- column._underlineType
187
- }}`).join(', ')})`;
188
- }
189
-
190
- return `${field} ${operator} ${placeholder}`
191
- }).join(' AND ')}` : '';
192
- }
193
-
194
- whereParams(
195
- filters: { field: string, operator: AdminForthFilterOperators, value: any }[]
196
- ): any {
197
- const params = {};
198
- filters.length ? filters.forEach((f, i) => {
199
- // for arrays do set in map
200
- const v = f.value;
201
-
202
- if (f.operator == AdminForthFilterOperators.LIKE || f.operator == AdminForthFilterOperators.ILIKE) {
203
- params[`f${i}`] = `%${v}%`;
204
- } else if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
205
- v.forEach((_, j) => {
206
- params[`p${i}_${j}`] = v[j];
207
- });
208
- } else {
209
- params[`f${i}`] = v;
210
- }
211
- }) : [];
212
-
213
- return params;
214
- }
215
-
216
- async getDataWithOriginalTypes({ resource, limit, offset, sort, filters }: {
217
- resource: AdminForthResource,
218
- limit: number,
219
- offset: number,
220
- sort: { field: string, direction: AdminForthSortDirections }[],
221
- filters: { field: string, operator: AdminForthFilterOperators, value: any }[],
222
- }): Promise<any[]> {
223
- console.log('getDataWithOriginalTypes', resource, limit, offset, sort, filters);
224
- const columns = resource.dataSourceColumns.map((col) => col.name).join(', ');
225
- const tableName = resource.table;
226
-
227
- const where = this.whereClause(resource, filters);
228
-
229
- const params = this.whereParams(filters);
230
-
231
- const orderBy = sort.length ? `ORDER BY ${sort.map((s) => `${s.field} ${this.SortDirectionsMap[s.direction]}`).join(', ')}` : '';
232
-
233
-
234
- const q = `SELECT ${columns} FROM ${tableName} ${where} ${orderBy} LIMIT {limit:Int} OFFSET {offset:Int}`;
235
- const d = {
236
- ...params,
237
- limit,
238
- offset,
239
- };
240
-
241
- const stmt = await this.client.query({
242
- query: q,
243
- format: 'JSONEachRow',
244
- query_params: d,
245
- });
246
-
247
- const rows = await stmt.json();
248
-
249
- return rows.map((row) => {
250
- const newRow = {};
251
- for (const [key, value] of Object.entries(row)) {
252
- newRow[key] = value;
253
- }
254
- return newRow;
255
- });
256
- }
257
-
258
- async getCount({
259
- resource,
260
- filters,
261
- }: {
262
- resource: AdminForthResource;
263
- filters: { field: string, operator: AdminForthFilterOperators, value: any }[];
264
- }): Promise<number> {
265
- const tableName = resource.table;
266
- const where = this.whereClause(resource, filters);
267
- const d = this.whereParams(filters);
268
-
269
- const countQ = await this.client.query({
270
- query: `SELECT COUNT(*) as count FROM ${tableName} ${where}`,
271
- format: 'JSONEachRow',
272
- query_params: d,
273
- });
274
- const countResp = await countQ.json()
275
- return +countResp[0]['count'];
276
- }
277
-
278
- async getMinMaxForColumnsWithOriginalTypes({ resource, columns }: { resource: AdminForthResource, columns: AdminForthResourceColumn[] }): Promise<{ [key: string]: { min: any, max: any } }> {
279
- const tableName = resource.table;
280
- const result = {};
281
- await Promise.all(columns.map(async (col) => {
282
- const stmt = await this.client.query({
283
- query: `SELECT MIN(${col.name}) as min, MAX(${col.name}) as max FROM ${tableName}`,
284
- format: 'JSONEachRow',
285
- });
286
- const rows = await stmt.json();
287
- result[col.name] = {
288
- min: rows[0].min,
289
- max: rows[0].max,
290
- };
291
-
292
- }))
293
- return result;
294
- }
295
- async createRecordOriginalValues({ resource, record }: { resource: AdminForthResource, record: any }) {
296
- const tableName = resource.table;
297
- const columns = Object.keys(record);
298
- await this.client.insert({
299
- database: this.dbName,
300
- table: tableName,
301
- columns: columns,
302
- values: [Object.values(record)],
303
- });
304
- }
305
-
306
- async updateRecordOriginalValues({ resource, recordId, newValues }: { resource: AdminForthResource, recordId: any, newValues: any }) {
307
- const columnsWithPlaceholders = Object.keys(newValues).map((col) => {
308
- return `${col} = {${col}:${resource.dataSourceColumns.find((c) => c.name == col)._underlineType}}`
309
- });
310
-
311
- await this.client.command(
312
- {
313
- query: `ALTER TABLE ${this.dbName}.${resource.table} UPDATE ${columnsWithPlaceholders.join(', ')} WHERE ${this.getPrimaryKey(resource)} = {recordId:${resource.dataSourceColumns.find((c) => c.primaryKey)._underlineType}}`,
314
- query_params: { ...newValues, recordId },
315
- }
316
- );
317
- }
318
-
319
- async deleteRecord({ resource, recordId }: { resource: AdminForthResource, recordId: any }): Promise<boolean> {
320
- const pkColumn = resource.dataSourceColumns.find((col) => col.primaryKey);
321
- const res = await this.client.command(
322
- {
323
- query: `ALTER TABLE ${this.dbName}.${resource.table} DELETE WHERE ${
324
- pkColumn.name
325
- } = {recordId:${pkColumn._underlineType}}`,
326
- query_params: { recordId },
327
- }
328
- );
329
- // todo test what is in res
330
- return res;
331
- }
332
-
333
- close() {
334
- this.client.disconnect();
335
- }
336
- }
337
-
338
- export default ClickhouseConnector;