@soleil-se/config-svelte 1.30.3 → 1.31.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.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,18 @@ All notable changes to this project will be documented in this file.
8
8
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
9
9
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
10
10
 
11
+ ## [1.31.0] - 2026-04-10
12
+
13
+ - Add error messages for when fetching data for `ImageSelector` and `RepositoryNodeSelector` fails due to lack of permissions or REST-API being disabled or restricted.
14
+ - New `fetchRestApi` utility function for fetching data from the REST-API with proper error handling.
15
+ - Add missing default slot for `TextList` component.
16
+ - Set label as `undefined` correclty in `RadioGroup`.
17
+
18
+ ## [1.30.4] - 2026-02-26
19
+
20
+ - Do not set focus on hidden inputs in `Modal` component.
21
+ - Get `CustomList` list names from `textarea` elements as well.
22
+
11
23
  ## [1.30.3] - 2026-02-26
12
24
 
13
25
  - Update the type definitions as well.
@@ -49,7 +49,9 @@
49
49
  id,
50
50
  name:
51
51
  document.querySelector(`#modal_${id} .custom-list-item-template`)?.innerHTML ||
52
- document.querySelector(`#modal_${id} input:not([type=radio],[type=checkbox])`)?.value ||
52
+ document.querySelector(
53
+ `#modal_${id} :is(input:not([type=radio],[type=checkbox]), textarea)`,
54
+ )?.value ||
53
55
  id,
54
56
  open: false,
55
57
  }));
@@ -1,8 +1,7 @@
1
- <!-- prettier-ignore -->
2
1
  <script>
3
2
  import { createEventDispatcher } from 'svelte';
4
3
 
5
- import { triggerInput } from '../../utils';
4
+ import { triggerInput, fetchRestApi } from '../../utils';
6
5
  import i18n from './i18n';
7
6
  import Panel from '../Panel';
8
7
  import NodeSelector from '../NodeSelector';
@@ -21,7 +20,7 @@
21
20
  export let node = undefined;
22
21
  /** @type {string} */
23
22
  export let alt;
24
- alt = name ? values[`${name}Alt`] ?? alt : alt;
23
+ alt = name ? (values[`${name}Alt`] ?? alt) : alt;
25
24
  /** @type {boolean} */
26
25
  export let hideDecorative = false;
27
26
  /** @type {boolean} */
@@ -34,17 +33,27 @@
34
33
  let useCustomAlt = !!alt;
35
34
  let customAlt = alt;
36
35
  let validationEl;
36
+ let fetchError = null;
37
37
 
38
38
  const dispatch = createEventDispatcher();
39
39
 
40
- const setCustomValidity = () => {
40
+ function setCustomValidity() {
41
41
  validationEl?.setCustomValidity(imageArchiveAlt ? '' : i18n('validation'));
42
- };
42
+ }
43
43
 
44
- const onChange = async () => {
45
- if (node) {
46
- const response = await fetch(`/rest-api/1/0/${node}/properties`).then((res) => res.json());
47
- imageArchiveAlt = response.alt || '';
44
+ async function fetchImageAlt() {
45
+ fetchError = null;
46
+ if (node && !decorative && !useCustomAlt) {
47
+ try {
48
+ const response = await fetchRestApi(`${node}/properties`, {
49
+ data: { properties: ['alt'] },
50
+ });
51
+ imageArchiveAlt = response?.alt || '';
52
+ } catch (error) {
53
+ console.error(error);
54
+ fetchError = error;
55
+ imageArchiveAlt = i18n('couldNotGetAlt');
56
+ }
48
57
 
49
58
  setCustomValidity();
50
59
  } else {
@@ -52,12 +61,13 @@
52
61
  validationEl?.setCustomValidity('');
53
62
  }
54
63
  triggerInput(validationEl);
55
-
56
64
  dispatch('input', { node, alt });
57
65
  dispatch('change', { node, alt });
58
- };
66
+ }
67
+
68
+ function onUseCustomToggle() {
69
+ fetchImageAlt();
59
70
 
60
- const onUseCustomToggle = () => {
61
71
  if (useCustomAlt) {
62
72
  alt = customAlt || '';
63
73
  } else {
@@ -65,8 +75,7 @@
65
75
  alt = undefined;
66
76
  setCustomValidity();
67
77
  }
68
- };
69
-
78
+ }
70
79
  </script>
71
80
 
72
81
  <!--
@@ -77,6 +86,12 @@
77
86
  -->
78
87
 
79
88
  <Panel {heading} {show}>
89
+ {#if fetchError}
90
+ <p class="alert alert-danger" role="alert">
91
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -->
92
+ {@html i18n(fetchError.status === 403 ? 'fetchError403' : 'fetchError')}
93
+ </p>
94
+ {/if}
80
95
  <NodeSelector
81
96
  name="{name}Node"
82
97
  label={i18n('selectImage')}
@@ -84,36 +99,36 @@
84
99
  selectable="sv:image"
85
100
  type="image"
86
101
  bind:value={node}
87
- on:change={onChange}
102
+ on:change={fetchImageAlt}
88
103
  />
89
104
  <div class="form-group">
90
105
  {#if !hideDecorative}
91
106
  <Checkbox
92
107
  name="{name}Decorative"
93
- class="checkbox-group"
94
108
  label={i18n('imageIsDecorative')}
109
+ class="checkbox-group"
95
110
  bind:value={decorative}
96
- on:change={onChange}
111
+ on:change={fetchImageAlt}
97
112
  />
98
113
  {/if}
99
114
  {#if !decorative}
100
115
  {#if useCustomAlt}
101
116
  <InputField name="{name}Alt" label={i18n('altText')} required bind:value={alt} />
102
117
  {:else}
103
- <InputField disabled label={i18n('altFromCaption')} readonly value={imageArchiveAlt}>
118
+ <InputField label={i18n('altFromCaption')} disabled readonly value={imageArchiveAlt}>
104
119
  <input bind:this={validationEl} class="sr-only" />
105
120
  </InputField>
106
121
  {/if}
107
122
 
108
123
  <Checkbox
109
- class="checkbox-group"
110
124
  label={i18n('customAltText')}
125
+ class="checkbox-group"
111
126
  bind:value={useCustomAlt}
112
127
  on:change={onUseCustomToggle}
113
128
  />
114
129
  {/if}
115
130
  </div>
116
- <slot />
131
+ <slot></slot>
117
132
  </Panel>
118
133
 
119
134
  <style>
@@ -8,7 +8,10 @@ export default createI18n({
8
8
  altFromCaption: 'Alt-text (från bildbeskrivning)',
9
9
  customAltText: 'Ange egen alt-text',
10
10
  validation: 'Bilden saknar beskrivning (alt-text). Klicka på bilden ovan och skriv texten i fältet Bildbeskrivning.',
11
+ couldNotGetAlt: 'Det gick inte att hämta bildens alt-text...',
11
12
  imageIsDecorative: 'Bilden är dekorativ',
13
+ fetchError: '<strong>Fel:</strong> Det gick inte att hämta bildens egenskaper.',
14
+ fetchError403: '<strong>Fel:</strong> Det gick inte att hämta bildens egenskaper, antingen har du inte behörighet till bilden eller så är REST-API:et inaktiverat eller begränsat. Kontrollera behörigheter och webbplatsinställningar.',
12
15
  },
13
16
  no: {
14
17
  heading: 'Bilde',
@@ -17,7 +20,10 @@ export default createI18n({
17
20
  altFromCaption: 'Alt-tekst (fra bildebeskrivelse)',
18
21
  customAltText: 'Skriv inn din egen alt-tekst',
19
22
  validation: 'Bildet mangler beskrivelse (alt-tekst). Klikk på bildet over og skriv inn teksten i feltet Bildebeskrivelse.',
23
+ couldNotGetAlt: 'Det gikk ikke å hente bildets alt-tekst...',
20
24
  imageIsDecorative: 'Bildet er dekorativt',
25
+ fetchError: '<strong>Feil:</strong> Det gikk ikke å hente bildets egenskaper.',
26
+ fetchError403: '<strong>Feil:</strong> Det gikk ikke å hente bildets egenskaper, enten har du ikke behørighet til bildet eller så er REST-API:et inaktivert eller begrenset. Kontroller behørigheter og nettstedsinnstillinger.',
21
27
  },
22
28
  en: {
23
29
  heading: 'Image',
@@ -26,6 +32,9 @@ export default createI18n({
26
32
  altFromCaption: 'Alt text (from image caption)',
27
33
  customAltText: 'Use custom alt text',
28
34
  validation: 'The image lacks description (alt-text). Click on the image above and enter the text in the Image description field.',
35
+ couldNotGetAlt: 'Could not fetch the image alt text...',
29
36
  imageIsDecorative: 'Image is decorative',
37
+ fetchError: '<strong>Error:</strong> An error occurred while fetching the image properties.',
38
+ fetchError403: '<strong>Error:</strong> An error occurred while fetching the image properties, you either do not have permission to see the image or the REST-API is not enabled or restricted. Check permissions and site settings.',
30
39
  },
31
40
  });
@@ -54,7 +54,7 @@
54
54
  }
55
55
 
56
56
  function focusFirstInput() {
57
- const firstInput = element.querySelector('input, textarea');
57
+ const firstInput = element.querySelector('input:not([type=hidden]), textarea');
58
58
  if (firstInput) firstInput.focus();
59
59
  }
60
60
 
@@ -14,7 +14,7 @@
14
14
  /** @type {string} */
15
15
  export let legend;
16
16
  /** @type {string} */
17
- export let label;
17
+ export let label = undefined;
18
18
  /** @type {string} */
19
19
  export let description = undefined;
20
20
  /** @type {boolean} */
@@ -1,7 +1,6 @@
1
- <!-- prettier-ignore -->
2
1
  <script>
3
2
  import { onMount } from 'svelte';
4
- import { getAppContext } from '../../utils';
3
+ import { getAppContext, fetchRestApi } from '../../utils';
5
4
  import CustomSelector from '../CustomSelector';
6
5
  import i18n from './i18n';
7
6
 
@@ -29,7 +28,7 @@
29
28
  export let type;
30
29
 
31
30
  let options = [];
32
- let error = false;
31
+ let fetchError = null;
33
32
 
34
33
  function toTitleCase(str) {
35
34
  return str
@@ -43,23 +42,27 @@
43
42
  repository = !repository.endsWith('Repository') ? `${repository} Repository` : repository;
44
43
  const endpoint = repository.includes('Alias') ? 'aliases' : 'nodes';
45
44
 
46
- return `/rest-api/1/0/${siteName}/${repository}/${endpoint}`;
45
+ return `${siteName}/${repository}/${endpoint}`;
47
46
  }
48
47
 
49
48
  onMount(async () => {
50
49
  const endpoint = getEndpoint();
51
- const result = await fetch(endpoint)
52
- .then((res) => {
53
- if (res.ok) return res;
54
- error = true;
55
- throw new Error(
56
- `Error (${res.status}) - Unable to fetch nodes, is site name and repository correct?`,
50
+ try {
51
+ fetchError = null;
52
+ const result = await fetchRestApi(endpoint);
53
+ options = (result || []).map((item) => ({ label: item.name, value: item.id }));
54
+ } catch (error) {
55
+ fetchError = error;
56
+ if (error.status === 403) {
57
+ console.error(
58
+ `Error (${error.status}) - Unable to fetch nodes, check permissions and if REST-API is enabled.`,
57
59
  );
58
- })
59
- .then((res) => res.json())
60
- .catch((e) => console.error(e.message));
61
-
62
- options = (result || []).map((item) => ({ label: item.name, value: item.id }));
60
+ } else {
61
+ console.error(
62
+ `Error (${error.status}) - Unable to fetch nodes, is site name and repository correct?${error.message ? ` ${error.message}` : ''}`,
63
+ );
64
+ }
65
+ }
63
66
  });
64
67
  </script>
65
68
 
@@ -73,9 +76,9 @@
73
76
  <CustomSelector
74
77
  {id}
75
78
  {name}
79
+ {label}
76
80
  {description}
77
81
  {disabled}
78
- {label}
79
82
  {multiple}
80
83
  {options}
81
84
  {required}
@@ -85,11 +88,13 @@
85
88
  on:input
86
89
  on:change
87
90
  >
88
- <slot name="label" />
89
- <slot />
90
- {#if error}
91
- <!-- eslint-disable-next-line svelte/no-at-html-tags -->
92
- <p class="alert alert-danger">{@html i18n('error')}</p>
91
+ <slot name="label"></slot>
92
+ <slot></slot>
93
+ {#if fetchError}
94
+ <p class="alert alert-danger" role="alert">
95
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -->
96
+ {@html i18n(fetchError.status === 403 ? 'fetchError403' : 'fetchError')}
97
+ </p>
93
98
  {/if}
94
99
  </CustomSelector>
95
100
 
@@ -2,12 +2,15 @@ import createI18n from '../../utils/createI18n';
2
2
 
3
3
  export default createI18n({
4
4
  sv: {
5
- error: '<strong>Fel:</strong> Det gick inte att hämta noder, se konsolen för mer information.',
5
+ fetchError: '<strong>Fel:</strong> Det gick inte att hämta noder, se konsolen för mer information.',
6
+ fetchError403: '<strong>Fel:</strong> Det gick inte att hämta noder, antingen har du inte rätt behörighet eller så är REST-API:et inaktiverat eller begränsat. Kontrollera behörigheter och webbplatsinställningar.',
6
7
  },
7
8
  no: {
8
- error: '<strong>Feil:</strong> Noder kunne ikke hentes, se konsollen for mer informasjon.',
9
+ fetchError: '<strong>Feil:</strong> Noder kunne ikke hentes, se konsollen for mer informasjon.',
10
+ fetchError403: '<strong>Feil:</strong> Noder kunne ikke hentes, enten har du ikke riktig behørighet eller så er REST-API:et inaktivert eller begrenset. Kontroller behørigheter og nettstedsinnstillinger.',
9
11
  },
10
12
  en: {
11
- error: '<strong>Error:</strong> Nodes could not be fetched, see console for more information.',
13
+ fetchError: '<strong>Error:</strong> Nodes could not be fetched, see console for more information.',
14
+ fetchError403: '<strong>Error:</strong> Nodes could not be fetched, you either do not have the correct permissions or the REST-API is not enabled or restricted. Check permissions and site settings.',
12
15
  },
13
16
  });
@@ -119,6 +119,7 @@
119
119
  {#if disabled}
120
120
  <div class="disabled-overlay"></div>
121
121
  {/if}
122
+ <slot />
122
123
  <DataHolder {id} {name} {required} {value} />
123
124
  </div>
124
125
 
package/jsconfig.json ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "exclude": ["node_modules", "dist"]
3
+ }
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "svelte"
10
10
  ],
11
11
  "homepage": "https://docs.soleil.se/packages/config-svelte",
12
- "version": "1.30.3",
12
+ "version": "1.31.0",
13
13
  "main": "./index.js",
14
14
  "module": "./index.js",
15
15
  "svelte": "./index.js",
@@ -39,23 +39,24 @@
39
39
  "svelte": "^3.46.0 || ^4.0.0 || ^5.0.0-next || ^5.0.0"
40
40
  },
41
41
  "devDependencies": {
42
- "@soleil-se/eslint-config": "^6.0.2",
43
- "@soleil-se/stylelint-config": "^4.3.0",
44
- "eslint": "^9.18.0",
45
- "glob": "^11.0.0",
46
- "stylelint": "^16.14.1",
47
- "sveld": "^0.22.1",
48
- "svelte": "^4.2.19",
42
+ "@soleil-se/eslint-config": "^6.3.1",
43
+ "@soleil-se/stylelint-config": "^4.4.0",
44
+ "eslint": "^9.39.4",
45
+ "glob": "^11.1.0",
46
+ "sass": "^1.99.0",
47
+ "stylelint": "^16.26.1",
48
+ "sveld": "^0.22.5",
49
+ "svelte": "^4.2.20",
49
50
  "svelte-preprocess": "^6.0.3"
50
51
  },
51
52
  "dependencies": {
52
- "@soleil-se/config-validate": "^1.2.1",
53
- "a11y-dialog": "8.1.3",
54
- "svelte-dnd-action": "^0.9.61"
53
+ "@soleil-se/config-validate": "^1.3.0",
54
+ "a11y-dialog": "8.1.5",
55
+ "svelte-dnd-action": "^0.9.69"
55
56
  },
56
57
  "scripts": {
57
58
  "create-type-definitions": "node ./createTypeDefinitions.js"
58
59
  },
59
60
  "private": false,
60
- "gitHead": "0fe5393da1f2dace60efb2c8463cbe314880d575"
61
+ "gitHead": "eb052ae65db1ed6a372ee25894719d587368e80d"
61
62
  }
@@ -0,0 +1,6 @@
1
+ /* eslint-disable import/no-extraneous-dependencies */
2
+ const preprocess = require('svelte-preprocess');
3
+
4
+ module.exports = {
5
+ preprocess: preprocess(),
6
+ };
@@ -24,7 +24,7 @@ export type RadioGroupProps = {
24
24
  /**
25
25
  * @default undefined
26
26
  */
27
- label: string;
27
+ label?: string;
28
28
 
29
29
  /**
30
30
  * @default undefined
@@ -50,5 +50,5 @@ export type TextListProps = {
50
50
  export default class TextList extends SvelteComponentTyped<
51
51
  TextListProps,
52
52
  { input: CustomEvent<any>; change: CustomEvent<any> },
53
- { description: {}; label: {} }
53
+ { default: {}; description: {}; label: {} }
54
54
  > {}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Fetches JSON data from the REST-API.
3
+ * @template T
4
+ * @param {string} uri - The URI to fetch JSON data from.
5
+ * @param {Options} [options] - The options for the fetch request with some extensions.
6
+ * @returns {Promise<T>} A promise that resolves to the JSON response.
7
+ * @throws {FetchError} If the response is not successful or cannot be parsed as JSON.
8
+ */
9
+ export default function fetchRestApi<T>(uri: string, options?: Options): Promise<T>;
10
+ /**
11
+ * @typedef {Object} ExtensionOptions
12
+ * @property {{ [key: string]: unknown }} [data] - The data to be included in the request as JSON.
13
+ * @property {'offline' | 'online'} [model] - Selects which REST API model path to use.
14
+ *
15
+ * @typedef {RequestInit & ExtensionOptions} Options
16
+ */
17
+ /**
18
+ * Custom error class for fetch-related errors with additional HTTP context.
19
+ */
20
+ export class FetchError extends Error {
21
+ /**
22
+ * @param {string} message - The error message.
23
+ * @param {Object} [options] - Error options.
24
+ * @param {number} [options.status] - HTTP status code.
25
+ * @param {boolean} [options.aborted=false] - Whether the request was aborted.
26
+ * @param {Object.<string, unknown>} [options.additionalProps] - Additional properties.
27
+ */
28
+ constructor(message: string, { status, aborted, ...additionalProps }?: {
29
+ status?: number;
30
+ aborted?: boolean;
31
+ additionalProps?: {
32
+ [x: string]: unknown;
33
+ };
34
+ });
35
+ status: number;
36
+ aborted: boolean;
37
+ }
38
+ export type ExtensionOptions = {
39
+ /**
40
+ * - The data to be included in the request as JSON.
41
+ */
42
+ data?: {
43
+ [key: string]: unknown;
44
+ };
45
+ /**
46
+ * - Selects which REST API model path to use.
47
+ */
48
+ model?: "offline" | "online";
49
+ };
50
+ export type Options = RequestInit & ExtensionOptions;
@@ -6,4 +6,5 @@ export { default as triggerInput } from "./triggerInput";
6
6
  export { default as createI18n } from "./createI18n";
7
7
  export { default as getAppProps } from "./getAppProps";
8
8
  export { default as getAppContext } from "./getAppContext";
9
+ export { default as fetchRestApi } from "./fetchRestApi";
9
10
  export { generateId, addGeneratedIds } from "./generateId";
@@ -0,0 +1,123 @@
1
+ /**
2
+ * @typedef {Object} ExtensionOptions
3
+ * @property {{ [key: string]: unknown }} [data] - The data to be included in the request as JSON.
4
+ * @property {'offline' | 'online'} [model] - Selects which REST API model path to use.
5
+ *
6
+ * @typedef {RequestInit & ExtensionOptions} Options
7
+ */
8
+
9
+ /**
10
+ * Custom error class for fetch-related errors with additional HTTP context.
11
+ */
12
+ export class FetchError extends Error {
13
+ /**
14
+ * @param {string} message - The error message.
15
+ * @param {Object} [options] - Error options.
16
+ * @param {number} [options.status] - HTTP status code.
17
+ * @param {boolean} [options.aborted=false] - Whether the request was aborted.
18
+ * @param {Object.<string, unknown>} [options.additionalProps] - Additional properties.
19
+ */
20
+ constructor(message, { status, aborted = false, ...additionalProps } = {}) {
21
+ super(message);
22
+ this.name = 'FetchError';
23
+ this.status = status;
24
+ this.aborted = aborted;
25
+
26
+ // Add any additional properties from the error response
27
+ Object.entries(additionalProps).forEach(([key, value]) => {
28
+ if (key !== 'message' && key !== 'status' && key !== 'aborted') {
29
+ this[key] = value;
30
+ }
31
+ });
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Attempts to parse response text as JSON.
37
+ * @param {Response} response - The fetch Response object.
38
+ * @returns {Promise<unknown | undefined>} The parsed JSON object or undefined if parsing fails.
39
+ */
40
+ async function toJson(response) {
41
+ const text = await response.text();
42
+ try {
43
+ return JSON.parse(text);
44
+ } catch (e) {
45
+ return undefined;
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Creates a FetchError object from a failed response, including JSON error details.
51
+ * @param {Response} response - The failed fetch Response object.
52
+ * @returns {Promise<FetchError>} A FetchError with additional properties from the response JSON.
53
+ */
54
+ async function responseError(response) {
55
+ const json = await toJson(response) || {};
56
+ const message = json?.message || response?.statusText;
57
+ return new FetchError(message, {
58
+ status: response.status,
59
+ aborted: false,
60
+ ...json,
61
+ });
62
+ }
63
+
64
+ /**
65
+ * Handles the fetch response, throwing errors for non-OK responses and parsing JSON.
66
+ * @param {Response} response - The fetch Response object.
67
+ * @returns {Promise<unknown>} The parsed JSON response.
68
+ * @throws {FetchError} If the response is not OK or cannot be parsed as JSON.
69
+ */
70
+ async function handleResponse(response) {
71
+ if (!response.ok) {
72
+ throw await responseError(response);
73
+ }
74
+ const json = await toJson(response);
75
+ if (json === undefined) {
76
+ throw new FetchError('Response could not be parsed as JSON.');
77
+ }
78
+ return json;
79
+ }
80
+
81
+ /**
82
+ * Maps the model option to the corresponding query parameter value for the REST-API.
83
+ * @param {string} model - The model type, either 'offline' or 'online'.
84
+ * @returns {string} The corresponding path parameter value for the REST-API.
85
+ * @throws {Error} If an unknown model type is provided.
86
+ */
87
+ function getModelPathParameter(model) {
88
+ switch (model) {
89
+ case 'offline':
90
+ return '0';
91
+ case 'online':
92
+ return '1';
93
+ default:
94
+ throw new Error(`Unknown model: ${model}`);
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Fetches JSON data from the REST-API.
100
+ * @template T
101
+ * @param {string} uri - The URI to fetch JSON data from.
102
+ * @param {Options} [options] - The options for the fetch request with some extensions.
103
+ * @returns {Promise<T>} A promise that resolves to the JSON response.
104
+ * @throws {FetchError} If the response is not successful or cannot be parsed as JSON.
105
+ */
106
+ export default function fetchRestApi(uri, options = {}) {
107
+ const { data = {}, model = 'offline', ...rest } = options;
108
+
109
+ const qs = Object.keys(data).length
110
+ ? `${uri.includes('?') ? '&' : '?'}${`format=json&json=${encodeURIComponent(JSON.stringify(data))}`}`
111
+ : '';
112
+
113
+ return fetch(`/rest-api/1/${getModelPathParameter(model)}/${uri}${qs}`, rest)
114
+ .then(handleResponse)
115
+ .catch((error) => {
116
+ // Re-throw FetchError as-is, convert other errors
117
+ if (error instanceof FetchError) {
118
+ return Promise.reject(error);
119
+ }
120
+
121
+ return Promise.reject(new FetchError(error.message, { status: error.status || 500 }));
122
+ });
123
+ }
package/utils/index.js CHANGED
@@ -7,3 +7,4 @@ export { default as triggerInput } from './triggerInput';
7
7
  export { default as createI18n } from './createI18n';
8
8
  export { default as getAppProps } from './getAppProps';
9
9
  export { default as getAppContext } from './getAppContext';
10
+ export { default as fetchRestApi } from './fetchRestApi';