adminforth 1.0.80 → 1.0.82

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 (38) hide show
  1. package/dist/index.js +13 -0
  2. package/dist/modules/codeInjector.js +8 -6
  3. package/dist/spa/spa/src/App.vue +1 -1
  4. package/dist/spa/spa/src/components/ResourceForm.vue +5 -4
  5. package/dist/spa/spa/src/stores/core.ts +1 -0
  6. package/dist/spa/spa/src/utils.ts +12 -0
  7. package/dist/spa/spa/src/views/CreateView.vue +5 -1
  8. package/dist/spa/spa/src/views/EditView.vue +3 -1
  9. package/dist/spa/spa/src/views/ListView.vue +2 -1
  10. package/dist/spa/spa/src/views/ShowView.vue +47 -3
  11. package/documentation/docusaurus.config.ts +34 -26
  12. package/documentation/package.json +1 -1
  13. package/documentation/src/components/HomepageFeatures/index.tsx +13 -15
  14. package/documentation/src/pages/index.module.css +1 -0
  15. package/documentation/src/pages/index.tsx +2 -2
  16. package/documentation/static/img/css.png +0 -0
  17. package/documentation/static/img/db.png +0 -0
  18. package/documentation/static/img/db2.png +0 -0
  19. package/documentation/static/img/gen.sh +24 -0
  20. package/documentation/static/img/tail.png +0 -0
  21. package/documentation/static/img/tail.png:Zone.Identifier +0 -0
  22. package/documentation/static/img/vue.png +0 -0
  23. package/index.ts +13 -0
  24. package/modules/codeInjector.ts +8 -6
  25. package/package.json +2 -2
  26. package/spa/src/App.vue +1 -1
  27. package/spa/src/components/ResourceForm.vue +5 -4
  28. package/spa/src/stores/core.ts +1 -0
  29. package/spa/src/utils.ts +12 -0
  30. package/spa/src/views/CreateView.vue +5 -1
  31. package/spa/src/views/EditView.vue +3 -1
  32. package/spa/src/views/ListView.vue +2 -1
  33. package/spa/src/views/ShowView.vue +47 -3
  34. package/styles.js +52 -0
  35. package/types/AdminForthConfig.ts +240 -9
  36. package/documentation/static/img/undraw_docusaurus_mountain.svg +0 -171
  37. package/documentation/static/img/undraw_docusaurus_react.svg +0 -170
  38. package/documentation/static/img/undraw_docusaurus_tree.svg +0 -40
@@ -121,9 +121,6 @@
121
121
  <IconEyeSolid class="w-6 h-6 text-gray-400" v-if="!unmasked[column.name]" />
122
122
  <IconEyeSlashSolid class="w-6 h-6 text-gray-400" v-else />
123
123
  </button>
124
-
125
-
126
-
127
124
  <div v-if="columnError(column) && validating" class="mt-1 text-xs text-red-500 dark:text-red-400">{{ columnError(column) }}</div>
128
125
 
129
126
  <div v-if="column.editingNote && column.editingNote[mode]" class="mt-1 text-xs text-gray-400 dark:text-gray-500">{{ column.editingNote[mode] }}</div>
@@ -197,7 +194,11 @@ const columnError = (column) => {
197
194
  for (let i = 0; i < validationArray.length; i++) {
198
195
  if (validationArray[i].regExp) {
199
196
  const regExp = new RegExp(validationArray[i].regExp);
200
- if (!regExp.test(currentValues.value[column.name])) {
197
+ let value = currentValues.value[column.name];
198
+ if (value === undefined || value === null) {
199
+ value = '';
200
+ }
201
+ if (!regExp.test(value)) {
201
202
  return validationArray[i].message;
202
203
  }
203
204
  }
@@ -85,6 +85,7 @@ export const useCoreStore = defineStore('core', () => {
85
85
  } else {
86
86
  resourceColumns.value = res.resource.columns;
87
87
  resourceById.value[resourceId] = res.resource;
88
+ resourceOptions.value = res.resource.options;
88
89
  }
89
90
  }
90
91
 
package/spa/src/utils.ts CHANGED
@@ -2,6 +2,7 @@ import { onMounted, ref, resolveComponent } from 'vue';
2
2
  import type { CoreConfig } from './spa_types/core';
3
3
 
4
4
  import router from "./router";
5
+ import { useRouter } from 'vue-router';
5
6
  import { useCoreStore } from './stores/core';
6
7
 
7
8
  export async function callApi({path, method, body=undefined} ) {
@@ -80,3 +81,14 @@ export function checkEmptyValues(value: any, viewType:'show' | 'list' ) {
80
81
  }
81
82
  return value;
82
83
  }
84
+
85
+ export function checkAcessByAllowedActions(allowedActions:any, action:any ) {
86
+ if (!allowedActions) {
87
+ console.warn('allowedActions not set');
88
+ return;
89
+ }
90
+ if(allowedActions[action] === false) {
91
+ console.warn(`Action ${action} is not allowed`);
92
+ router.back();
93
+ }
94
+ }
@@ -23,6 +23,7 @@
23
23
  </BreadcrumbsWithButtons>
24
24
 
25
25
  <SingleSkeletLoader v-if="loading"></SingleSkeletLoader>
26
+
26
27
 
27
28
  <ResourceForm
28
29
  v-else
@@ -35,6 +36,7 @@
35
36
  >
36
37
  </ResourceForm>
37
38
 
39
+
38
40
  </div>
39
41
  </template>
40
42
 
@@ -45,11 +47,12 @@ import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
45
47
  import ResourceForm from '@/components/ResourceForm.vue';
46
48
  import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
47
49
  import { useCoreStore } from '@/stores/core';
48
- import { callAdminForthApi, getCustomComponent } from '@/utils';
50
+ import { callAdminForthApi, getCustomComponent,checkAcessByAllowedActions } from '@/utils';
49
51
  import { IconFloppyDiskSolid } from '@iconify-prerendered/vue-flowbite';
50
52
  import { onMounted, ref } from 'vue';
51
53
  import { useRoute, useRouter } from 'vue-router';
52
54
 
55
+
53
56
  const isValid = ref(false);
54
57
  const validating = ref(false);
55
58
 
@@ -81,6 +84,7 @@ onMounted(async () => {
81
84
  return acc;
82
85
  }, {});
83
86
  loading.value = false;
87
+ checkAcessByAllowedActions(coreStore.resourceOptions.allowedActions,'create');
84
88
  });
85
89
 
86
90
  async function saveRecord() {
@@ -41,7 +41,7 @@ import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
41
41
  import ResourceForm from '@/components/ResourceForm.vue';
42
42
  import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
43
43
  import { useCoreStore } from '@/stores/core';
44
- import { callAdminForthApi, getCustomComponent } from '@/utils';
44
+ import { callAdminForthApi, getCustomComponent,checkAcessByAllowedActions } from '@/utils';
45
45
  import { IconFloppyDiskSolid } from '@iconify-prerendered/vue-flowbite';
46
46
  import { computed, onMounted, ref } from 'vue';
47
47
  import { useRoute, useRouter } from 'vue-router';
@@ -79,6 +79,7 @@ const editableRecord = computed(() => {
79
79
  })
80
80
 
81
81
  onMounted(async () => {
82
+
82
83
  loading.value = true;
83
84
 
84
85
  await coreStore.fetchResourceFull({
@@ -88,6 +89,7 @@ onMounted(async () => {
88
89
  resourceId: route.params.resourceId,
89
90
  primaryKey: route.params.primaryKey,
90
91
  });
92
+ checkAcessByAllowedActions(coreStore.resourceOptions.allowedActions,'edit');
91
93
 
92
94
  editComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
93
95
  if (column.component?.edit) {
@@ -65,7 +65,7 @@
65
65
  </BreadcrumbsWithButtons>
66
66
 
67
67
  <!-- table -->
68
- <div class="relative overflow-x-auto shadow-list-table-shadow dark:shadow-black rounded-default">
68
+ <div class="relative overflow-x-auto shadow-list-table-shadow dark:shadow-black overflow-y-hidden rounded-default">
69
69
 
70
70
 
71
71
  <!-- skelet loader -->
@@ -208,6 +208,7 @@
208
208
  <td class=" items-center px-6 py-4">
209
209
  <div class="flex">
210
210
  <RouterLink
211
+ v-if="allowedActions.show"
211
212
  :to="{ name: 'resource-show', params: { resourceId: $route.params.resourceId, primaryKey: row._primaryKeyValue } }"
212
213
  class="font-medium text-blue-600 dark:text-blue-500 hover:underline"
213
214
  :data-tooltip-target="`tooltip-show-${rowI}`"
@@ -8,7 +8,7 @@
8
8
  Edit
9
9
  </RouterLink>
10
10
 
11
- <button v-if="coreStore?.resourceOptions?.allowedActions?.delete" @click="deleteRecord"
11
+ <button v-if="coreStore?.resourceOptions?.allowedActions?.delete" @click="showDeleteModal"
12
12
  class="flex items-center py-1 px-3 mb-2 text-sm font-medium rounded-default text-red-600 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-red-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-red-500 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
13
13
  >
14
14
  <IconTrashBinSolid class="w-4 h-4" />
@@ -83,20 +83,26 @@ import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
83
83
 
84
84
  import ValueRenderer from '@/components/ValueRenderer.vue';
85
85
  import { useCoreStore } from '@/stores/core';
86
- import { getCustomComponent } from '@/utils';
86
+ import { useModalStore } from '@/stores/modal';
87
+ import { getCustomComponent,checkAcessByAllowedActions } from '@/utils';
87
88
  import { IconPenSolid, IconTrashBinSolid } from '@iconify-prerendered/vue-flowbite';
88
89
  import { onMounted, ref } from 'vue';
89
- import { useRoute } from 'vue-router';
90
+ import { useRoute,useRouter } from 'vue-router';
91
+ import {callAdminForthApi} from '@/utils';
90
92
 
91
93
 
92
94
  const item = ref(null);
93
95
  const route = useRoute();
96
+ const router = useRouter();
94
97
  const loading = ref(false);
95
98
  let showComponentsPerColumn = {};
96
99
  let showRowComponentsPerColumn = {};
97
100
 
101
+ console.log(route.params,'showWiev');
102
+
98
103
 
99
104
  const coreStore = useCoreStore();
105
+ const modalStore = useModalStore();
100
106
 
101
107
  onMounted(async () => {
102
108
  loading.value = true;
@@ -107,6 +113,8 @@ onMounted(async () => {
107
113
  resourceId: route.params.resourceId,
108
114
  primaryKey: route.params.primaryKey,
109
115
  });
116
+ checkAcessByAllowedActions(coreStore.resourceOptions.allowedActions,'show');
117
+
110
118
  showComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
111
119
  if (column.component?.show) {
112
120
  acc[column.name] = getCustomComponent(column.component.show);
@@ -123,4 +131,40 @@ onMounted(async () => {
123
131
  loading.value = false;
124
132
  });
125
133
 
134
+ function showDeleteModal(row) {
135
+ if (!coreStore.config?.deleteConfirmation) {
136
+ return deleteRecord(row);
137
+ }
138
+ modalStore.setModalContent({
139
+ content: 'Are you sure you want to delete this item?',
140
+ acceptText: 'Delete',
141
+ cancelText: 'Cancel',
142
+ });
143
+ modalStore.setOnAcceptFunction(() => {
144
+ return deleteRecord(row)
145
+ })
146
+ modalStore.togleModal();
147
+
148
+
149
+ }
150
+
151
+ async function deleteRecord(row) {
152
+ try{
153
+ await callAdminForthApi({
154
+ path: '/delete_record',
155
+ method: 'POST',
156
+ body: {
157
+ resourceId: route.params.resourceId,
158
+ primaryKey: route.params.primaryKey,
159
+ recordId: route.params.primaryKey
160
+ }
161
+ });
162
+ router.push({ name: 'resource-list', params: { resourceId: route.params.resourceId } });
163
+ } catch (error) {
164
+ console.log(error);
165
+ }
166
+
167
+ modalStore.resetmodalState()
168
+ }
169
+
126
170
  </script>
package/styles.js ADDED
@@ -0,0 +1,52 @@
1
+ export const styles = () => `
2
+
3
+ "colors": {
4
+ "html-bg": "#FFFFFF", // Changed to white
5
+ "header-bg": "#EDEDED", // Light grey
6
+ "header-bg-hover": "#D3D3D3", // Light grey hover
7
+ "header-text": "#333333", // Dark grey text
8
+ "header-text-hover": "#333333", // Dark grey text hover
9
+ "header-icons": "#333333", // Dark grey icons
10
+ "header-icons-hover": "#333333", // Dark grey icons hover
11
+ "header-logo-color": "#333333", // Dark grey logo
12
+ "header-border-color": "#CCCCCC", // Light grey border
13
+
14
+ "nav-menu-bg": "#F5F5F5", // Very light grey
15
+ "nav-menu-bg-hover": "#E0E0E0", // Light grey hover
16
+ "nav-menu-bg-active": "#CCCCCC", // Light grey active
17
+ "nav-menu-hover": "#E0E0E0", // Light grey hover
18
+ "nav-menu-active": "#CCCCCC", // Light grey active
19
+ "nav-menu-active-text": "#333333", // Dark grey active text
20
+ "nav-menu-text": "#333333", // Dark grey text
21
+ "nav-menu-text-hover": "#333333", // Dark grey text hover
22
+ "nav-menu-text-active": "#333333", // Dark grey text active
23
+ "nav-menu-devider": "#E0E0E0", // Light grey divider
24
+ "nav-menu-icons": "#333333", // Dark grey icons
25
+ "nav-menu-heading": "#333333", // Dark grey heading
26
+
27
+ "list-view-bg": "#FFFFFF", // White background
28
+ "list-view-table-bg": "#FFFFFF", // White table background
29
+ "list-view-table-heading": "#F5F5F5", // Very light grey heading
30
+ "list-view-table-heading-text": "#333333", // Dark grey heading text
31
+ "list-view-table-text": "#333333", // Dark grey text
32
+ "list-view-table-row-hover": "#F0F0F0", // Very light grey row hover
33
+ "list-view-bread-crumbs-text": "#666666", // Medium grey bread crumbs
34
+ "list-view-border-color": "#DDDDDD", // Light grey border
35
+
36
+ "form-view-bg": "#FFFFFF", // White form background
37
+ "form-view-border-color": "#F5F5F5", // Very light grey border
38
+ "form-view-heading": "#EDEDED" // Dark grey heading
39
+ },
40
+ "boxShadow": {
41
+ "custom-light": "0 4px 8px rgba(0, 0, 0, 0.1)", // Lighter shadow
42
+ "header-shadow": "0 4px 8px rgba(0, 0, 0, 0.1)", // Lighter shadow
43
+ "list-table-shadow": "0 4px 8px rgba(0, 0, 0, 0.1)", // Lighter shadow
44
+ "resourse-form-shadow": "0 4px 8px rgba(0, 0, 0, 0.1)" // Lighter shadow
45
+ },
46
+ "fontSize": {
47
+ "header-text-size": "1rem"
48
+ },
49
+ "borderRadius": {
50
+ "default": "2px"
51
+ }
52
+ `;
@@ -46,6 +46,7 @@ export type AdminForthResourceColumn = {
46
46
  showIn?: Array<string>,
47
47
  fillOnCreate?: Function,
48
48
  isUnique?: boolean,
49
+ validation?: Array<ValidationObject>,
49
50
  virtual?: boolean,
50
51
  allowMinMaxQuery?: boolean,
51
52
  component?: AdminForthResourceColumnComponent
@@ -62,13 +63,59 @@ export type AdminForthResourceColumn = {
62
63
  }
63
64
 
64
65
  export type AdminForthResource = {
66
+ /**
67
+ * Unique identifier of resource. By default it equals to table name in database.
68
+ * If you wish you can explicitly set it to any string.
69
+ * We added to support cases when 2 datasources have tables with the same name.
70
+ */
65
71
  resourceId: string,
72
+
73
+ /**
74
+ * Label for resource which will be displayed in the admin panel.
75
+ * By default it equals to table name in database.
76
+ */
66
77
  label?: string,
78
+
79
+ /**
80
+ * Table name in database which will be used to fetch data from. Might be case sensitive.
81
+ */
67
82
  table: string,
83
+
84
+ /**
85
+ * ID of datasource which will be used to fetch data from.
86
+ */
68
87
  dataSource: string,
88
+
89
+ /**
90
+ * Array of columns which will be displayed in the admin panel.
91
+ * Each column has its own configuration.
92
+ */
69
93
  columns: Array<AdminForthResourceColumn>,
70
- dataSourceColumns?: Array<AdminForthResourceColumn>,
94
+
95
+ dataSourceColumns?: Array<AdminForthResourceColumn>, // TODO, mark as private
96
+
97
+ /**
98
+ * Hook which allow you to modify item label
99
+ *
100
+ * Example:
101
+ *
102
+ * ```ts
103
+ * itemLabel: (item) => `${item.name} - ${item.id}`,
104
+ * ```
105
+ *
106
+ */
71
107
  itemLabel?: Function,
108
+
109
+ /**
110
+ * Hook which allow you to modify item title
111
+ *
112
+ * Example:
113
+ *
114
+ * ```ts
115
+ * itemTitle: (item) => `${item.name} - ${item.id}`,
116
+ * ```
117
+ *
118
+ */
72
119
  plugins?: Array<AdminForthPluginType>,
73
120
  hooks?: {
74
121
  show?: {
@@ -109,37 +156,193 @@ export type AdminForthDataSource = {
109
156
  id: string,
110
157
  url: string,
111
158
  }
112
-
113
- export type AdminForthConfig = {
159
+
160
+ /**
161
+ * Main configuration object for AdminForth
162
+ */
163
+ export type AdminForthConfig = {
164
+
165
+ /**
166
+ * Root user should be used to login to the admin panel first time.
167
+ * Then you should create User for yourself using AdminForth (so it will be persisted in DB),
168
+ * and then disable this option as it is less secure
169
+ */
114
170
  rootUser?: {
171
+ /**
172
+ * Username for root user
173
+ */
115
174
  username: string,
175
+
176
+ /**
177
+ * Password for root user
178
+ */
116
179
  password: string,
117
180
  },
181
+
182
+ /**
183
+ * Authorization module configuration
184
+ */
118
185
  auth?: {
186
+ /**
187
+ * Resource ID for user resource.
188
+ * Resource is a table in database where users will be stored and fetched from. Resources and their ids are defined in resources section of the config.
189
+ * In other words this setting is a reference to a table in database where users will be fetched from on login.
190
+ */
119
191
  resourceId: string,
192
+
193
+ /**
194
+ * Field name (column name) in user resource which will be used as username for searching user in database during login.
195
+ * Can be e.g. 'email' or 'username'
196
+ */
120
197
  usernameField: string,
198
+
199
+ /**
200
+ * Field name (column name) in user resource which will be used to get hash of password.
201
+ * Can be e.g. 'passwordHash'
202
+ */
121
203
  passwordHashField: string,
204
+
205
+ /**
206
+ * File path to custom background image for login page
207
+ * Example:
208
+ * Place file `login-background.jpg` to `./custom` folder and set this option:
209
+ *
210
+ * ```ts
211
+ * loginBackgroundImage: '@@/login-background.jpg',
212
+ * ```
213
+ */
122
214
  loginBackgroundImage?: string,
215
+
216
+ /**
217
+ * Optionally if your users table has a field(column) with full name, you can set it here.
218
+ * This field will be used to display user name in the top right corner of the admin panel.
219
+ */
123
220
  userFullNameField?: string,
124
221
  },
222
+ /**
223
+ * Array of resources which will be displayed in the admin panel.
224
+ * Resource represents one table or collection in database.
225
+ * Each resource has its own configuration.
226
+ */
125
227
  resources: Array<AdminForthResource>,
228
+
229
+ /**
230
+ * Array of left sidebar menu items which will be displayed in the admin panel.
231
+ * Menu items can be links to resources or custom pages.
232
+ * Menu items can be grouped.
233
+ *
234
+ */
126
235
  menu: Array<AdminForthConfigMenuItem>,
127
- databaseConnectors?: any,
236
+
237
+ /**
238
+ * If you want use custom DataSource which is not supported by AdminForth yet, you can define it's class here
239
+ *
240
+ */
241
+ databaseConnectors?: any, // TODO Define interface for database connector
242
+
243
+ /**
244
+ * List of data sources which will be used to fetch data for resources.
245
+ * Datasource is one database connection
246
+ *
247
+ */
128
248
  dataSources: Array<DataSource>,
249
+
250
+ /**
251
+ * Settings which allow you to customize AdminForth
252
+ *
253
+ */
129
254
  customization?: {
130
- customComponentsDir?: string,
131
- vueUsesFile?: string,
132
- customPublicDir?: string,
255
+ /**
256
+ * Your app name
257
+ */
133
258
  brandName?: string,
259
+
260
+ /**
261
+ * Path to your app logo
262
+ *
263
+ * Example:
264
+ * Place file `logo.svg` to `./custom` folder and set this option:
265
+ *
266
+ * ```ts
267
+ * brandLogo: '@@/logo.svg',
268
+ * ```
269
+ *
270
+ */
271
+ brandLogo?: string,
272
+
273
+ /**
274
+ * DayJS format string for all dates in the app
275
+ */
134
276
  datesFormat?: string,
277
+
278
+ /**
279
+ * HTML title tag value, defaults to brandName
280
+ */
135
281
  title?: string,
136
- brandLogo?: string,
282
+
283
+ /**
284
+ * Placeholder for empty fields in lists and show views, by default empty string ''
285
+ */
137
286
  emptyFieldPlaceholder?: {
138
287
  show?: string,
139
288
  list?: string,
140
289
 
141
290
  } | string,
291
+
292
+ /**
293
+ * Relative or absolute path to custom components directory
294
+ * By default equals `./custom`.
295
+ *
296
+ * Custom .vue files, images, and any other assets placed in this directory can be accessed in AdminForth components and configs with `@@/`.
297
+ *
298
+ * For example if file path is `./custom/comp/my.vue`, you can use it in AdminForth config like this:
299
+ *
300
+ * ```ts
301
+ * component: {
302
+ * show: '@@/comp/my.vue',
303
+ * }
304
+ *
305
+ */
306
+ customComponentsDir?: string,
307
+
308
+ /**
309
+ * Path to custom .ts file which allows to inject custom Vue uses in SPA or add custom imports.
310
+ *
311
+ * Example: Create file: `./custom/vue-uses.ts` with next content:
312
+ *
313
+ * ```ts
314
+ * import HighchartsVue from 'highcharts-vue';
315
+ * // import '@@/custom.scss'; // here is how you can import custom styles
316
+ *
317
+ * export default function (app) {
318
+ * app.use(HighchartsVue);
319
+ * }
320
+ * ```
321
+ *
322
+ * Install HighCharts:
323
+ *
324
+ * ```bash
325
+ * npm install highcharts highcharts-vue
326
+ * ```
327
+ *
328
+ * And specify vueUsesFile in AdminForth config:
329
+ *
330
+ * ```ts
331
+ * vueUsesFile: '@@/vue-uses.ts',
332
+ * ```
333
+ *
334
+ */
335
+ vueUsesFile?: string,
142
336
  },
337
+
338
+ /**
339
+ * If you want to Serve AdminForth from a subdirectory, e.g. on example.com/backoffice, you can specify it like:
340
+ *
341
+ * ```ts
342
+ * baseUrl: '/backoffice',
343
+ * ```
344
+ *
345
+ */
143
346
  baseUrl?: string,
144
347
 
145
348
  deleteConfirmation?: boolean,
@@ -152,15 +355,43 @@ export type AllowedActions = {
152
355
  edit?: boolean,
153
356
  show?: boolean,
154
357
  delete?: boolean,
155
- }
358
+ }
156
359
 
157
360
  export type ValidationObject = {
361
+ /**
362
+ * Should be pure string (not RegExp string)
363
+ *
364
+ * Example:
365
+ *
366
+ * ```ts
367
+ * // regex for email
368
+ * regExp: '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$',
369
+ * ```
370
+ *
371
+ */
158
372
  regExp: string,
373
+
374
+ /**
375
+ * Error message shown to user if validation fails
376
+ *
377
+ * Example: "Invalid email format"
378
+ */
159
379
  message: string,
160
380
  }
161
381
 
162
382
  export type DataSource = {
383
+ /**
384
+ * ID of datasource which you will use in resources to specify from which database to fetch data from
385
+ */
163
386
  id: string,
387
+
388
+ /**
389
+ * URL to database. Examples:
390
+ *
391
+ * - MongoDB: `mongodb://<user>:<password>@<host>:<port>/<database>`
392
+ * - PostgreSQL: `postgresql://<user>:<password>@<host>:<port>/<database>`
393
+ * - SQLite: `sqlite://<path>`
394
+ */
164
395
  url: string,
165
396
  }
166
397