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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (250) 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/types/AdminForthConfig.d.ts +1619 -0
  106. package/dist/types/AdminForthConfig.d.ts.map +1 -0
  107. package/dist/types/AdminForthConfig.js +1 -0
  108. package/dist/types/AdminForthConfig.js.map +1 -0
  109. package/{types/FrontendAPI.ts → dist/types/FrontendAPI.d.ts} +27 -52
  110. package/dist/types/FrontendAPI.d.ts.map +1 -0
  111. package/dist/types/FrontendAPI.js +1 -0
  112. package/dist/types/FrontendAPI.js.map +1 -0
  113. package/package.json +15 -5
  114. package/auth.ts +0 -140
  115. package/basePlugin.ts +0 -70
  116. package/dataConnectors/baseConnector.ts +0 -216
  117. package/dataConnectors/clickhouse.ts +0 -338
  118. package/dataConnectors/mongo.ts +0 -202
  119. package/dataConnectors/postgres.ts +0 -306
  120. package/dataConnectors/sqlite.ts +0 -254
  121. package/dist/spa/.eslintrc.cjs +0 -14
  122. package/dist/spa/README.md +0 -39
  123. package/dist/spa/env.d.ts +0 -1
  124. package/dist/spa/index.html +0 -23
  125. package/dist/spa/package-lock.json +0 -4573
  126. package/dist/spa/package.json +0 -49
  127. package/dist/spa/postcss.config.js +0 -6
  128. package/dist/spa/public/assets/favicon.png +0 -0
  129. package/dist/spa/src/App.vue +0 -314
  130. package/dist/spa/src/assets/base.css +0 -2
  131. package/dist/spa/src/assets/logo.svg +0 -19
  132. package/dist/spa/src/components/AcceptModal.vue +0 -45
  133. package/dist/spa/src/components/Breadcrumbs.vue +0 -41
  134. package/dist/spa/src/components/BreadcrumbsWithButtons.vue +0 -26
  135. package/dist/spa/src/components/CustomDatePicker.vue +0 -176
  136. package/dist/spa/src/components/CustomDateRangePicker.vue +0 -218
  137. package/dist/spa/src/components/CustomRangePicker.vue +0 -152
  138. package/dist/spa/src/components/Dropdown.vue +0 -168
  139. package/dist/spa/src/components/Filters.vue +0 -222
  140. package/dist/spa/src/components/HelloWorld.vue +0 -17
  141. package/dist/spa/src/components/MenuLink.vue +0 -24
  142. package/dist/spa/src/components/ResourceForm.vue +0 -294
  143. package/dist/spa/src/components/ResourceListTable.vue +0 -394
  144. package/dist/spa/src/components/SingleSkeletLoader.vue +0 -13
  145. package/dist/spa/src/components/SkeleteLoader.vue +0 -23
  146. package/dist/spa/src/components/Toast.vue +0 -77
  147. package/dist/spa/src/components/ValueRenderer.vue +0 -66
  148. package/dist/spa/src/components/icons/IconCalendar.vue +0 -5
  149. package/dist/spa/src/components/icons/IconCommunity.vue +0 -7
  150. package/dist/spa/src/components/icons/IconDocumentation.vue +0 -7
  151. package/dist/spa/src/components/icons/IconEcosystem.vue +0 -7
  152. package/dist/spa/src/components/icons/IconSupport.vue +0 -7
  153. package/dist/spa/src/components/icons/IconTime.vue +0 -5
  154. package/dist/spa/src/components/icons/IconTooling.vue +0 -19
  155. package/dist/spa/src/composables/useFrontendApi.ts +0 -26
  156. package/dist/spa/src/composables/useStores.ts +0 -127
  157. package/dist/spa/src/index.scss +0 -27
  158. package/dist/spa/src/main.ts +0 -18
  159. package/dist/spa/src/router/index.ts +0 -63
  160. package/dist/spa/src/spa_types/core.ts +0 -51
  161. package/dist/spa/src/stores/core.ts +0 -144
  162. package/dist/spa/src/stores/filters.ts +0 -22
  163. package/dist/spa/src/stores/modal.ts +0 -48
  164. package/dist/spa/src/stores/toast.ts +0 -15
  165. package/dist/spa/src/stores/user.ts +0 -72
  166. package/dist/spa/src/types/AdminForthConfig.ts +0 -1477
  167. package/dist/spa/src/types/FrontendAPI.ts +0 -121
  168. package/dist/spa/src/utils.ts +0 -103
  169. package/dist/spa/src/views/CreateView.vue +0 -156
  170. package/dist/spa/src/views/EditView.vue +0 -157
  171. package/dist/spa/src/views/ListView.vue +0 -258
  172. package/dist/spa/src/views/LoginView.vue +0 -160
  173. package/dist/spa/src/views/ResourceParent.vue +0 -17
  174. package/dist/spa/src/views/ShowView.vue +0 -184
  175. package/dist/spa/tailwind.config.js +0 -17
  176. package/dist/spa/tsconfig.app.json +0 -14
  177. package/dist/spa/tsconfig.json +0 -11
  178. package/dist/spa/tsconfig.node.json +0 -19
  179. package/dist/spa/vite.config.ts +0 -56
  180. package/index.ts +0 -428
  181. package/modules/codeInjector.ts +0 -736
  182. package/modules/configValidator.ts +0 -571
  183. package/modules/operationalResource.ts +0 -98
  184. package/modules/restApi.ts +0 -718
  185. package/modules/styleGenerator.ts +0 -55
  186. package/modules/styles.ts +0 -126
  187. package/modules/utils.ts +0 -472
  188. package/servers/express.ts +0 -259
  189. package/spa/.eslintrc.cjs +0 -14
  190. package/spa/README.md +0 -39
  191. package/spa/env.d.ts +0 -1
  192. package/spa/index.html +0 -23
  193. package/spa/package-lock.json +0 -4602
  194. package/spa/package.json +0 -51
  195. package/spa/postcss.config.js +0 -6
  196. package/spa/public/assets/favicon.png +0 -0
  197. package/spa/src/App.vue +0 -418
  198. package/spa/src/assets/base.css +0 -2
  199. package/spa/src/assets/logo.svg +0 -19
  200. package/spa/src/components/AcceptModal.vue +0 -45
  201. package/spa/src/components/Breadcrumbs.vue +0 -41
  202. package/spa/src/components/BreadcrumbsWithButtons.vue +0 -26
  203. package/spa/src/components/CustomDatePicker.vue +0 -176
  204. package/spa/src/components/CustomDateRangePicker.vue +0 -218
  205. package/spa/src/components/CustomRangePicker.vue +0 -156
  206. package/spa/src/components/Dropdown.vue +0 -168
  207. package/spa/src/components/Filters.vue +0 -222
  208. package/spa/src/components/HelloWorld.vue +0 -17
  209. package/spa/src/components/MenuLink.vue +0 -27
  210. package/spa/src/components/ResourceForm.vue +0 -290
  211. package/spa/src/components/ResourceListTable.vue +0 -466
  212. package/spa/src/components/SingleSkeletLoader.vue +0 -13
  213. package/spa/src/components/SkeleteLoader.vue +0 -23
  214. package/spa/src/components/ThreeDotsMenu.vue +0 -43
  215. package/spa/src/components/Toast.vue +0 -78
  216. package/spa/src/components/ValueRenderer.vue +0 -114
  217. package/spa/src/components/icons/IconCalendar.vue +0 -5
  218. package/spa/src/components/icons/IconCommunity.vue +0 -7
  219. package/spa/src/components/icons/IconDocumentation.vue +0 -7
  220. package/spa/src/components/icons/IconEcosystem.vue +0 -7
  221. package/spa/src/components/icons/IconSupport.vue +0 -7
  222. package/spa/src/components/icons/IconTime.vue +0 -5
  223. package/spa/src/components/icons/IconTooling.vue +0 -19
  224. package/spa/src/composables/useFrontendApi.ts +0 -26
  225. package/spa/src/composables/useStores.ts +0 -131
  226. package/spa/src/index.scss +0 -31
  227. package/spa/src/main.ts +0 -18
  228. package/spa/src/renderers/CompactUUID.vue +0 -48
  229. package/spa/src/renderers/CountryFlag.vue +0 -69
  230. package/spa/src/router/index.ts +0 -59
  231. package/spa/src/spa_types/core.ts +0 -53
  232. package/spa/src/stores/core.ts +0 -148
  233. package/spa/src/stores/filters.ts +0 -27
  234. package/spa/src/stores/modal.ts +0 -48
  235. package/spa/src/stores/toast.ts +0 -31
  236. package/spa/src/stores/user.ts +0 -72
  237. package/spa/src/utils.ts +0 -160
  238. package/spa/src/views/CreateView.vue +0 -167
  239. package/spa/src/views/EditView.vue +0 -170
  240. package/spa/src/views/ListView.vue +0 -352
  241. package/spa/src/views/LoginView.vue +0 -192
  242. package/spa/src/views/ResourceParent.vue +0 -17
  243. package/spa/src/views/ShowView.vue +0 -186
  244. package/spa/tailwind.config.js +0 -17
  245. package/spa/tsconfig.app.json +0 -14
  246. package/spa/tsconfig.json +0 -11
  247. package/spa/tsconfig.node.json +0 -19
  248. package/spa/vite.config.ts +0 -56
  249. package/tsconfig.json +0 -112
  250. package/types/AdminForthConfig.ts +0 -1762
@@ -1,19 +0,0 @@
1
- {
2
- "extends": "@tsconfig/node20/tsconfig.json",
3
- "include": [
4
- "vite.config.*",
5
- "vitest.config.*",
6
- "cypress.config.*",
7
- "nightwatch.conf.*",
8
- "playwright.config.*"
9
- ],
10
- "compilerOptions": {
11
- "composite": true,
12
- "noEmit": true,
13
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
14
-
15
- "module": "ESNext",
16
- "moduleResolution": "Bundler",
17
- "types": ["node"]
18
- }
19
- }
@@ -1,56 +0,0 @@
1
- import { fileURLToPath, URL } from 'node:url'
2
-
3
- import { defineConfig } from 'vite'
4
- import vue from '@vitejs/plugin-vue'
5
- import { clear, error } from 'node:console';
6
-
7
-
8
- const customLogger = {
9
- info(msg: string) {
10
- // Filter out the lines containing '➜ Local:' or '➜ Network:'
11
- if (!msg.includes('➜')) {
12
- console.log(msg);
13
- }
14
- },
15
- warnOnce(msg: string) {
16
- console.warn('warn once', msg);
17
- if (!this.hasWarned) {
18
- this.hasWarned = true;
19
- }
20
- },
21
- warn(msg: string) {
22
- console.warn(msg);
23
- },
24
- error(msg: string) {
25
- console.error(msg);
26
- },
27
- clear() {
28
- console.clear();
29
- },
30
- clearScreen() {
31
- console.clear();
32
- },
33
- hasWarned: false,
34
- hasErrorLogged: (error: any): boolean => {
35
- return false;
36
- }
37
- };
38
-
39
- // https://vitejs.dev/config/
40
- export default defineConfig({
41
- base: process.env.VITE_ADMINFORTH_PUBLIC_PATH || '/',
42
- server: {
43
- port: 5173,
44
- strictPort: true, // better predictability
45
- },
46
- customLogger,
47
- plugins: [
48
- vue(),
49
- ],
50
- resolve: {
51
- alias: {
52
- '@': fileURLToPath(new URL('./src', import.meta.url)),
53
- '@@': fileURLToPath(new URL('./src/custom', import.meta.url)),
54
- }
55
- }
56
- })
package/index.ts DELETED
@@ -1,428 +0,0 @@
1
-
2
- import AdminForthAuth from './auth.js';
3
- import MongoConnector from './dataConnectors/mongo.js';
4
- import PostgresConnector from './dataConnectors/postgres.js';
5
- import SQLiteConnector from './dataConnectors/sqlite.js';
6
- import CodeInjector from './modules/codeInjector.js';
7
- import ExpressServer from './servers/express.js';
8
- import { ADMINFORTH_VERSION, listify, suggestIfTypo, RateLimiter, getClinetIp } from './modules/utils.js';
9
- import {
10
- type AdminForthConfig,
11
- type IAdminForth,
12
- type IConfigValidator,
13
- IOperationalResource,
14
- AdminForthFilterOperators,
15
- AdminForthDataTypes, IHttpServer,
16
- BeforeSaveFunction,
17
- AfterSaveFunction,
18
- AdminUser,
19
- AdminForthResource,
20
- IAdminForthDataSourceConnectorBase,
21
- } from './types/AdminForthConfig.js';
22
- import AdminForthPlugin from './basePlugin.js';
23
- import ConfigValidator from './modules/configValidator.js';
24
- import AdminForthRestAPI, { interpretResource } from './modules/restApi.js';
25
- import ClickhouseConnector from './dataConnectors/clickhouse.js';
26
- import OperationalResource from './modules/operationalResource.js';
27
-
28
- // exports
29
- export * from './types/AdminForthConfig.js';
30
- export { interpretResource };
31
- export { AdminForthPlugin };
32
- export { suggestIfTypo, RateLimiter, getClinetIp };
33
-
34
-
35
- class AdminForth implements IAdminForth {
36
- static Types = AdminForthDataTypes;
37
-
38
- static Utils = {
39
- generatePasswordHash: async (password) => {
40
- return await AdminForthAuth.generatePasswordHash(password);
41
- },
42
-
43
- PASSWORD_VALIDATORS: {
44
- UP_LOW_NUM_SPECIAL: {
45
- regExp: '^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.*[!@#\\$%\\^&\\*\\(\\)\\-_=\\+\\[\\]\\{\\}\\|;:\',\\.<>\\/\\?]).+$',
46
- message: 'Password must include at least one uppercase letter, one lowercase letter, one number, and one special character'
47
- },
48
- UP_LOW_NUM: {
49
- regExp: '^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9]).+$',
50
- message: 'Password must include at least one uppercase letter, one lowercase letter, and one number'
51
- },
52
- },
53
- EMAIL_VALIDATOR: {
54
- regExp: '^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}$',
55
- message: 'Email is not valid, must be in format example@test.com',
56
- }
57
- }
58
-
59
- #defaultConfig = {
60
- deleteConfirmation: true,
61
- }
62
-
63
- config: AdminForthConfig;
64
- express: ExpressServer;
65
- auth: AdminForthAuth;
66
- codeInjector: CodeInjector;
67
- connectors: {
68
- [dataSourceId: string]: IAdminForthDataSourceConnectorBase,
69
- };
70
- connectorClasses: any;
71
- runningHotReload: boolean;
72
- activatedPlugins: Array<AdminForthPlugin>;
73
- configValidator: IConfigValidator;
74
- restApi: AdminForthRestAPI;
75
- operationalResources: {
76
- [resourceId: string]: IOperationalResource,
77
- }
78
- baseUrlSlashed: string;
79
-
80
- statuses: {
81
- dbDiscover: 'running' | 'done',
82
- }
83
-
84
- constructor(config: AdminForthConfig) {
85
- this.config = {...this.#defaultConfig,...config};
86
- this.codeInjector = new CodeInjector(this);
87
- this.configValidator = new ConfigValidator(this, this.config);
88
- this.restApi = new AdminForthRestAPI(this);
89
- this.activatedPlugins = [];
90
-
91
- this.configValidator.validateConfig();
92
- this.activatePlugins();
93
- this.configValidator.validateConfig(); // revalidate after plugins
94
-
95
- this.express = new ExpressServer(this);
96
- this.auth = new AdminForthAuth(this);
97
- this.connectors = {};
98
- this.statuses = {
99
- dbDiscover: 'running',
100
- };
101
-
102
- console.log(`🚀 AdminForth v${ADMINFORTH_VERSION} starting up`)
103
- }
104
-
105
- activatePlugins() {
106
- process.env.HEAVY_DEBUG && console.log('🔌🔌🔌 Activating plugins');
107
- const allPluginInstances = [];
108
- for (let resource of this.config.resources) {
109
- for (let pluginInstance of resource.plugins || []) {
110
- allPluginInstances.push({pi: pluginInstance, resource});
111
- }
112
- }
113
- allPluginInstances.sort(({pi: a}, {pi: b}) => a.activationOrder - b.activationOrder);
114
- allPluginInstances.forEach(
115
- ({pi: pluginInstance, resource}) => {
116
- pluginInstance.modifyResourceConfig(this, resource);
117
- const plugin = this.activatedPlugins.find((p) => p.pluginInstanceId === pluginInstance.pluginInstanceId);
118
- if (plugin) {
119
- process.env.HEAVY_DEBUG && console.log(`Current plugin pluginInstance.pluginInstanceId ${pluginInstance.pluginInstanceId}`);
120
-
121
- throw new Error(`Attempt to activate Plugin ${pluginInstance.constructor.name} second time for same resource, but plugin does not support it.
122
- To support multiple plugin instance pre one resource, plugin should return unique string values for each installation from instanceUniqueRepresentation`);
123
- }
124
- this.activatedPlugins.push(pluginInstance);
125
- }
126
- );
127
- }
128
-
129
- getPluginsByClassName<T>(className: string): T[] {
130
- const plugins = this.activatedPlugins.filter((plugin) => plugin.className === className);
131
- return plugins as T[];
132
- }
133
-
134
- getPluginByClassName<T>(className: string): T {
135
- const plugins = this.getPluginsByClassName(className);
136
- if (plugins.length > 1) {
137
- throw new Error(`Multiple plugins with className ${className} found. Use getPluginsByClassName instead`);
138
- }
139
- if (plugins.length === 0) {
140
- const similar = suggestIfTypo(this.activatedPlugins.map((p) => p.className), className);
141
- throw new Error(`Plugin with className ${className} not found. ${similar ? `Did you mean ${similar}?` : ''}`);
142
- }
143
- return plugins[0] as T;
144
- }
145
-
146
- async discoverDatabases() {
147
- this.statuses.dbDiscover = 'running';
148
- this.connectorClasses = {
149
- 'sqlite': SQLiteConnector,
150
- 'postgres': PostgresConnector,
151
- 'mongodb': MongoConnector,
152
- 'clickhouse': ClickhouseConnector,
153
- };
154
- if (!this.config.databaseConnectors) {
155
- this.config.databaseConnectors = {...this.connectorClasses};
156
- }
157
- this.config.dataSources.forEach((ds) => {
158
- const dbType = ds.url.split(':')[0];
159
- if (!this.config.databaseConnectors[dbType]) {
160
- throw new Error(`Database type '${dbType}' is not supported, consider using one of ${Object.keys(this.connectorClasses).join(', ')} or create your own data-source connector`);
161
- }
162
- this.connectors[ds.id] = new this.config.databaseConnectors[dbType]({url: ds.url});
163
- });
164
-
165
- await Promise.all(this.config.resources.map(async (res) => {
166
- if (!this.connectors[res.dataSource]) {
167
- const similar = suggestIfTypo(Object.keys(this.connectors), res.dataSource);
168
- throw new Error(`Resource '${res.table}' refers to unknown dataSource '${res.dataSource}' ${similar
169
- ? `. Did you mean '${similar}'?` : 'Available dataSources: '+Object.keys(this.connectors).join(', ')}`
170
- );
171
- }
172
- const fieldTypes = await this.connectors[res.dataSource].discoverFields(res);
173
- if (fieldTypes !== null && !Object.keys(fieldTypes).length) {
174
- throw new Error(`Table '${res.table}' (In resource '${res.resourceId}') has no fields or does not exist`);
175
- }
176
- if (fieldTypes === null) {
177
- console.error(`⛔ DataSource ${res.dataSource} was not able to perform field discovery. It will not work properly`);
178
- return;
179
- }
180
- if (!res.columns) {
181
- res.columns = Object.keys(fieldTypes).map((name) => ({ name }));
182
- }
183
-
184
- res.columns.forEach((col, i) => {
185
- if (!fieldTypes[col.name] && !col.virtual) {
186
- const similar = suggestIfTypo(Object.keys(fieldTypes), col.name);
187
- throw new Error(`Resource '${res.table}' has no column '${col.name}'. ${similar ? `Did you mean '${similar}'?` : ''}`);
188
- }
189
- // first find discovered values, but allow override
190
- res.columns[i] = { ...fieldTypes[col.name], ...col };
191
- });
192
-
193
- this.configValidator.postProcessAfterDiscover(res);
194
-
195
- // check if primaryKey column is present
196
- if (!res.columns.some((col) => col.primaryKey)) {
197
- throw new Error(`Resource '${res.table}' has no column defined or auto-discovered. Please set 'primaryKey: true' in a columns which has unique value for each record and index`);
198
- }
199
-
200
- }));
201
-
202
- this.statuses.dbDiscover = 'done';
203
-
204
- this.operationalResources = {};
205
- this.config.resources.forEach((resource) => {
206
- this.operationalResources[resource.resourceId] = new OperationalResource(this.connectors[resource.dataSource], resource);
207
- });
208
-
209
- // console.log('⚙️⚙️⚙️ Database discovery done', JSON.stringify(this.config.resources, null, 2));
210
- }
211
-
212
- async bundleNow({ hotReload=false }) {
213
- await this.codeInjector.bundleNow({ hotReload });
214
- }
215
-
216
- async getUserByPk(pk: string) {
217
- // if database discovery is not done, throw
218
- if (this.statuses.dbDiscover !== 'done') {
219
- if (this.statuses.dbDiscover === 'running') {
220
- throw new Error('Database discovery is running. You can\'t use data API while database discovery is not finished.\n'+
221
- 'Consider moving your code to a place where it will be executed after database discovery is already done (after await admin.discoverDatabases())');
222
- }
223
- throw new Error('Database discovery is not yet started. You can\'t use data API before database discovery is done. \n'+
224
- 'Call admin.discoverDatabases() first and await it before using data API');
225
- }
226
-
227
- const resource = this.config.resources.find((res) => res.resourceId === this.config.auth.usersResourceId);
228
- if (!resource) {
229
- const similar = suggestIfTypo(this.config.resources.map((res) => res.resourceId), this.config.auth.usersResourceId);
230
- throw new Error(`No resource with ${this.config.auth.usersResourceId} found. ${similar ?
231
- `Did you mean '${similar}' in config.auth.usersResourceId?` : 'Please set correct resource in config.auth.usersResourceId'}`
232
- );
233
- }
234
- const users = await this.connectors[resource.dataSource].getData({
235
- resource,
236
- filters: [
237
- { field: resource.columns.find((col) => col.primaryKey).name, operator: AdminForthFilterOperators.EQ, value: pk },
238
- ],
239
- limit: 1,
240
- offset: 0,
241
- sort: [],
242
- });
243
- return users.data[0] || null;
244
- }
245
-
246
- async createResourceRecord(
247
- { resource, record, adminUser }:
248
- { resource: AdminForthResource, record: any, adminUser: AdminUser }
249
- ): Promise<{ error?: string, createdRecord?: any }> {
250
-
251
- // execute hook if needed
252
- for (const hook of listify(resource.hooks?.create?.beforeSave as BeforeSaveFunction[])) {
253
- const resp = await hook({ recordId: undefined, resource, record, adminUser });
254
- if (!resp || (!resp.ok && !resp.error)) {
255
- throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
256
- }
257
-
258
- if (resp.error) {
259
- return { error: resp.error };
260
- }
261
- }
262
-
263
- // remove virtual columns from record
264
- for (const column of resource.columns.filter((col) => col.virtual)) {
265
- if (record[column.name]) {
266
- delete record[column.name];
267
- }
268
- }
269
- const connector = this.connectors[resource.dataSource];
270
- process.env.HEAVY_DEBUG && console.log('🪲🆕 creating record createResourceRecord', record);
271
- const { error, createdRecord } = await connector.createRecord({ resource, record, adminUser });
272
- if ( error ) {
273
- return { error };
274
- }
275
-
276
- const primaryKey = record[resource.columns.find((col) => col.primaryKey).name];
277
-
278
- // execute hook if needed
279
- for (const hook of listify(resource.hooks?.create?.afterSave as AfterSaveFunction[])) {
280
- process.env.HEAVY_DEBUG && console.log('🪲 Hook afterSave', hook);
281
- const resp = await hook({
282
- recordId: primaryKey,
283
- resource,
284
- record: createdRecord,
285
- adminUser
286
- });
287
-
288
- if (!resp || (!resp.ok && !resp.error)) {
289
- throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
290
- }
291
-
292
- if (resp.error) {
293
- return { error: resp.error };
294
- }
295
- }
296
-
297
- return { error, createdRecord };
298
- }
299
-
300
- /**
301
- * record is partial record with only changed fields
302
- */
303
- async updateResourceRecord(
304
- { resource, recordId, record, oldRecord, adminUser }:
305
- { resource: AdminForthResource, recordId: any, record: any, oldRecord: any, adminUser: AdminUser }
306
- ): Promise<{ error?: string }> {
307
-
308
- // execute hook if needed
309
- for (const hook of listify(resource.hooks?.edit?.beforeSave as BeforeSaveFunction[])) {
310
- const resp = await hook({
311
- recordId,
312
- resource,
313
- record,
314
- oldRecord,
315
- adminUser
316
- });
317
- if (!resp || (!resp.ok && !resp.error)) {
318
- throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
319
- }
320
- if (resp.error) {
321
- return { error: resp.error };
322
- }
323
- }
324
- const newValues = {};
325
- const connector = this.connectors[resource.dataSource];
326
-
327
- for (const recordField in record) {
328
- if (record[recordField] !== oldRecord[recordField]) {
329
- // leave only changed fields to reduce data transfer/modifications in db
330
- const column = resource.columns.find((col) => col.name === recordField);
331
- if (!column || !column.virtual) {
332
- // exclude virtual columns
333
- newValues[recordField] = record[recordField];
334
- }
335
- }
336
- }
337
-
338
- if (Object.keys(newValues).length > 0) {
339
- await connector.updateRecord({ resource, recordId, newValues });
340
- }
341
-
342
- // execute hook if needed
343
- for (const hook of listify(resource.hooks?.edit?.afterSave as AfterSaveFunction[])) {
344
- const resp = await hook({
345
- resource,
346
- record,
347
- adminUser,
348
- oldRecord,
349
- recordId,
350
- });
351
- if (!resp || (!resp.ok && !resp.error)) {
352
- throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
353
- }
354
- if (resp.error) {
355
- return { error: resp.error };
356
- }
357
- }
358
-
359
- return { error: null };
360
- }
361
-
362
- async deleteResourceRecord(
363
- { resource, recordId, adminUser, record }:
364
- { resource: AdminForthResource, recordId: any, adminUser: AdminUser, record: any }
365
- ): Promise<{ error?: string }> {
366
- // execute hook if needed
367
- for (const hook of listify(resource.hooks?.delete?.beforeSave as BeforeSaveFunction[])) {
368
- const resp = await hook({
369
- resource,
370
- record,
371
- adminUser,
372
- recordId,
373
- });
374
- if (!resp || (!resp.ok && !resp.error)) {
375
- throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
376
- }
377
-
378
- if (resp.error) {
379
- return { error: resp.error };
380
- }
381
- }
382
-
383
- const connector = this.connectors[resource.dataSource];
384
- await connector.deleteRecord({ resource, recordId});
385
-
386
- // execute hook if needed
387
- for (const hook of listify(resource.hooks?.delete?.afterSave as BeforeSaveFunction[])) {
388
- const resp = await hook({
389
- resource,
390
- record,
391
- adminUser,
392
- recordId,
393
- });
394
- if (!resp || (!resp.ok && !resp.error)) {
395
- throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
396
- }
397
-
398
- if (resp.error) {
399
- return { error: resp.error };
400
- }
401
- }
402
-
403
- return { error: null };
404
- }
405
-
406
- resource(resourceId: string): IOperationalResource {
407
- if (this.statuses.dbDiscover !== 'done') {
408
- if (this.statuses.dbDiscover === 'running') {
409
- throw new Error('Database discovery is running. You can\'t use data API while database discovery is not finished.\n'+
410
- 'Consider moving your code to a place where it will be executed after database discovery is already done (after await admin.discoverDatabases())');
411
- } else {
412
- throw new Error('Database discovery is not yet started. You can\'t use data API before database discovery is done. \n'+
413
- 'Call admin.discoverDatabases() first and await it before using data API');
414
- }
415
- }
416
- if (!this.operationalResources[resourceId]) {
417
- const closeName = suggestIfTypo(Object.keys(this.operationalResources), resourceId);
418
- throw new Error(`Resource with id '${resourceId}' not found${closeName ? `. Did you mean '${closeName}'?` : ''}`);
419
- }
420
- return this.operationalResources[resourceId];
421
- }
422
-
423
- setupEndpoints(server: IHttpServer) {
424
- this.restApi.registerEndpoints(server);
425
- }
426
- }
427
-
428
- export default AdminForth;