@qoretechnologies/reqraft 0.10.53 → 0.10.54-pr.115.ge2d364c
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/.claude/CLAUDE.md +25 -0
- package/.tasks/INDEX.md +1 -0
- package/.tasks/STORY_NETWORK_ISOLATION.md +98 -0
- package/README.md +1 -1
- package/design/FORM_ENGINE_EXPRESSIONS.md +110 -0
- package/design/RENDER_EXPRESSION_TRANSPORT.md +18 -0
- package/dist/components/dpqlEditor/dpqlProbe.d.ts +37 -0
- package/dist/components/dpqlEditor/dpqlProbe.d.ts.map +1 -0
- package/dist/components/dpqlEditor/dpqlProbe.js +203 -0
- package/dist/components/dpqlEditor/dpqlProbe.js.map +1 -0
- package/dist/components/dpqlEditor/index.d.ts +4 -0
- package/dist/components/dpqlEditor/index.d.ts.map +1 -1
- package/dist/components/dpqlEditor/index.js +5 -1
- package/dist/components/dpqlEditor/index.js.map +1 -1
- package/dist/components/dpqlEditor/types.d.ts +29 -2
- package/dist/components/dpqlEditor/types.d.ts.map +1 -1
- package/dist/components/dpqlEditor/useDpqlProbe.d.ts +4 -0
- package/dist/components/dpqlEditor/useDpqlProbe.d.ts.map +1 -0
- package/dist/components/dpqlEditor/useDpqlProbe.js +29 -0
- package/dist/components/dpqlEditor/useDpqlProbe.js.map +1 -0
- package/dist/components/dpqlEditor/useDpqlSession.d.ts +5 -2
- package/dist/components/dpqlEditor/useDpqlSession.d.ts.map +1 -1
- package/dist/components/dpqlEditor/useDpqlSession.js +8 -2
- package/dist/components/dpqlEditor/useDpqlSession.js.map +1 -1
- package/dist/components/form/engine/CompactRow.d.ts.map +1 -1
- package/dist/components/form/engine/CompactRow.js +186 -21
- package/dist/components/form/engine/CompactRow.js.map +1 -1
- package/dist/components/form/engine/CompactToolbar.d.ts.map +1 -1
- package/dist/components/form/engine/CompactToolbar.js +5 -2
- package/dist/components/form/engine/CompactToolbar.js.map +1 -1
- package/dist/components/form/engine/FormEngine.d.ts +159 -11
- package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
- package/dist/components/form/engine/FormEngine.js +376 -75
- package/dist/components/form/engine/FormEngine.js.map +1 -1
- package/dist/components/form/engine/FormFieldsSkeleton.d.ts +48 -0
- package/dist/components/form/engine/FormFieldsSkeleton.d.ts.map +1 -0
- package/dist/components/form/engine/FormFieldsSkeleton.js +38 -0
- package/dist/components/form/engine/FormFieldsSkeleton.js.map +1 -0
- package/dist/components/form/engine/OptionFieldMessages.d.ts +17 -2
- package/dist/components/form/engine/OptionFieldMessages.d.ts.map +1 -1
- package/dist/components/form/engine/OptionFieldMessages.js +61 -11
- package/dist/components/form/engine/OptionFieldMessages.js.map +1 -1
- package/dist/components/form/engine/_structuredData/SchemaDataView.d.ts.map +1 -1
- package/dist/components/form/engine/_structuredData/SchemaDataView.js +7 -1
- package/dist/components/form/engine/_structuredData/SchemaDataView.js.map +1 -1
- package/dist/components/form/engine/compactToolbarContext.d.ts +1 -0
- package/dist/components/form/engine/compactToolbarContext.d.ts.map +1 -1
- package/dist/components/form/engine/readFirst.d.ts +42 -1
- package/dist/components/form/engine/readFirst.d.ts.map +1 -1
- package/dist/components/form/engine/readFirst.js +74 -7
- package/dist/components/form/engine/readFirst.js.map +1 -1
- package/dist/components/form/engine/rendererTypes.d.ts +13 -0
- package/dist/components/form/engine/rendererTypes.d.ts.map +1 -1
- package/dist/components/form/engine/rendererTypes.js.map +1 -1
- package/dist/components/form/engine/rowMenuContext.d.ts +78 -0
- package/dist/components/form/engine/rowMenuContext.d.ts.map +1 -0
- package/dist/components/form/engine/rowMenuContext.js +103 -0
- package/dist/components/form/engine/rowMenuContext.js.map +1 -0
- package/dist/components/form/engine/typeChoices.d.ts +37 -0
- package/dist/components/form/engine/typeChoices.d.ts.map +1 -0
- package/dist/components/form/engine/typeChoices.js +25 -0
- package/dist/components/form/engine/typeChoices.js.map +1 -0
- package/dist/components/form/expressions/ExpressionField.d.ts +7 -1
- package/dist/components/form/expressions/ExpressionField.d.ts.map +1 -1
- package/dist/components/form/expressions/ExpressionField.js +181 -33
- package/dist/components/form/expressions/ExpressionField.js.map +1 -1
- package/dist/components/form/expressions/argumentPresence.d.ts +97 -0
- package/dist/components/form/expressions/argumentPresence.d.ts.map +1 -0
- package/dist/components/form/expressions/argumentPresence.js +203 -0
- package/dist/components/form/expressions/argumentPresence.js.map +1 -0
- package/dist/components/form/expressions/builder/argumentLabel.d.ts.map +1 -1
- package/dist/components/form/expressions/builder/argumentLabel.js +9 -4
- package/dist/components/form/expressions/builder/argumentLabel.js.map +1 -1
- package/dist/components/form/expressions/builder/index.d.ts +15 -2
- package/dist/components/form/expressions/builder/index.d.ts.map +1 -1
- package/dist/components/form/expressions/builder/index.js +19 -57
- package/dist/components/form/expressions/builder/index.js.map +1 -1
- package/dist/components/form/expressions/dpqlMockLsp.d.ts +17 -0
- package/dist/components/form/expressions/dpqlMockLsp.d.ts.map +1 -1
- package/dist/components/form/expressions/dpqlMockLsp.js +75 -9
- package/dist/components/form/expressions/dpqlMockLsp.js.map +1 -1
- package/dist/components/form/expressions/renderExpressionToText.d.ts.map +1 -1
- package/dist/components/form/expressions/renderExpressionToText.js +94 -19
- package/dist/components/form/expressions/renderExpressionToText.js.map +1 -1
- package/dist/components/form/fields/Field.d.ts.map +1 -1
- package/dist/components/form/fields/Field.js +3 -1
- package/dist/components/form/fields/Field.js.map +1 -1
- package/dist/components/form/fields/allowed-values/AllowedValues.d.ts +11 -1
- package/dist/components/form/fields/allowed-values/AllowedValues.d.ts.map +1 -1
- package/dist/components/form/fields/allowed-values/AllowedValues.js +14 -2
- package/dist/components/form/fields/allowed-values/AllowedValues.js.map +1 -1
- package/dist/components/form/fields/auto/AutoFormField.d.ts.map +1 -1
- package/dist/components/form/fields/auto/AutoFormField.js +13 -2
- package/dist/components/form/fields/auto/AutoFormField.js.map +1 -1
- package/dist/components/form/fields/rich-text/RichText.d.ts +11 -0
- package/dist/components/form/fields/rich-text/RichText.d.ts.map +1 -1
- package/dist/components/form/fields/rich-text/RichText.js +84 -5
- package/dist/components/form/fields/rich-text/RichText.js.map +1 -1
- package/dist/components/form/fields/template/ReadOnlyTemplateTag.d.ts +2 -1
- package/dist/components/form/fields/template/ReadOnlyTemplateTag.d.ts.map +1 -1
- package/dist/components/form/fields/template/ReadOnlyTemplateTag.js +3 -2
- package/dist/components/form/fields/template/ReadOnlyTemplateTag.js.map +1 -1
- package/dist/components/form/fields/template/TemplateField.d.ts +31 -1
- package/dist/components/form/fields/template/TemplateField.d.ts.map +1 -1
- package/dist/components/form/fields/template/TemplateField.js +507 -40
- package/dist/components/form/fields/template/TemplateField.js.map +1 -1
- package/dist/components/form/index.d.ts +1 -0
- package/dist/components/form/index.d.ts.map +1 -1
- package/dist/components/form/index.js +1 -0
- package/dist/components/form/index.js.map +1 -1
- package/dist/components/smartEditor/__fixtures__/mockLspServer.d.ts +1 -0
- package/dist/components/smartEditor/__fixtures__/mockLspServer.d.ts.map +1 -1
- package/dist/components/smartEditor/__fixtures__/mockLspServer.js +6 -1
- package/dist/components/smartEditor/__fixtures__/mockLspServer.js.map +1 -1
- package/dist/helpers/dpqlDetection.d.ts +58 -0
- package/dist/helpers/dpqlDetection.d.ts.map +1 -0
- package/dist/helpers/dpqlDetection.js +164 -0
- package/dist/helpers/dpqlDetection.js.map +1 -0
- package/dist/helpers/optionUiTypes.d.ts +1 -1
- package/dist/helpers/options.d.ts +37 -0
- package/dist/helpers/options.d.ts.map +1 -1
- package/dist/helpers/options.js +78 -1
- package/dist/helpers/options.js.map +1 -1
- package/dist/helpers/templateItems.d.ts +47 -0
- package/dist/helpers/templateItems.d.ts.map +1 -0
- package/dist/helpers/templateItems.js +88 -0
- package/dist/helpers/templateItems.js.map +1 -0
- package/dist/helpers/templates.d.ts +21 -0
- package/dist/helpers/templates.d.ts.map +1 -1
- package/dist/helpers/templates.js +49 -3
- package/dist/helpers/templates.js.map +1 -1
- package/dist/helpers/validations.d.ts.map +1 -1
- package/dist/helpers/validations.js +56 -9
- package/dist/helpers/validations.js.map +1 -1
- package/dist/hooks/useArgSchema.d.ts +2 -1
- package/dist/hooks/useArgSchema.d.ts.map +1 -1
- package/dist/hooks/useFetch/useFetch.d.ts +3 -1
- package/dist/hooks/useFetch/useFetch.d.ts.map +1 -1
- package/dist/hooks/useFetch/useFetch.js +47 -9
- package/dist/hooks/useFetch/useFetch.js.map +1 -1
- package/dist/hooks/useTemplates.d.ts +20 -0
- package/dist/hooks/useTemplates.d.ts.map +1 -1
- package/dist/hooks/useTemplates.js +34 -2
- package/dist/hooks/useTemplates.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/stories/storyNetwork.d.ts +36 -0
- package/dist/stories/storyNetwork.d.ts.map +1 -0
- package/dist/stories/storyNetwork.js +82 -0
- package/dist/stories/storyNetwork.js.map +1 -0
- package/dist/utils/fetch.d.ts +151 -1
- package/dist/utils/fetch.d.ts.map +1 -1
- package/dist/utils/fetch.js +256 -44
- package/dist/utils/fetch.js.map +1 -1
- package/dist/utils/lspClient.d.ts +6 -0
- package/dist/utils/lspClient.d.ts.map +1 -1
- package/dist/utils/lspClient.js +62 -11
- package/dist/utils/lspClient.js.map +1 -1
- package/dist/utils/websocket.d.ts +6 -0
- package/dist/utils/websocket.d.ts.map +1 -1
- package/dist/utils/websocket.js +15 -7
- package/dist/utils/websocket.js.map +1 -1
- package/package.json +2 -2
- package/src/components/dpqlEditor/DpqlEditor.stories.tsx +0 -2
- package/src/components/dpqlEditor/dpqlProbe.ts +165 -0
- package/src/components/dpqlEditor/index.ts +4 -0
- package/src/components/dpqlEditor/types.ts +26 -2
- package/src/components/dpqlEditor/useDpqlProbe.ts +33 -0
- package/src/components/dpqlEditor/useDpqlSession.ts +20 -4
- package/src/components/form/engine/CompactRow.tsx +231 -11
- package/src/components/form/engine/CompactToolbar.tsx +10 -3
- package/src/components/form/engine/FormEngine.stories.tsx +331 -30
- package/src/components/form/engine/FormEngine.tsx +504 -87
- package/src/components/form/engine/FormFieldsSkeleton.tsx +76 -0
- package/src/components/form/engine/OptionFieldMessages.tsx +89 -11
- package/src/components/form/engine/_structuredData/SchemaDataView.tsx +11 -0
- package/src/components/form/engine/compactToolbarContext.ts +1 -1
- package/src/components/form/engine/readFirst.ts +86 -7
- package/src/components/form/engine/rendererTypes.ts +12 -0
- package/src/components/form/engine/rowMenuContext.ts +159 -0
- package/src/components/form/engine/typeChoices.ts +55 -0
- package/src/components/form/expressions/ExpressionField.stories.tsx +828 -31
- package/src/components/form/expressions/ExpressionField.tsx +234 -29
- package/src/components/form/expressions/argumentPresence.ts +200 -0
- package/src/components/form/expressions/builder/argumentLabel.tsx +10 -4
- package/src/components/form/expressions/builder/index.tsx +31 -75
- package/src/components/form/expressions/dpqlMockLsp.ts +81 -1
- package/src/components/form/expressions/renderExpressionToText.ts +106 -17
- package/src/components/form/fields/Field.tsx +3 -2
- package/src/components/form/fields/allowed-values/AllowedValues.tsx +13 -2
- package/src/components/form/fields/auto/AutoFormField.tsx +12 -2
- package/src/components/form/fields/rich-text/RichText.tsx +112 -19
- package/src/components/form/fields/template/ReadOnlyTemplateTag.tsx +4 -2
- package/src/components/form/fields/template/TemplateField.tsx +667 -95
- package/src/components/form/index.tsx +1 -0
- package/src/components/log/Log.stories.tsx +2 -1
- package/src/components/smartEditor/__fixtures__/mockLspServer.ts +6 -1
- package/src/helpers/dpqlDetection.ts +209 -0
- package/src/helpers/options.ts +70 -0
- package/src/helpers/templateItems.ts +81 -0
- package/src/helpers/templates.ts +51 -2
- package/src/helpers/validations.ts +59 -9
- package/src/hooks/useFetch/useFetch.stories.tsx +6 -5
- package/src/hooks/useFetch/useFetch.tsx +53 -7
- package/src/hooks/useStorage/useStorage.stories.tsx +2 -1
- package/src/hooks/useTemplates.ts +41 -0
- package/src/hooks/useWebSocket/useWebsocket.stories.tsx +2 -1
- package/src/index.tsx +14 -1
- package/src/stores/currentUser/currentUser.stories.tsx +2 -1
- package/src/stories/storyNetwork.ts +92 -0
- package/src/utils/fetch.ts +311 -21
- package/src/utils/lspClient.ts +68 -11
- package/src/utils/websocket.ts +20 -9
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { QueryClient, useQueryClient } from '@tanstack/react-query';
|
|
1
2
|
import { useCallback, useMemo, useState } from 'react';
|
|
2
3
|
import { useEffectOnce } from 'react-use';
|
|
3
4
|
import { useContextSelector } from 'use-context-selector';
|
|
4
5
|
import { FetchContext } from '../../contexts/FetchContext';
|
|
5
|
-
import {
|
|
6
|
+
import { ReqraftQueryClient } from '../../providers/ReqraftProvider';
|
|
7
|
+
import { IReqraftQueryConfig, reqraftCacheKey } from '../../utils/fetch';
|
|
6
8
|
|
|
7
9
|
export interface IReqraftUseFetch<T> {
|
|
8
10
|
data: T | undefined;
|
|
@@ -41,8 +43,40 @@ export function useFetch<T>({
|
|
|
41
43
|
}
|
|
42
44
|
});
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
/* An answer we ALREADY HAVE is not a loading state.
|
|
47
|
+
`loading` started at `loadOnMount` unconditionally, so every consumer that
|
|
48
|
+
mounted reported "loading" for at least one async turn even when the
|
|
49
|
+
response was sitting in the cache — and `FormEngine` renders its
|
|
50
|
+
`options-loading-skeleton` whenever any of its sources says that. So each
|
|
51
|
+
form appearing on a page flashed a skeleton whether or not anything was
|
|
52
|
+
actually fetched, which is what the repeated skeletons on an IDE page are:
|
|
53
|
+
measured on a fully warm SECOND visit to one page, three skeleton waves
|
|
54
|
+
against two requests, the last wave with no request anywhere near it.
|
|
55
|
+
Seeded from the query cache instead, so a resource already in hand renders
|
|
56
|
+
immediately. */
|
|
57
|
+
/* The provider is OPTIONAL here. `useQueryClient` THROWS when no
|
|
58
|
+
`QueryClientProvider` is above it, and plenty of consumers mount a form
|
|
59
|
+
with only a `FetchContext` — requiring one turned a rendering optimisation
|
|
60
|
+
into a hard dependency and took 131 tests down with it. `useContext` runs
|
|
61
|
+
before that throw, so hook order is unaffected and catching is safe. The
|
|
62
|
+
fallback is the same client `query` itself defaults to, so a cache hit is
|
|
63
|
+
found either way. */
|
|
64
|
+
let queryClient: QueryClient;
|
|
65
|
+
try {
|
|
66
|
+
queryClient = useQueryClient();
|
|
67
|
+
} catch {
|
|
68
|
+
queryClient = ReqraftQueryClient;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const cached = queryClient.getQueryData([
|
|
72
|
+
reqraftCacheKey({ url, method, body, noApiPrefix }),
|
|
73
|
+
]) as { data?: T; ok?: boolean } | undefined;
|
|
74
|
+
const hasCachedAnswer = !!cached && cached.ok !== false && cached.data !== undefined;
|
|
75
|
+
|
|
76
|
+
const [loading, setLoading] = useState(!!loadOnMount && !hasCachedAnswer);
|
|
77
|
+
const [data, setData] = useState<T | undefined>(
|
|
78
|
+
hasCachedAnswer ? (cached?.data as T) : defaultData
|
|
79
|
+
);
|
|
46
80
|
const [response, setResponse] = useState<Response | undefined>();
|
|
47
81
|
const [error, setError] = useState<Error | undefined>();
|
|
48
82
|
const [errorData, setErrorData] = useState<any>();
|
|
@@ -51,13 +85,23 @@ export function useFetch<T>({
|
|
|
51
85
|
async ({
|
|
52
86
|
body: customBody,
|
|
53
87
|
mergeBodies,
|
|
54
|
-
|
|
55
|
-
|
|
88
|
+
silent,
|
|
89
|
+
}: {
|
|
90
|
+
body?: Record<string | number, any>;
|
|
91
|
+
mergeBodies?: boolean;
|
|
92
|
+
/** Revalidate without announcing a loading state — see the note above. */
|
|
93
|
+
silent?: boolean;
|
|
94
|
+
} = {}) => {
|
|
95
|
+
if (!silent) {
|
|
96
|
+
setLoading(true);
|
|
97
|
+
}
|
|
56
98
|
|
|
57
99
|
const _body = mergeBodies ? { ...body, ...customBody } : customBody || body;
|
|
58
100
|
const response = await query<T>({ url, body: _body, cache, noApiPrefix });
|
|
59
101
|
|
|
60
|
-
|
|
102
|
+
if (!silent) {
|
|
103
|
+
setLoading(false);
|
|
104
|
+
}
|
|
61
105
|
|
|
62
106
|
if (response.ok) {
|
|
63
107
|
setError(undefined);
|
|
@@ -75,7 +119,9 @@ export function useFetch<T>({
|
|
|
75
119
|
|
|
76
120
|
useEffectOnce(() => {
|
|
77
121
|
if (loadOnMount) {
|
|
78
|
-
|
|
122
|
+
// Still revalidates when the entry is cached — `query` honours its own
|
|
123
|
+
// staleness — but silently, so a cached answer never flashes a skeleton.
|
|
124
|
+
load({ silent: hasCachedAnswer });
|
|
79
125
|
}
|
|
80
126
|
});
|
|
81
127
|
|
|
@@ -4,6 +4,7 @@ import { useRef } from 'react';
|
|
|
4
4
|
import { fireEvent, within } from 'storybook/test';
|
|
5
5
|
import { storiesStorageMock } from '../../../__tests__/ mock';
|
|
6
6
|
import { testsWaitForText } from '../../../__tests__/utils';
|
|
7
|
+
import { storyApiUrl } from '../../stories/storyNetwork';
|
|
7
8
|
import { StoryMeta } from '../../types';
|
|
8
9
|
import { useReqraftStorage } from './useStorage';
|
|
9
10
|
|
|
@@ -44,7 +45,7 @@ export const DefaultValue: Story = {
|
|
|
44
45
|
},
|
|
45
46
|
mockData: [
|
|
46
47
|
{
|
|
47
|
-
url: '
|
|
48
|
+
url: storyApiUrl('users?action=current'),
|
|
48
49
|
method: 'GET',
|
|
49
50
|
status: 200,
|
|
50
51
|
response: {},
|
|
@@ -13,6 +13,39 @@ export interface IUseTemplates {
|
|
|
13
13
|
|
|
14
14
|
export const TemplatesCache = new Map<string, IReqoreFormTemplates>();
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Hand this hook a `getContextData` payload the host has already fetched.
|
|
18
|
+
*
|
|
19
|
+
* The host and this library keep SEPARATE caches over one API, so a page that
|
|
20
|
+
* has already paid for the context data pays again here — and, worse, waits for
|
|
21
|
+
* it: `FormEngine` will not render a field before its templates exist, because
|
|
22
|
+
* an any-typed field with no templates falls back to the type picker and asks
|
|
23
|
+
* the author to choose `string`/`int`/`hash` for a value they already captured.
|
|
24
|
+
*
|
|
25
|
+
* Measured on the live alert rule: three form sections were placeholders from
|
|
26
|
+
* 1459ms to 2039ms and, when they cleared, 193 elements that had been on screen
|
|
27
|
+
* for ~590ms dropped 67px, because the placeholder is not the height of the
|
|
28
|
+
* form it stands in for. Seeded, the form has its templates on its FIRST render
|
|
29
|
+
* and there is no placeholder to swap out at all.
|
|
30
|
+
*
|
|
31
|
+
* Same shape as `seedQueryCache` for the type catalogue, and deliberately
|
|
32
|
+
* first-write-wins: a real fetch that has already answered is not overwritten.
|
|
33
|
+
*/
|
|
34
|
+
export const seedTemplatesCache = (
|
|
35
|
+
interfaceContext: string,
|
|
36
|
+
payload: ITemplatesPayload
|
|
37
|
+
): void => {
|
|
38
|
+
if (!interfaceContext || TemplatesCache.has(interfaceContext)) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const templates = buildTemplates(payload);
|
|
43
|
+
|
|
44
|
+
if (templates) {
|
|
45
|
+
TemplatesCache.set(interfaceContext, templates);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
16
49
|
/**
|
|
17
50
|
* Templates for form fields. Ported from qorus-ide `useTemplates` with one
|
|
18
51
|
* deliberate difference: fetching `system/getContextData` is **opt-in** via
|
|
@@ -29,6 +62,14 @@ export const useTemplates = (
|
|
|
29
62
|
|
|
30
63
|
const globalTemplates = useAsyncRetry(async () => {
|
|
31
64
|
if (shouldFetch) {
|
|
65
|
+
/* Already answered — by a previous mount or by the host seeding it. The
|
|
66
|
+
cache was consulted for `loading` and for the value but not here, so a
|
|
67
|
+
seeded page still issued the request it had been handed the answer to. */
|
|
68
|
+
const cached = TemplatesCache.get(interfaceContext!);
|
|
69
|
+
if (cached) {
|
|
70
|
+
return cached;
|
|
71
|
+
}
|
|
72
|
+
|
|
32
73
|
const serverTemplates = await query<ITemplatesPayload>({
|
|
33
74
|
url: `system/getContextData?interface_context=${interfaceContext}`,
|
|
34
75
|
method: 'PUT',
|
|
@@ -6,6 +6,7 @@ import { Server } from 'mock-socket';
|
|
|
6
6
|
import { useEffect, useState } from 'react';
|
|
7
7
|
import { useMount } from 'react-use';
|
|
8
8
|
import { sleep, testsClickButton, testsWaitForText } from '../../../__tests__/utils';
|
|
9
|
+
import { storySocketUrl } from '../../stories/storyNetwork';
|
|
9
10
|
import { StoryMeta } from '../../types';
|
|
10
11
|
import { ReqraftWebSocketsManager } from '../../utils/websocket';
|
|
11
12
|
import { IUseReqraftWebSocketOptions, useReqraftWebSocket } from './useWebSocket';
|
|
@@ -40,7 +41,7 @@ const CompWithHook = (args: IUseReqraftWebSocketOptions) => {
|
|
|
40
41
|
const meta = {
|
|
41
42
|
title: 'Hooks/useWebSocket',
|
|
42
43
|
async beforeEach({ parameters }: { parameters: Record<string, any> }) {
|
|
43
|
-
const url =
|
|
44
|
+
const url = storySocketUrl('log-test');
|
|
44
45
|
let server = new Server(url);
|
|
45
46
|
let killTimeout: NodeJS.Timeout;
|
|
46
47
|
/** Set while the server is dead: every connection that lands is closed. */
|
package/src/index.tsx
CHANGED
|
@@ -36,7 +36,20 @@ export type {
|
|
|
36
36
|
ICurrentUser,
|
|
37
37
|
ICurrentUserStore,
|
|
38
38
|
} from './stores/currentUser/currentUser';
|
|
39
|
-
export {
|
|
39
|
+
export {
|
|
40
|
+
fetchConfig,
|
|
41
|
+
query,
|
|
42
|
+
queryRaw,
|
|
43
|
+
reqraftApiPath,
|
|
44
|
+
reqraftCacheKey,
|
|
45
|
+
setupFetch,
|
|
46
|
+
} from './utils/fetch';
|
|
47
|
+
export type {
|
|
48
|
+
IReqraftFetchConfig,
|
|
49
|
+
IReqraftFetchResponse,
|
|
50
|
+
IReqraftQueryConfig,
|
|
51
|
+
IReqraftRawResponse,
|
|
52
|
+
} from './utils/fetch';
|
|
40
53
|
export * from './utils/websocket';
|
|
41
54
|
export { ReqraftLspClient } from './utils/lspClient';
|
|
42
55
|
export type { IReqraftLspClientOptions } from './utils/lspClient';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ReqoreP, ReqoreSpinner, ReqoreTree } from '@qoretechnologies/reqore';
|
|
2
2
|
import { StoryObj } from '@storybook/react-vite';
|
|
3
3
|
import { testsWaitForText } from '../../../__tests__/utils';
|
|
4
|
+
import { storyApiUrl } from '../../stories/storyNetwork';
|
|
4
5
|
import { StoryMeta } from '../../types';
|
|
5
6
|
import { currentUserStore } from './currentUser';
|
|
6
7
|
|
|
@@ -9,7 +10,7 @@ const meta = {
|
|
|
9
10
|
parameters: {
|
|
10
11
|
mockData: [
|
|
11
12
|
{
|
|
12
|
-
url: '
|
|
13
|
+
url: storyApiUrl('users?action=current'),
|
|
13
14
|
method: 'GET',
|
|
14
15
|
status: 200,
|
|
15
16
|
response: {
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Copyright 2026 Qore Technologies, s.r.o.
|
|
2
|
+
// Where a story's network goes: the instance its Reqraft points at, the URLs
|
|
3
|
+
// its mocks must intercept, and the requests every story is mocked for.
|
|
4
|
+
//
|
|
5
|
+
// `query()` and `ReqraftWebSocket` build absolute URLs from the configured
|
|
6
|
+
// instance, and both mock layers match on the host as well as the path —
|
|
7
|
+
// storybook-addon-mock keys on `host + pathname`, mock-socket on the URL minus
|
|
8
|
+
// its query. A mock written against a hard-coded host stops matching the moment
|
|
9
|
+
// the instance is overridden, and the request goes out to the real network
|
|
10
|
+
// instead, silently. Build mock URLs with the helpers below.
|
|
11
|
+
import { defaultQorusTypes } from '../hooks/useQorusTypes';
|
|
12
|
+
import { buildReqraftApiUrl } from '../utils/fetch';
|
|
13
|
+
import { buildReqraftSocketUrl } from '../utils/websocket';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* What storybook-addon-mock accepts as a response. Anything else — a bare
|
|
17
|
+
* number or string — fails the addon's validation and the entry is kept but
|
|
18
|
+
* never matched, so the request goes out to the network with no warning.
|
|
19
|
+
*/
|
|
20
|
+
export type TStoryMockResponse = object | ((request: unknown) => unknown);
|
|
21
|
+
|
|
22
|
+
export interface IStoryMockRequest {
|
|
23
|
+
url: string;
|
|
24
|
+
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
25
|
+
status: number;
|
|
26
|
+
response: TStoryMockResponse;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The instance every story's Reqraft is initialized with. Override it with
|
|
31
|
+
* `REACT_APP_QORUS_INSTANCE` (or `QORUS_INSTANCE`) to run live stories against
|
|
32
|
+
* your own Qorus; mocks built from it follow the override.
|
|
33
|
+
*/
|
|
34
|
+
export const STORY_QORUS_INSTANCE =
|
|
35
|
+
process.env.REACT_APP_QORUS_INSTANCE || 'https://hq.qoretechnologies.com:8092/';
|
|
36
|
+
|
|
37
|
+
/** The URL `query()` requests for `path`. */
|
|
38
|
+
export const storyApiUrl = (path: string): string =>
|
|
39
|
+
buildReqraftApiUrl(STORY_QORUS_INSTANCE, path);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The URL `ReqraftWebSocket` dials for `path`, token omitted — mock-socket
|
|
43
|
+
* matches servers without the query string.
|
|
44
|
+
*/
|
|
45
|
+
export const storySocketUrl = (path: string): string =>
|
|
46
|
+
buildReqraftSocketUrl(STORY_QORUS_INSTANCE, path);
|
|
47
|
+
|
|
48
|
+
/** A GET of `path` answered with `response`. */
|
|
49
|
+
const mockGet = (path: string, response: TStoryMockResponse): IStoryMockRequest[] => [
|
|
50
|
+
{ url: storyApiUrl(path), method: 'GET', status: 200, response },
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The type catalogue, pinned to the built-in list. `useQorusTypes` uses a
|
|
55
|
+
* reachable server's list *instead of* the built-in one, so an unmocked story
|
|
56
|
+
* renders whichever labels that server happens to have — and every FormEngine
|
|
57
|
+
* shows its loading skeleton until the request settles, which against an
|
|
58
|
+
* unreachable instance is never.
|
|
59
|
+
*/
|
|
60
|
+
const QORUS_TYPE_INFO_MOCK_DATA = mockGet('system/qorus-type-info', defaultQorusTypes);
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The system expression catalogue, empty. `useExpressions` fetches it whenever
|
|
64
|
+
* functions are allowed — even when the story passes its own list, which is
|
|
65
|
+
* merged in as extras — and TemplateField shows a skeleton in place of "Use
|
|
66
|
+
* Expression" until it arrives. Stories that need functions provide them
|
|
67
|
+
* (`expressions`, or their own `mockData` for this URL).
|
|
68
|
+
*/
|
|
69
|
+
const EXPRESSIONS_CATALOGUE_MOCK_DATA = mockGet('system?action=expressions&context=ui', []);
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* `ReqraftWebSocket`'s server probe, which every reconnect attempt awaits
|
|
73
|
+
* before it dials. Unanswered, no attempt is ever made — a mock socket server
|
|
74
|
+
* that comes back is never reconnected to, and one that stays down is never
|
|
75
|
+
* given up on. The probe only needs an answer; its body is not read.
|
|
76
|
+
*/
|
|
77
|
+
const SERVER_STATUS_MOCK_DATA = mockGet('system/pid', {});
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Requests components make on their own, whatever the story is about —
|
|
81
|
+
* mounting a FormEngine fetches the type catalogue; allowing functions fetches
|
|
82
|
+
* the expression catalogue; a reconnecting socket probes the server.
|
|
83
|
+
* Registered for every story as storybook-addon-mock's `globalMockData`
|
|
84
|
+
* (`.storybook/preview.tsx`, and mirrored for the Vitest run in
|
|
85
|
+
* `.storybook/vitest.setup.ts`); a story's own `mockData` entry for the same
|
|
86
|
+
* URL takes precedence.
|
|
87
|
+
*/
|
|
88
|
+
export const GLOBAL_STORY_MOCK_DATA: IStoryMockRequest[] = [
|
|
89
|
+
...QORUS_TYPE_INFO_MOCK_DATA,
|
|
90
|
+
...EXPRESSIONS_CATALOGUE_MOCK_DATA,
|
|
91
|
+
...SERVER_STATUS_MOCK_DATA,
|
|
92
|
+
];
|