@budibase/frontend-core 2.11.43 → 2.11.45

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.
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@budibase/frontend-core",
3
- "version": "2.11.43",
3
+ "version": "2.11.45",
4
4
  "description": "Budibase frontend core libraries used in builder and client",
5
5
  "author": "Budibase",
6
6
  "license": "MPL-2.0",
7
7
  "svelte": "src/index.js",
8
8
  "dependencies": {
9
- "@budibase/bbui": "2.11.43",
10
- "@budibase/shared-core": "2.11.43",
9
+ "@budibase/bbui": "2.11.45",
10
+ "@budibase/shared-core": "2.11.45",
11
11
  "dayjs": "^1.10.8",
12
12
  "lodash": "^4.17.21",
13
13
  "socket.io-client": "^4.6.1",
14
14
  "svelte": "^3.46.2"
15
15
  },
16
- "gitHead": "7f83c1b897398f29dbc5571ac039cff430a5db47"
16
+ "gitHead": "c3b2735a78e2c16d36d6cee6e534c7f7dc39fa98"
17
17
  }
@@ -4,6 +4,8 @@
4
4
  import { Icon, Popover, Menu, MenuItem, clickOutside } from "@budibase/bbui"
5
5
  import GridCell from "./GridCell.svelte"
6
6
  import { getColumnIcon } from "../lib/utils"
7
+ import { debounce } from "../../../utils/utils"
8
+ import { FieldType, FormulaTypes } from "@budibase/types"
7
9
 
8
10
  export let column
9
11
  export let idx
@@ -24,23 +26,69 @@
24
26
  definition,
25
27
  datasource,
26
28
  schema,
29
+ focusedCellId,
30
+ filter,
31
+ inlineFilters,
27
32
  } = getContext("grid")
28
33
 
34
+ const searchableTypes = [
35
+ FieldType.STRING,
36
+ FieldType.OPTIONS,
37
+ FieldType.NUMBER,
38
+ FieldType.BIGINT,
39
+ FieldType.ARRAY,
40
+ FieldType.LONGFORM,
41
+ ]
42
+
29
43
  let anchor
30
44
  let open = false
31
45
  let editIsOpen = false
32
46
  let timeout
33
47
  let popover
48
+ let searchValue
49
+ let input
34
50
 
35
51
  $: sortedBy = column.name === $sort.column
36
52
  $: canMoveLeft = orderable && idx > 0
37
53
  $: canMoveRight = orderable && idx < $renderedColumns.length - 1
38
- $: ascendingLabel = ["number", "bigint"].includes(column.schema?.type)
39
- ? "low-high"
40
- : "A-Z"
41
- $: descendingLabel = ["number", "bigint"].includes(column.schema?.type)
42
- ? "high-low"
43
- : "Z-A"
54
+ $: sortingLabels = getSortingLabels(column.schema?.type)
55
+ $: searchable = isColumnSearchable(column)
56
+ $: resetSearchValue(column.name)
57
+ $: searching = searchValue != null
58
+ $: debouncedUpdateFilter(searchValue)
59
+
60
+ const getSortingLabels = type => {
61
+ switch (type) {
62
+ case FieldType.NUMBER:
63
+ case FieldType.BIGINT:
64
+ return {
65
+ ascending: "low-high",
66
+ descending: "high-low",
67
+ }
68
+ case FieldType.DATETIME:
69
+ return {
70
+ ascending: "old-new",
71
+ descending: "new-old",
72
+ }
73
+ default:
74
+ return {
75
+ ascending: "A-Z",
76
+ descending: "Z-A",
77
+ }
78
+ }
79
+ }
80
+
81
+ const resetSearchValue = name => {
82
+ searchValue = $inlineFilters?.find(x => x.id === `inline-${name}`)?.value
83
+ }
84
+
85
+ const isColumnSearchable = col => {
86
+ const { type, formulaType } = col.schema
87
+ return (
88
+ searchableTypes.includes(type) ||
89
+ (type === FieldType.FORMULA && formulaType === FormulaTypes.STATIC)
90
+ )
91
+ }
44
92
 
45
93
  const editColumn = async () => {
46
94
  editIsOpen = true
@@ -141,12 +189,46 @@
141
189
  })
142
190
  }
143
191
 
192
+ const startSearching = async () => {
193
+ $focusedCellId = null
194
+ searchValue = ""
195
+ await tick()
196
+ input?.focus()
197
+ }
198
+
199
+ const onInputKeyDown = e => {
200
+ if (e.key === "Enter") {
201
+ updateFilter()
202
+ } else if (e.key === "Escape") {
203
+ input?.blur()
204
+ }
205
+ }
206
+
207
+ const stopSearching = () => {
208
+ searchValue = null
209
+ updateFilter()
210
+ }
211
+
212
+ const onBlurInput = () => {
213
+ if (searchValue === "") {
214
+ searchValue = null
215
+ }
216
+ updateFilter()
217
+ }
218
+
219
+ const updateFilter = () => {
220
+ filter.actions.addInlineFilter(column, searchValue)
221
+ }
222
+ const debouncedUpdateFilter = debounce(updateFilter, 250)
223
+
144
224
  onMount(() => subscribe("close-edit-column", cancelEdit))
145
225
  </script>
146
226
 
147
227
  <div
148
228
  class="header-cell"
149
229
  class:open
230
+ class:searchable
231
+ class:searching
150
232
  style="flex: 0 0 {column.width}px;"
151
233
  bind:this={anchor}
152
234
  class:disabled={$isReordering || $isResizing}
@@ -161,30 +243,49 @@
161
243
  defaultHeight
162
244
  center
163
245
  >
164
- <Icon
165
- size="S"
166
- name={getColumnIcon(column)}
167
- color={`var(--spectrum-global-color-gray-600)`}
168
- />
246
+ {#if searching}
247
+ <input
248
+ bind:this={input}
249
+ type="text"
250
+ bind:value={searchValue}
251
+ on:blur={onBlurInput}
252
+ on:click={() => focusedCellId.set(null)}
253
+ on:keydown={onInputKeyDown}
254
+ data-grid-ignore
255
+ />
256
+ {/if}
257
+
258
+ <div class="column-icon">
259
+ <Icon size="S" name={getColumnIcon(column)} />
260
+ </div>
261
+ <div class="search-icon" on:click={startSearching}>
262
+ <Icon hoverable size="S" name="Search" />
263
+ </div>
264
+
169
265
  <div class="name">
170
266
  {column.label}
171
267
  </div>
172
- {#if sortedBy}
173
- <div class="sort-indicator">
174
- <Icon
175
- size="S"
176
- name={$sort.order === "descending" ? "SortOrderDown" : "SortOrderUp"}
177
- color="var(--spectrum-global-color-gray-600)"
178
- />
268
+
269
+ {#if searching}
270
+ <div class="clear-icon" on:click={stopSearching}>
271
+ <Icon hoverable size="S" name="Close" />
272
+ </div>
273
+ {:else}
274
+ {#if sortedBy}
275
+ <div class="sort-indicator">
276
+ <Icon
277
+ hoverable
278
+ size="S"
279
+ name={$sort.order === "descending"
280
+ ? "SortOrderDown"
281
+ : "SortOrderUp"}
282
+ />
283
+ </div>
284
+ {/if}
285
+ <div class="more-icon" on:click={() => (open = true)}>
286
+ <Icon hoverable size="S" name="MoreVertical" />
179
287
  </div>
180
288
  {/if}
181
- <div class="more" on:click={() => (open = true)}>
182
- <Icon
183
- size="S"
184
- name="MoreVertical"
185
- color="var(--spectrum-global-color-gray-600)"
186
- />
187
- </div>
188
289
  </GridCell>
189
290
  </div>
190
291
 
@@ -235,7 +336,7 @@
235
336
  disabled={!canBeSortColumn(column.schema.type) ||
236
337
  (column.name === $sort.column && $sort.order === "ascending")}
237
338
  >
238
- Sort {ascendingLabel}
339
+ Sort {sortingLabels.ascending}
239
340
  </MenuItem>
240
341
  <MenuItem
241
342
  icon="SortOrderDown"
@@ -243,7 +344,7 @@
243
344
  disabled={!canBeSortColumn(column.schema.type) ||
244
345
  (column.name === $sort.column && $sort.order === "descending")}
245
346
  >
246
- Sort {descendingLabel}
347
+ Sort {sortingLabels.descending}
247
348
  </MenuItem>
248
349
  <MenuItem disabled={!canMoveLeft} icon="ChevronLeft" on:click={moveLeft}>
249
350
  Move left
@@ -283,6 +384,29 @@
283
384
  background: var(--grid-background-alt);
284
385
  }
285
386
 
387
+ /* Icon colors */
388
+ .header-cell :global(.spectrum-Icon) {
389
+ color: var(--spectrum-global-color-gray-600);
390
+ }
391
+ .header-cell :global(.spectrum-Icon.hoverable:hover) {
392
+ color: var(--spectrum-global-color-gray-800) !important;
393
+ cursor: pointer;
394
+ }
395
+
396
+ /* Search icon */
397
+ .search-icon {
398
+ display: none;
399
+ }
400
+ .header-cell.searchable:not(.open):hover .search-icon,
401
+ .header-cell.searchable.searching .search-icon {
402
+ display: block;
403
+ }
404
+ .header-cell.searchable:not(.open):hover .column-icon,
405
+ .header-cell.searchable.searching .column-icon {
406
+ display: none;
407
+ }
408
+
409
+ /* Main center content */
286
410
  .name {
287
411
  flex: 1 1 auto;
288
412
  width: 0;
@@ -290,23 +414,45 @@
290
414
  text-overflow: ellipsis;
291
415
  overflow: hidden;
292
416
  }
417
+ .header-cell.searching .name {
418
+ opacity: 0;
419
+ pointer-events: none;
420
+ }
421
+ input {
422
+ display: none;
423
+ font-family: var(--font-sans);
424
+ outline: none;
425
+ border: 1px solid transparent;
426
+ background: transparent;
427
+ color: var(--spectrum-global-color-gray-800);
428
+ position: absolute;
429
+ top: 0;
430
+ left: 0;
431
+ width: 100%;
432
+ height: 100%;
433
+ padding: 0 30px;
434
+ border-radius: 2px;
435
+ }
436
+ input:focus {
437
+ border: 1px solid var(--accent-color);
438
+ }
439
+ input:not(:focus) {
440
+ background: var(--spectrum-global-color-gray-200);
441
+ }
442
+ .header-cell.searching input {
443
+ display: block;
444
+ }
293
445
 
294
- .more {
446
+ /* Right icons */
447
+ .more-icon {
295
448
  display: none;
296
449
  padding: 4px;
297
450
  margin: 0 -4px;
298
451
  }
299
- .header-cell.open .more,
300
- .header-cell:hover .more {
452
+ .header-cell.open .more-icon,
453
+ .header-cell:hover .more-icon {
301
454
  display: block;
302
455
  }
303
- .more:hover {
304
- cursor: pointer;
305
- }
306
- .more:hover :global(.spectrum-Icon) {
307
- color: var(--spectrum-global-color-gray-800) !important;
308
- }
309
-
310
456
  .header-cell.open .sort-indicator,
311
457
  .header-cell:hover .sort-indicator {
312
458
  display: none;
@@ -27,8 +27,10 @@
27
27
  rowVerticalInversionIndex,
28
28
  columnHorizontalInversionIndex,
29
29
  selectedRows,
30
- loading,
30
+ loaded,
31
+ refreshing,
31
32
  config,
33
+ filter,
32
34
  } = getContext("grid")
33
35
 
34
36
  let visible = false
@@ -153,7 +155,7 @@
153
155
  <!-- New row FAB -->
154
156
  <TempTooltip
155
157
  text="Click here to create your first row"
156
- condition={hasNoRows && !$loading}
158
+ condition={hasNoRows && $loaded && !$filter?.length && !$refreshing}
157
159
  type={TooltipType.Info}
158
160
  >
159
161
  {#if !visible && !selectedRowCount && $config.canAddRows}
@@ -21,6 +21,7 @@
21
21
  const ignoredOriginSelectors = [
22
22
  ".spectrum-Modal",
23
23
  "#builder-side-panel-container",
24
+ "[data-grid-ignore]",
24
25
  ]
25
26
 
26
27
  // Global key listener which intercepts all key events
@@ -1,8 +1,9 @@
1
- import { derived, get, writable } from "svelte/store"
2
- import { getDatasourceDefinition } from "../../../fetch"
1
+ import { derived, get } from "svelte/store"
2
+ import { getDatasourceDefinition, getDatasourceSchema } from "../../../fetch"
3
+ import { memo } from "../../../utils"
3
4
 
4
5
  export const createStores = () => {
5
- const definition = writable(null)
6
+ const definition = memo(null)
6
7
 
7
8
  return {
8
9
  definition,
@@ -10,10 +11,15 @@ export const createStores = () => {
10
11
  }
11
12
 
12
13
  export const deriveStores = context => {
13
- const { definition, schemaOverrides, columnWhitelist, datasource } = context
14
+ const { API, definition, schemaOverrides, columnWhitelist, datasource } =
15
+ context
14
16
 
15
17
  const schema = derived(definition, $definition => {
16
- let schema = $definition?.schema
18
+ let schema = getDatasourceSchema({
19
+ API,
20
+ datasource: get(datasource),
21
+ definition: $definition,
22
+ })
17
23
  if (!schema) {
18
24
  return null
19
25
  }
@@ -66,6 +66,8 @@ export const initialise = context => {
66
66
  datasource,
67
67
  sort,
68
68
  filter,
69
+ inlineFilters,
70
+ allFilters,
69
71
  nonPlus,
70
72
  initialFilter,
71
73
  initialSortColumn,
@@ -87,6 +89,7 @@ export const initialise = context => {
87
89
 
88
90
  // Wipe state
89
91
  filter.set(get(initialFilter))
92
+ inlineFilters.set([])
90
93
  sort.set({
91
94
  column: get(initialSortColumn),
92
95
  order: get(initialSortOrder) || "ascending",
@@ -94,14 +97,14 @@ export const initialise = context => {
94
97
 
95
98
  // Update fetch when filter changes
96
99
  unsubscribers.push(
97
- filter.subscribe($filter => {
100
+ allFilters.subscribe($allFilters => {
98
101
  // Ensure we're updating the correct fetch
99
102
  const $fetch = get(fetch)
100
103
  if (!isSameDatasource($fetch?.options?.datasource, $datasource)) {
101
104
  return
102
105
  }
103
106
  $fetch.update({
104
- filter: $filter,
107
+ filter: $allFilters,
105
108
  })
106
109
  })
107
110
  )
@@ -71,6 +71,8 @@ export const initialise = context => {
71
71
  datasource,
72
72
  fetch,
73
73
  filter,
74
+ inlineFilters,
75
+ allFilters,
74
76
  sort,
75
77
  table,
76
78
  initialFilter,
@@ -93,6 +95,7 @@ export const initialise = context => {
93
95
 
94
96
  // Wipe state
95
97
  filter.set(get(initialFilter))
98
+ inlineFilters.set([])
96
99
  sort.set({
97
100
  column: get(initialSortColumn),
98
101
  order: get(initialSortOrder) || "ascending",
@@ -100,14 +103,14 @@ export const initialise = context => {
100
103
 
101
104
  // Update fetch when filter changes
102
105
  unsubscribers.push(
103
- filter.subscribe($filter => {
106
+ allFilters.subscribe($allFilters => {
104
107
  // Ensure we're updating the correct fetch
105
108
  const $fetch = get(fetch)
106
109
  if ($fetch?.options?.datasource?.tableId !== $datasource.tableId) {
107
110
  return
108
111
  }
109
112
  $fetch.update({
110
- filter: $filter,
113
+ filter: $allFilters,
111
114
  })
112
115
  })
113
116
  )
@@ -73,6 +73,8 @@ export const initialise = context => {
73
73
  sort,
74
74
  rows,
75
75
  filter,
76
+ inlineFilters,
77
+ allFilters,
76
78
  subscribe,
77
79
  viewV2,
78
80
  initialFilter,
@@ -97,6 +99,7 @@ export const initialise = context => {
97
99
 
98
100
  // Reset state for new view
99
101
  filter.set(get(initialFilter))
102
+ inlineFilters.set([])
100
103
  sort.set({
101
104
  column: get(initialSortColumn),
102
105
  order: get(initialSortOrder) || "ascending",
@@ -143,21 +146,19 @@ export const initialise = context => {
143
146
  order: $sort.order || "ascending",
144
147
  },
145
148
  })
146
- await rows.actions.refreshData()
147
149
  }
148
150
  }
149
- // Otherwise just update the fetch
150
- else {
151
- // Ensure we're updating the correct fetch
152
- const $fetch = get(fetch)
153
- if ($fetch?.options?.datasource?.tableId !== $datasource.tableId) {
154
- return
155
- }
156
- $fetch.update({
157
- sortOrder: $sort.order || "ascending",
158
- sortColumn: $sort.column,
159
- })
151
+
152
+ // Also update the fetch to ensure the new sort is respected.
153
+ // Ensure we're updating the correct fetch.
154
+ const $fetch = get(fetch)
155
+ if ($fetch?.options?.datasource?.tableId !== $datasource.tableId) {
156
+ return
160
157
  }
158
+ $fetch.update({
159
+ sortOrder: $sort.order,
160
+ sortColumn: $sort.column,
161
+ })
161
162
  })
162
163
  )
163
164
 
@@ -176,20 +177,25 @@ export const initialise = context => {
176
177
  ...$view,
177
178
  query: $filter,
178
179
  })
179
- await rows.actions.refreshData()
180
180
  }
181
181
  }
182
- // Otherwise just update the fetch
183
- else {
184
- // Ensure we're updating the correct fetch
185
- const $fetch = get(fetch)
186
- if ($fetch?.options?.datasource?.tableId !== $datasource.tableId) {
187
- return
188
- }
189
- $fetch.update({
190
- filter: $filter,
191
- })
182
+ })
183
+ )
184
+
185
+ // Keep fetch up to date with filters.
186
+ // If we're able to save filters against the view then we only need to apply
187
+ // inline filters to the fetch, as saved filters are applied server side.
188
+ // If we can't save filters, then all filters must be applied to the fetch.
189
+ unsubscribers.push(
190
+ allFilters.subscribe($allFilters => {
191
+ // Ensure we're updating the correct fetch
192
+ const $fetch = get(fetch)
193
+ if ($fetch?.options?.datasource?.tableId !== $datasource.tableId) {
194
+ return
192
195
  }
196
+ $fetch.update({
197
+ filter: $allFilters,
198
+ })
193
199
  })
194
200
  )
195
201
 
@@ -1,13 +1,79 @@
1
- import { writable, get } from "svelte/store"
1
+ import { writable, get, derived } from "svelte/store"
2
+ import { FieldType } from "@budibase/types"
2
3
 
3
4
  export const createStores = context => {
4
5
  const { props } = context
5
6
 
6
7
  // Initialise to default props
7
8
  const filter = writable(get(props).initialFilter)
9
+ const inlineFilters = writable([])
8
10
 
9
11
  return {
10
12
  filter,
13
+ inlineFilters,
14
+ }
15
+ }
16
+
17
+ export const deriveStores = context => {
18
+ const { filter, inlineFilters } = context
19
+
20
+ const allFilters = derived(
21
+ [filter, inlineFilters],
22
+ ([$filter, $inlineFilters]) => {
23
+ return [...($filter || []), ...$inlineFilters]
24
+ }
25
+ )
26
+
27
+ return {
28
+ allFilters,
29
+ }
30
+ }
31
+
32
+ export const createActions = context => {
33
+ const { filter, inlineFilters } = context
34
+
35
+ const addInlineFilter = (column, value) => {
36
+ const filterId = `inline-${column.name}`
37
+ const type = column.schema.type
38
+ let inlineFilter = {
39
+ field: column.name,
40
+ id: filterId,
41
+ operator: "string",
42
+ valueType: "value",
43
+ type,
44
+ value,
45
+ }
46
+
47
+ // Add overrides specific so the certain column type
48
+ if (type === FieldType.NUMBER) {
49
+ inlineFilter.value = parseFloat(value)
50
+ inlineFilter.operator = "equal"
51
+ } else if (type === FieldType.BIGINT) {
52
+ inlineFilter.operator = "equal"
53
+ } else if (type === FieldType.ARRAY) {
54
+ inlineFilter.operator = "contains"
55
+ }
56
+
57
+ // Add this filter
58
+ inlineFilters.update($inlineFilters => {
59
+ // Remove any existing inline filter for this column
60
+ $inlineFilters = $inlineFilters?.filter(x => x.id !== filterId)
61
+
62
+ // Add new one if a value exists
63
+ if (value) {
64
+ $inlineFilters.push(inlineFilter)
65
+ }
66
+ return $inlineFilters
67
+ })
68
+ }
69
+
70
+ return {
71
+ filter: {
72
+ ...filter,
73
+ actions: {
74
+ addInlineFilter,
75
+ },
76
+ },
11
77
  }
12
78
  }
13
79
 
@@ -8,6 +8,7 @@ export const createStores = () => {
8
8
  const rows = writable([])
9
9
  const loading = writable(false)
10
10
  const loaded = writable(false)
11
+ const refreshing = writable(false)
11
12
  const rowChangeCache = writable({})
12
13
  const inProgressChanges = writable({})
13
14
  const hasNextPage = writable(false)
@@ -53,6 +54,7 @@ export const createStores = () => {
53
54
  fetch,
54
55
  rowLookupMap,
55
56
  loaded,
57
+ refreshing,
56
58
  loading,
57
59
  rowChangeCache,
58
60
  inProgressChanges,
@@ -66,7 +68,7 @@ export const createActions = context => {
66
68
  rows,
67
69
  rowLookupMap,
68
70
  definition,
69
- filter,
71
+ allFilters,
70
72
  loading,
71
73
  sort,
72
74
  datasource,
@@ -82,6 +84,7 @@ export const createActions = context => {
82
84
  notifications,
83
85
  fetch,
84
86
  isDatasourcePlus,
87
+ refreshing,
85
88
  } = context
86
89
  const instanceLoaded = writable(false)
87
90
 
@@ -108,7 +111,7 @@ export const createActions = context => {
108
111
  // Tick to allow other reactive logic to update stores when datasource changes
109
112
  // before proceeding. This allows us to wipe filters etc if needed.
110
113
  await tick()
111
- const $filter = get(filter)
114
+ const $allFilters = get(allFilters)
112
115
  const $sort = get(sort)
113
116
 
114
117
  // Determine how many rows to fetch per page
@@ -120,7 +123,7 @@ export const createActions = context => {
120
123
  API,
121
124
  datasource: $datasource,
122
125
  options: {
123
- filter: $filter,
126
+ filter: $allFilters,
124
127
  sortColumn: $sort.column,
125
128
  sortOrder: $sort.order,
126
129
  limit,
@@ -176,6 +179,9 @@ export const createActions = context => {
176
179
  // Notify that we're loaded
177
180
  loading.set(false)
178
181
  }
182
+
183
+ // Update refreshing state
184
+ refreshing.set($fetch.loading)
179
185
  })
180
186
 
181
187
  fetch.set(newFetch)
@@ -35,9 +35,28 @@ export default class ViewV2Fetch extends DataFetch {
35
35
  }
36
36
 
37
37
  async getData() {
38
- const { datasource, limit, sortColumn, sortOrder, sortType, paginate } =
39
- this.options
40
- const { cursor, query } = get(this.store)
38
+ const {
39
+ datasource,
40
+ limit,
41
+ sortColumn,
42
+ sortOrder,
43
+ sortType,
44
+ paginate,
45
+ filter,
46
+ } = this.options
47
+ const { cursor, query, definition } = get(this.store)
48
+
49
+ // If sort/filter params are not defined, update options to store the
50
+ // params built in to this view. This ensures that we can accurately
51
+ // compare old and new params and skip a redundant API call.
52
+ if (!sortColumn && definition.sort?.field) {
53
+ this.options.sortColumn = definition.sort.field
54
+ this.options.sortOrder = definition.sort.order
55
+ }
56
+ if (!filter?.length && definition.query?.length) {
57
+ this.options.filter = definition.query
58
+ }
59
+
41
60
  try {
42
61
  const res = await this.API.viewV2.fetch({
43
62
  viewId: datasource.id,
@@ -32,12 +32,24 @@ export const fetchData = ({ API, datasource, options }) => {
32
32
  return new Fetch({ API, datasource, ...options })
33
33
  }
34
34
 
35
- // Fetches the definition of any type of datasource
36
- export const getDatasourceDefinition = async ({ API, datasource }) => {
35
+ // Creates an empty fetch instance with no datasource configured, so no data
36
+ // will initially be loaded
37
+ const createEmptyFetchInstance = ({ API, datasource }) => {
37
38
  const handler = DataFetchMap[datasource?.type]
38
39
  if (!handler) {
39
40
  return null
40
41
  }
41
- const instance = new handler({ API })
42
- return await instance.getDefinition(datasource)
42
+ return new handler({ API })
43
+ }
44
+
45
+ // Fetches the definition of any type of datasource
46
+ export const getDatasourceDefinition = async ({ API, datasource }) => {
47
+ const instance = createEmptyFetchInstance({ API, datasource })
48
+ return await instance?.getDefinition(datasource)
49
+ }
50
+
51
+ // Fetches the schema of any type of datasource
52
+ export const getDatasourceSchema = ({ API, datasource, definition }) => {
53
+ const instance = createEmptyFetchInstance({ API, datasource })
54
+ return instance?.getSchema(datasource, definition)
43
55
  }