@qoretechnologies/reqraft 0.10.53 → 0.10.54-pr.115.gaf0d7a7
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/FORM_ENGINE_FIELD_MIGRATION.md +155 -1
- package/design/RENDER_EXPRESSION_TRANSPORT.md +76 -10
- package/design/SMART_EDITOR_UX.md +21 -0
- package/dist/components/dpqlEditor/DpqlEditor.d.ts.map +1 -1
- package/dist/components/dpqlEditor/DpqlEditor.js +3 -3
- package/dist/components/dpqlEditor/DpqlEditor.js.map +1 -1
- package/dist/components/dpqlEditor/dpqlHelpers.d.ts +6 -0
- package/dist/components/dpqlEditor/dpqlHelpers.d.ts.map +1 -1
- package/dist/components/dpqlEditor/dpqlHelpers.js +21 -2
- package/dist/components/dpqlEditor/dpqlHelpers.js.map +1 -1
- 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/dpqlTags.d.ts +8 -0
- package/dist/components/dpqlEditor/dpqlTags.d.ts.map +1 -1
- package/dist/components/dpqlEditor/dpqlTags.js +5 -1
- package/dist/components/dpqlEditor/dpqlTags.js.map +1 -1
- 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 +42 -2
- package/dist/components/dpqlEditor/types.d.ts.map +1 -1
- package/dist/components/dpqlEditor/types.js +0 -4
- package/dist/components/dpqlEditor/types.js.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 +192 -45
- 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 +160 -12
- package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
- package/dist/components/form/engine/FormEngine.js +380 -78
- 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 +73 -12
- 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 +42 -0
- package/dist/components/form/engine/typeChoices.d.ts.map +1 -0
- package/dist/components/form/engine/typeChoices.js +31 -0
- package/dist/components/form/engine/typeChoices.js.map +1 -0
- package/dist/components/form/expressions/DpqlRendering.d.ts +26 -0
- package/dist/components/form/expressions/DpqlRendering.d.ts.map +1 -0
- package/dist/components/form/expressions/DpqlRendering.js +53 -0
- package/dist/components/form/expressions/DpqlRendering.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 +203 -45
- 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 +20 -58
- package/dist/components/form/expressions/builder/index.js.map +1 -1
- package/dist/components/form/expressions/builder/renderTemplate.d.ts +4 -1
- package/dist/components/form/expressions/builder/renderTemplate.d.ts.map +1 -1
- package/dist/components/form/expressions/builder/renderTemplate.js +10 -67
- package/dist/components/form/expressions/builder/renderTemplate.js.map +1 -1
- package/dist/components/form/expressions/dpqlMockLanguage.d.ts +76 -0
- package/dist/components/form/expressions/dpqlMockLanguage.d.ts.map +1 -0
- package/dist/components/form/expressions/dpqlMockLanguage.js +525 -0
- package/dist/components/form/expressions/dpqlMockLanguage.js.map +1 -0
- package/dist/components/form/expressions/dpqlMockLsp.d.ts +31 -2
- package/dist/components/form/expressions/dpqlMockLsp.d.ts.map +1 -1
- package/dist/components/form/expressions/dpqlMockLsp.js +86 -80
- package/dist/components/form/expressions/dpqlMockLsp.js.map +1 -1
- package/dist/components/form/expressions/mockExpressions.d.ts.map +1 -1
- package/dist/components/form/expressions/mockExpressions.js +8 -4
- package/dist/components/form/expressions/mockExpressions.js.map +1 -1
- package/dist/components/form/expressions/renderExpressionToText.d.ts +1 -1
- package/dist/components/form/expressions/renderExpressionToText.d.ts.map +1 -1
- package/dist/components/form/expressions/renderExpressionToText.js +95 -19
- package/dist/components/form/expressions/renderExpressionToText.js.map +1 -1
- package/dist/components/form/expressions/types.d.ts +7 -0
- package/dist/components/form/expressions/types.d.ts.map +1 -1
- package/dist/components/form/expressions/useRenderExpression.d.ts +7 -16
- package/dist/components/form/expressions/useRenderExpression.d.ts.map +1 -1
- package/dist/components/form/expressions/useRenderExpression.js +50 -126
- package/dist/components/form/expressions/useRenderExpression.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 +91 -28
- package/dist/components/form/fields/auto/AutoFormField.js.map +1 -1
- package/dist/components/form/fields/date/Date.d.ts +1 -1
- package/dist/components/form/fields/date/Date.d.ts.map +1 -1
- package/dist/components/form/fields/date/Date.js +16 -2
- package/dist/components/form/fields/date/Date.js.map +1 -1
- package/dist/components/form/fields/long-string/LongString.d.ts.map +1 -1
- package/dist/components/form/fields/long-string/LongString.js +14 -2
- package/dist/components/form/fields/long-string/LongString.js.map +1 -1
- package/dist/components/form/fields/rich-text/RichText.d.ts +35 -2
- package/dist/components/form/fields/rich-text/RichText.d.ts.map +1 -1
- package/dist/components/form/fields/rich-text/RichText.js +153 -12
- 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 +34 -2
- package/dist/components/form/fields/template/TemplateField.d.ts.map +1 -1
- package/dist/components/form/fields/template/TemplateField.js +604 -58
- 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/SmartEditor.d.ts.map +1 -1
- package/dist/components/smartEditor/SmartEditor.js +29 -8
- package/dist/components/smartEditor/SmartEditor.js.map +1 -1
- package/dist/components/smartEditor/__fixtures__/mockLspServer.d.ts +8 -0
- package/dist/components/smartEditor/__fixtures__/mockLspServer.d.ts.map +1 -1
- package/dist/components/smartEditor/__fixtures__/mockLspServer.js +53 -10
- package/dist/components/smartEditor/__fixtures__/mockLspServer.js.map +1 -1
- package/dist/components/smartEditor/useLspSession.d.ts +5 -1
- package/dist/components/smartEditor/useLspSession.d.ts.map +1 -1
- package/dist/components/smartEditor/useLspSession.js +16 -2
- package/dist/components/smartEditor/useLspSession.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 +19 -3
- package/dist/helpers/optionUiTypes.d.ts.map +1 -1
- package/dist/helpers/optionUiTypes.js +20 -1
- package/dist/helpers/optionUiTypes.js.map +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/templateText.d.ts +37 -0
- package/dist/helpers/templateText.d.ts.map +1 -0
- package/dist/helpers/templateText.js +64 -0
- package/dist/helpers/templateText.js.map +1 -0
- package/dist/helpers/templates.d.ts +19 -11
- package/dist/helpers/templates.d.ts.map +1 -1
- package/dist/helpers/templates.js +46 -29
- 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 +3 -119
- package/src/components/dpqlEditor/DpqlEditor.tsx +5 -2
- package/src/components/dpqlEditor/dpqlHelpers.ts +20 -1
- package/src/components/dpqlEditor/dpqlProbe.ts +165 -0
- package/src/components/dpqlEditor/dpqlTags.ts +13 -0
- package/src/components/dpqlEditor/index.ts +4 -0
- package/src/components/dpqlEditor/types.ts +38 -2
- package/src/components/dpqlEditor/useDpqlProbe.ts +33 -0
- package/src/components/dpqlEditor/useDpqlSession.ts +20 -4
- package/src/components/form/engine/CompactRow.tsx +240 -51
- package/src/components/form/engine/CompactToolbar.tsx +10 -3
- package/src/components/form/engine/FormEngine.stories.tsx +431 -124
- package/src/components/form/engine/FormEngine.tsx +508 -90
- package/src/components/form/engine/FormFieldsSkeleton.tsx +76 -0
- package/src/components/form/engine/OptionFieldMessages.tsx +103 -12
- 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 +61 -0
- package/src/components/form/expressions/DpqlRendering.tsx +85 -0
- package/src/components/form/expressions/ExpressionField.stories.tsx +826 -48
- package/src/components/form/expressions/ExpressionField.tsx +266 -54
- package/src/components/form/expressions/argumentPresence.ts +200 -0
- package/src/components/form/expressions/builder/ExpressionBuilder.stories.tsx +31 -14
- package/src/components/form/expressions/builder/argumentLabel.tsx +10 -4
- package/src/components/form/expressions/builder/index.tsx +39 -77
- package/src/components/form/expressions/builder/renderTemplate.tsx +23 -56
- package/src/components/form/expressions/dpqlMockLanguage.ts +565 -0
- package/src/components/form/expressions/dpqlMockLsp.ts +109 -83
- package/src/components/form/expressions/mockExpressions.ts +8 -4
- package/src/components/form/expressions/renderExpressionToText.ts +112 -22
- package/src/components/form/expressions/types.ts +7 -0
- package/src/components/form/expressions/useRenderExpression.ts +43 -127
- 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.stories.tsx +85 -18
- package/src/components/form/fields/auto/AutoFormField.tsx +90 -20
- package/src/components/form/fields/date/Date.tsx +17 -1
- package/src/components/form/fields/long-string/LongString.tsx +15 -3
- package/src/components/form/fields/rich-text/RichText.tsx +235 -30
- package/src/components/form/fields/template/ReadOnlyTemplateTag.tsx +4 -2
- package/src/components/form/fields/template/TemplateField.stories.tsx +93 -14
- package/src/components/form/fields/template/TemplateField.tsx +823 -129
- package/src/components/form/index.tsx +1 -0
- package/src/components/log/Log.stories.tsx +2 -1
- package/src/components/smartEditor/SmartEditor.tsx +41 -9
- package/src/components/smartEditor/__fixtures__/mockLspServer.ts +52 -3
- package/src/components/smartEditor/useLspSession.ts +21 -3
- package/src/helpers/dpqlDetection.ts +209 -0
- package/src/helpers/optionUiTypes.ts +22 -0
- package/src/helpers/options.ts +70 -0
- package/src/helpers/templateItems.ts +81 -0
- package/src/helpers/templateText.ts +92 -0
- package/src/helpers/templates.ts +47 -37
- 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,51 +1,36 @@
|
|
|
1
1
|
// Copyright 2026 Qore Technologies, s.r.o.
|
|
2
|
-
// The "Explain" seam. Turns an expression AST into
|
|
2
|
+
// The "Explain" seam. Turns an expression AST into its readable rendering.
|
|
3
3
|
//
|
|
4
|
-
// The
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// `LspSharedConnection`, and JSON-RPC request ids associate each response
|
|
9
|
-
//
|
|
10
|
-
//
|
|
4
|
+
// The rendering is the server's `DataProvider::renderExpression`, reached over
|
|
5
|
+
// the LSP custom method `dpql/renderExpression` (added next to `dpql/serialize`
|
|
6
|
+
// in `QorusLspWebSocketHandler.qc`). All consumers share ONE render socket: the
|
|
7
|
+
// module-level `ReqraftLspClient` below rides the per-endpoint
|
|
8
|
+
// `LspSharedConnection`, and JSON-RPC request ids associate each response with
|
|
9
|
+
// the caller — N expression builders on one form means N independent in-flight
|
|
10
|
+
// requests on one connection, no cross-wiring.
|
|
11
11
|
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
12
|
+
// There is no client-side stand-in. A Qorus form does not work without Qorus,
|
|
13
|
+
// so the only time the server was "unreachable" in practice was while the
|
|
14
|
+
// page's first connection was still opening — and racing that with a 1.5s timer
|
|
15
|
+
// replaced the real rendering with an approximation labelled "Approximate", on
|
|
16
|
+
// exactly the first Explain of every page. A rendering now waits for the
|
|
17
|
+
// connection; `null` means the server cannot render at all (it predates the
|
|
18
|
+
// method, or the connection gave up), which the caller says plainly.
|
|
15
19
|
import { nanoid } from 'nanoid';
|
|
16
|
-
import { useCallback, useEffect, useRef
|
|
20
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
17
21
|
import { ReqraftLspClient } from '../../../utils/lspClient';
|
|
18
|
-
import {
|
|
22
|
+
import { IExpressionValue } from './types';
|
|
19
23
|
|
|
24
|
+
/* The synchronous summary a collapsed row prints — see `readFirst.ts`. It is
|
|
25
|
+
not a stand-in for this rendering and is never shown in its place. */
|
|
20
26
|
export { renderExpressionToText } from './renderExpressionToText';
|
|
21
|
-
import { renderExpressionToText } from './renderExpressionToText';
|
|
22
|
-
|
|
23
|
-
// ── The shared render transport ───────────────────────────────────────
|
|
24
|
-
// One lazy module-level client for ALL `useRenderExpression` consumers.
|
|
25
|
-
// Rendering needs no document/context state, so a single connection (shared
|
|
26
|
-
// further with any open DPQL editors via `LspSharedConnection`) serves
|
|
27
|
-
// every builder on the page.
|
|
28
27
|
|
|
29
28
|
/** Coalesce rapid re-renders per consumer (live typing) — trailing edge. */
|
|
30
29
|
const RENDER_DEBOUNCE_MS = 300;
|
|
31
|
-
/** How long a render call waits for the socket before falling back. */
|
|
32
|
-
const CONNECT_WAIT_MS = 1500;
|
|
33
|
-
/** After a failed wait, fall back instantly for this long before re-waiting. */
|
|
34
|
-
const CONNECT_RETRY_COOLDOWN_MS = 10000;
|
|
35
30
|
|
|
36
31
|
let renderClient: ReqraftLspClient | null = null;
|
|
37
32
|
/** Set after a `-32601 method not found` — the server predates the method. */
|
|
38
33
|
let methodUnsupported = false;
|
|
39
|
-
let lastConnectWaitFailedAt = 0;
|
|
40
|
-
|
|
41
|
-
let lastServerRendering = false;
|
|
42
|
-
const serverRenderingListeners = new Set<(on: boolean) => void>();
|
|
43
|
-
const setLastServerRendering = (on: boolean): void => {
|
|
44
|
-
if (on !== lastServerRendering) {
|
|
45
|
-
lastServerRendering = on;
|
|
46
|
-
serverRenderingListeners.forEach((listener) => listener(on));
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
34
|
|
|
50
35
|
const getRenderClient = (): ReqraftLspClient => {
|
|
51
36
|
if (!renderClient) {
|
|
@@ -59,55 +44,24 @@ const getRenderClient = (): ReqraftLspClient => {
|
|
|
59
44
|
return renderClient;
|
|
60
45
|
};
|
|
61
46
|
|
|
62
|
-
/**
|
|
63
|
-
* Wait (briefly) for the shared connection. `connect()` is re-entrant and the
|
|
64
|
-
* underlying socket keeps its own reconnect schedule, so this only bounds how
|
|
65
|
-
* long a single render call blocks before falling back — after a failed wait
|
|
66
|
-
* we fall back instantly for a cooldown period instead of re-blocking.
|
|
67
|
-
*/
|
|
68
|
-
const waitForConnection = async (client: ReqraftLspClient): Promise<boolean> => {
|
|
69
|
-
if (client.isConnected) return true;
|
|
70
|
-
if (Date.now() - lastConnectWaitFailedAt < CONNECT_RETRY_COOLDOWN_MS) {
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
const connected = await Promise.race([
|
|
74
|
-
client.connect().then(
|
|
75
|
-
() => true,
|
|
76
|
-
() => false
|
|
77
|
-
),
|
|
78
|
-
new Promise<boolean>((resolve) => {
|
|
79
|
-
setTimeout(() => resolve(false), CONNECT_WAIT_MS);
|
|
80
|
-
}),
|
|
81
|
-
]);
|
|
82
|
-
if (!connected || !client.isConnected) {
|
|
83
|
-
lastConnectWaitFailedAt = Date.now();
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
return true;
|
|
87
|
-
};
|
|
88
|
-
|
|
89
47
|
/** One rendered expression — what `renderRich` resolves with. */
|
|
90
48
|
export interface IRenderedExpression {
|
|
91
49
|
/** The readable rendering (plain text). */
|
|
92
50
|
text: string;
|
|
93
51
|
/**
|
|
94
52
|
* The server's richtext form — template references arrive as `tag` nodes
|
|
95
|
-
* (the IDE renders them as chips).
|
|
96
|
-
* Convert with `richtextResponseToSlate` for `RichTextFormField`.
|
|
53
|
+
* (the IDE renders them as chips). Convert with `richtextResponseToSlate`.
|
|
97
54
|
*/
|
|
98
55
|
richtext: unknown | null;
|
|
99
|
-
/** `true` when the server `dpql/renderExpression` produced this result. */
|
|
100
|
-
server: boolean;
|
|
101
56
|
}
|
|
102
57
|
|
|
103
|
-
|
|
104
|
-
const renderViaServer = async (
|
|
105
|
-
value: IExpressionValue
|
|
106
|
-
): Promise<{ text: string; richtext: unknown | null } | null> => {
|
|
58
|
+
const renderViaServer = async (value: IExpressionValue): Promise<IRenderedExpression | null> => {
|
|
107
59
|
if (methodUnsupported) return null;
|
|
108
60
|
const client = getRenderClient();
|
|
109
|
-
if (!(await waitForConnection(client))) return null;
|
|
110
61
|
try {
|
|
62
|
+
// Waits as long as the connection does: it has its own reconnect schedule
|
|
63
|
+
// and rejects once it gives up, so this never outlives a dead socket.
|
|
64
|
+
await client.connect();
|
|
111
65
|
const result = await client.customRequest<{ rendered?: string; richtext?: unknown }>(
|
|
112
66
|
'dpql/renderExpression',
|
|
113
67
|
// The server accepts both the bare `{exp, args}` AST and the
|
|
@@ -131,75 +85,50 @@ export const _resetRenderExpressionTransportForTests = (): void => {
|
|
|
131
85
|
renderClient?.disconnect();
|
|
132
86
|
renderClient = null;
|
|
133
87
|
methodUnsupported = false;
|
|
134
|
-
lastConnectWaitFailedAt = 0;
|
|
135
|
-
lastServerRendering = false;
|
|
136
88
|
};
|
|
137
89
|
|
|
138
90
|
interface IPendingRender {
|
|
139
91
|
value: IExpressionValue;
|
|
140
|
-
|
|
141
|
-
resolvers: Array<(rendered: IRenderedExpression) => void>;
|
|
92
|
+
resolvers: Array<(rendered: IRenderedExpression | null) => void>;
|
|
142
93
|
}
|
|
143
94
|
|
|
144
95
|
export interface IUseRenderExpressionResult {
|
|
145
|
-
/**
|
|
146
|
-
render: (value: IExpressionValue
|
|
96
|
+
/** The readable rendering of an expression AST, or `null` when the server cannot render. */
|
|
97
|
+
render: (value: IExpressionValue) => Promise<string | null>;
|
|
147
98
|
/**
|
|
148
99
|
* Like `render`, but also resolves the server's richtext form (template
|
|
149
|
-
* references as `tag` nodes — chips)
|
|
150
|
-
*
|
|
151
|
-
*/
|
|
152
|
-
renderRich: (
|
|
153
|
-
value: IExpressionValue,
|
|
154
|
-
expressions?: IExpressionSchema[]
|
|
155
|
-
) => Promise<IRenderedExpression>;
|
|
156
|
-
/**
|
|
157
|
-
* `true` when the server `dpql/renderExpression` path served the last
|
|
158
|
-
* render; `false` while on the client-side approximation (LSP unreachable
|
|
159
|
-
* or the server predates the method).
|
|
100
|
+
* references as `tag` nodes — chips). `null` when there is nothing to render
|
|
101
|
+
* or the server cannot render it.
|
|
160
102
|
*/
|
|
161
|
-
|
|
103
|
+
renderRich: (value: IExpressionValue) => Promise<IRenderedExpression | null>;
|
|
162
104
|
}
|
|
163
105
|
|
|
164
106
|
export const useRenderExpression = (): IUseRenderExpressionResult => {
|
|
165
|
-
const [serverRendering, setServerRendering] = useState(lastServerRendering);
|
|
166
107
|
const pendingRef = useRef<IPendingRender | null>(null);
|
|
167
108
|
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
168
109
|
const lastRunRef = useRef(0);
|
|
169
110
|
|
|
170
|
-
useEffect(
|
|
171
|
-
|
|
172
|
-
return () => {
|
|
173
|
-
serverRenderingListeners.delete(setServerRendering);
|
|
111
|
+
useEffect(
|
|
112
|
+
() => () => {
|
|
174
113
|
if (timerRef.current) {
|
|
175
114
|
clearTimeout(timerRef.current);
|
|
176
115
|
timerRef.current = null;
|
|
177
116
|
}
|
|
178
|
-
// Settle any coalesced callers
|
|
179
|
-
// promise is left hanging past unmount.
|
|
117
|
+
// Settle any coalesced callers so no promise is left hanging past unmount.
|
|
180
118
|
const pending = pendingRef.current;
|
|
181
119
|
pendingRef.current = null;
|
|
182
|
-
pending?.resolvers.forEach((resolve) =>
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
server: false,
|
|
187
|
-
})
|
|
188
|
-
);
|
|
189
|
-
};
|
|
190
|
-
}, []);
|
|
120
|
+
pending?.resolvers.forEach((resolve) => resolve(null));
|
|
121
|
+
},
|
|
122
|
+
[]
|
|
123
|
+
);
|
|
191
124
|
|
|
192
125
|
const renderRich = useCallback(
|
|
193
|
-
(
|
|
194
|
-
|
|
195
|
-
expressions: IExpressionSchema[] = []
|
|
196
|
-
): Promise<IRenderedExpression> =>
|
|
197
|
-
new Promise<IRenderedExpression>((resolve) => {
|
|
126
|
+
(value: IExpressionValue): Promise<IRenderedExpression | null> =>
|
|
127
|
+
new Promise<IRenderedExpression | null>((resolve) => {
|
|
198
128
|
// Coalesce calls per consumer: rapid live updates collapse to the
|
|
199
129
|
// newest AST; every awaiting caller gets that newest rendering.
|
|
200
130
|
pendingRef.current = {
|
|
201
131
|
value,
|
|
202
|
-
expressions,
|
|
203
132
|
resolvers: [...(pendingRef.current?.resolvers ?? []), resolve],
|
|
204
133
|
};
|
|
205
134
|
|
|
@@ -208,20 +137,7 @@ export const useRenderExpression = (): IUseRenderExpressionResult => {
|
|
|
208
137
|
pendingRef.current = null;
|
|
209
138
|
if (!job) return;
|
|
210
139
|
lastRunRef.current = Date.now();
|
|
211
|
-
|
|
212
|
-
if (!job.value?.exp) {
|
|
213
|
-
outcome = { text: '', richtext: null, server: false };
|
|
214
|
-
} else {
|
|
215
|
-
const served = await renderViaServer(job.value);
|
|
216
|
-
setLastServerRendering(served !== null);
|
|
217
|
-
outcome = served
|
|
218
|
-
? { ...served, server: true }
|
|
219
|
-
: {
|
|
220
|
-
text: renderExpressionToText(job.value, job.expressions),
|
|
221
|
-
richtext: null,
|
|
222
|
-
server: false,
|
|
223
|
-
};
|
|
224
|
-
}
|
|
140
|
+
const outcome = job.value?.exp ? await renderViaServer(job.value) : null;
|
|
225
141
|
job.resolvers.forEach((r) => r(outcome));
|
|
226
142
|
};
|
|
227
143
|
|
|
@@ -240,10 +156,10 @@ export const useRenderExpression = (): IUseRenderExpressionResult => {
|
|
|
240
156
|
);
|
|
241
157
|
|
|
242
158
|
const render = useCallback(
|
|
243
|
-
(value: IExpressionValue
|
|
244
|
-
renderRich(value
|
|
159
|
+
(value: IExpressionValue): Promise<string | null> =>
|
|
160
|
+
renderRich(value).then((r) => r?.text ?? null),
|
|
245
161
|
[renderRich]
|
|
246
162
|
);
|
|
247
163
|
|
|
248
|
-
return { render, renderRich
|
|
164
|
+
return { render, renderRich };
|
|
249
165
|
};
|
|
@@ -2,7 +2,6 @@ import {
|
|
|
2
2
|
IReqoreLabelProps,
|
|
3
3
|
ReqoreCheckbox,
|
|
4
4
|
ReqoreControlGroup,
|
|
5
|
-
ReqoreSkeleton,
|
|
6
5
|
ReqoreSpan,
|
|
7
6
|
} from '@qoretechnologies/reqore';
|
|
8
7
|
import { IWithReqoreSize } from '@qoretechnologies/reqore/dist/types/global';
|
|
@@ -38,6 +37,7 @@ import { IDataSchemaDefinition } from './schema-definition/types';
|
|
|
38
37
|
// Direct import — circular dep (FormEngine → TemplateField → FormField → FormEngine) is safe
|
|
39
38
|
// because modules are all loaded before any component renders
|
|
40
39
|
import { FormEngine } from '../engine/FormEngine';
|
|
40
|
+
import { FormFieldsSkeleton } from '../engine/FormFieldsSkeleton';
|
|
41
41
|
|
|
42
42
|
/** Soft Qore types (softint, softstring, …) render as their concrete base —
|
|
43
43
|
* the soft/hard distinction is value-level, not UI. */
|
|
@@ -345,7 +345,8 @@ export const FormField = <T extends TFormFieldType>({
|
|
|
345
345
|
|
|
346
346
|
case 'hash': {
|
|
347
347
|
if (argSchemaLoading) {
|
|
348
|
-
|
|
348
|
+
// A hash renders a nested FORM, so it waits in the shape of one.
|
|
349
|
+
return <FormFieldsSkeleton rows={2} />;
|
|
349
350
|
}
|
|
350
351
|
if (finalArgSchema) {
|
|
351
352
|
return (
|
|
@@ -40,13 +40,23 @@ const DISALLOW_COMPACT_AO_TYPES = ['connection'];
|
|
|
40
40
|
* Whether a creatable field's value is carried by the chip picker rather than
|
|
41
41
|
* by a raw editor with a suggestion list beside it.
|
|
42
42
|
*
|
|
43
|
-
*
|
|
43
|
+
* String-valued only, and only with something to suggest. Every other shape a
|
|
44
44
|
* creatable field can declare either does not fit a one-line chip (a document
|
|
45
45
|
* — `long-string`, `binary`, `data`; a hash; a list) or would come back out of
|
|
46
46
|
* one as a string (a number, a date), and a field with nothing to offer is
|
|
47
47
|
* better served by the editor it has always had. Those keep today's rendering:
|
|
48
48
|
* the raw editor, with the saved-and-suggested picker under it.
|
|
49
49
|
*
|
|
50
|
+
* `select-string` counts, and leaving it out was the bug. A picker field
|
|
51
|
+
* declares its storage type (`*string`) and its `ui_type` (`select-string`),
|
|
52
|
+
* and by the time a schema reaches here the two have been flattened to one:
|
|
53
|
+
* `type` arrives as `select-string`. So this returned false for every such
|
|
54
|
+
* field and NEITHER renderer stood down — the picker drew the value, and the
|
|
55
|
+
* saved-and-suggested list drew the same candidates again underneath it. A
|
|
56
|
+
* test's Service and Method fields showed 183 and 7 items twice over, in two
|
|
57
|
+
* controls that set one value. A select-string's value IS a string and fits the
|
|
58
|
+
* chip, which is the only thing this predicate is really asking.
|
|
59
|
+
*
|
|
50
60
|
* Both the renderer of the picker (`FieldAllowedValues`) and the renderer of
|
|
51
61
|
* the raw editor (`AutoFormField`, `FormField`) ask this, so that exactly one
|
|
52
62
|
* of them draws the value.
|
|
@@ -55,7 +65,8 @@ export const rendersCreatableValueSelect = (
|
|
|
55
65
|
type?: string,
|
|
56
66
|
allowCreation?: boolean,
|
|
57
67
|
items?: unknown[]
|
|
58
|
-
): boolean =>
|
|
68
|
+
): boolean =>
|
|
69
|
+
!!allowCreation && (type === 'string' || type === 'select-string') && count(items) > 0;
|
|
59
70
|
|
|
60
71
|
export const FieldAllowedValuesCheckGroup = memo(
|
|
61
72
|
({
|
|
@@ -54,22 +54,24 @@ const waitForText = async (text: string | RegExp) => {
|
|
|
54
54
|
|
|
55
55
|
// --- reqraft behavioral stories (kept from the field-migration batch) -------
|
|
56
56
|
|
|
57
|
-
/**
|
|
57
|
+
/** Untyped, no value — the field opens on an editor to type into, never on a type question. */
|
|
58
58
|
export const Empty: Story = {
|
|
59
59
|
args: {},
|
|
60
60
|
parameters: {
|
|
61
61
|
docs: {
|
|
62
62
|
description: {
|
|
63
63
|
story:
|
|
64
|
-
'Renders AutoFormField with no value or defaultType
|
|
64
|
+
'Renders AutoFormField with no value or defaultType. The field opens on a text editor to type into; it does not show a type picker or ask for a data type.',
|
|
65
65
|
},
|
|
66
66
|
},
|
|
67
67
|
},
|
|
68
68
|
async play({ canvasElement }) {
|
|
69
69
|
const canvas = within(canvasElement);
|
|
70
|
-
// The
|
|
71
|
-
await expect(
|
|
72
|
-
|
|
70
|
+
// The type resolves in a mount effect — assert asynchronously.
|
|
71
|
+
await waitFor(() => expect(canvasElement.querySelector('textarea')).toBeInTheDocument(), {
|
|
72
|
+
timeout: 5000,
|
|
73
|
+
});
|
|
74
|
+
await expect(canvas.queryByText('Please select data type')).not.toBeInTheDocument();
|
|
73
75
|
await expect(canvasElement.querySelector('input[type="number"]')).not.toBeInTheDocument();
|
|
74
76
|
},
|
|
75
77
|
};
|
|
@@ -179,7 +181,7 @@ export const Nullable: Story = {
|
|
|
179
181
|
},
|
|
180
182
|
};
|
|
181
183
|
|
|
182
|
-
/**
|
|
184
|
+
/** A field declaring several allowed types still picks between them. */
|
|
183
185
|
export const AllowedTypesSubset: Story = {
|
|
184
186
|
args: {
|
|
185
187
|
allowedTypes: [
|
|
@@ -191,14 +193,14 @@ export const AllowedTypesSubset: Story = {
|
|
|
191
193
|
docs: {
|
|
192
194
|
description: {
|
|
193
195
|
story:
|
|
194
|
-
'Renders AutoFormField with allowedTypes restricted to Integer and Text
|
|
196
|
+
'Renders AutoFormField with allowedTypes restricted to Integer and Text. The type picker offers those 2 types (its badge reads 2), and the field can be typed into meanwhile without being asked for a data type.',
|
|
195
197
|
},
|
|
196
198
|
},
|
|
197
199
|
},
|
|
198
|
-
async play() {
|
|
200
|
+
async play({ canvasElement }) {
|
|
199
201
|
// The picker badge reflects the restricted list — 2 instead of 14.
|
|
200
|
-
await waitForText('Please select data type');
|
|
201
202
|
await waitForText('2');
|
|
203
|
+
await expect(within(canvasElement).queryByText('Please select data type')).not.toBeInTheDocument();
|
|
202
204
|
},
|
|
203
205
|
};
|
|
204
206
|
|
|
@@ -206,31 +208,35 @@ export const AllowedTypesSubset: Story = {
|
|
|
206
208
|
export const NoSoftTypes: Story = {
|
|
207
209
|
args: {
|
|
208
210
|
noSoft: true,
|
|
211
|
+
/* The picker belongs to a value with a type: an untyped field that is
|
|
212
|
+
empty is typed into, and one holding text has resolved to `string`. */
|
|
213
|
+
defaultType: 'auto',
|
|
214
|
+
value: 'some text',
|
|
209
215
|
},
|
|
210
216
|
parameters: {
|
|
211
217
|
docs: {
|
|
212
218
|
description: {
|
|
213
219
|
story:
|
|
214
|
-
'Renders AutoFormField with noSoft enabled
|
|
220
|
+
'Renders an untyped AutoFormField holding a text value with noSoft enabled. The picker for the type the value resolved to leaves out the soft* variants, so its badge reads 9 instead of 14.',
|
|
215
221
|
},
|
|
216
222
|
},
|
|
217
223
|
},
|
|
218
|
-
async play() {
|
|
219
|
-
await waitForText('Please select data type');
|
|
224
|
+
async play({ canvasElement }) {
|
|
220
225
|
await waitForText('9');
|
|
226
|
+
await expect(within(canvasElement).queryByText('Please select data type')).not.toBeInTheDocument();
|
|
221
227
|
},
|
|
222
228
|
};
|
|
223
229
|
|
|
224
230
|
/**
|
|
225
231
|
* The whole point of the migration: a `FormEngine` schema with a field typed
|
|
226
|
-
* `auto` renders the
|
|
232
|
+
* `auto` renders the untyped editor for free, via FormEngine → TemplateField → auto.
|
|
227
233
|
*/
|
|
228
234
|
export const ViaFormEngine: Story = {
|
|
229
235
|
parameters: {
|
|
230
236
|
docs: {
|
|
231
237
|
description: {
|
|
232
238
|
story:
|
|
233
|
-
'Renders a FormEngine schema whose single option is typed as auto
|
|
239
|
+
'Renders a FormEngine schema whose single option is typed as auto. The engine flows through TemplateField to the untyped editor without any extra wiring: an editor to type into, with no data-type question.',
|
|
234
240
|
},
|
|
235
241
|
},
|
|
236
242
|
},
|
|
@@ -255,15 +261,76 @@ export const ViaFormEngine: Story = {
|
|
|
255
261
|
async play({ canvasElement }) {
|
|
256
262
|
const canvas = within(canvasElement);
|
|
257
263
|
// Generous timeout: findByText defaults to 1s, which flakes under CI load
|
|
258
|
-
// while the engine boots the auto field
|
|
259
|
-
// waits ~10s).
|
|
264
|
+
// while the engine boots the auto field (the rest of the suite waits ~10s).
|
|
260
265
|
await expect(
|
|
261
266
|
await canvas.findByText('My Auto Field', undefined, { timeout: 10000 })
|
|
262
267
|
).toBeInTheDocument();
|
|
263
|
-
// The
|
|
268
|
+
// The untyped field opens on an editor inside the engine-driven form, and
|
|
269
|
+
// never asks for a data type.
|
|
270
|
+
await waitFor(() => expect(canvasElement.querySelector('textarea')).toBeInTheDocument(), {
|
|
271
|
+
timeout: 10000,
|
|
272
|
+
});
|
|
273
|
+
await expect(canvas.queryByText('Please select data type')).not.toBeInTheDocument();
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* The ⋮ on that same field holds one group — "Set Custom Value" and the data
|
|
279
|
+
* types under it — and opens it, rather than asking for a click to reach the
|
|
280
|
+
* only thing on offer.
|
|
281
|
+
*/
|
|
282
|
+
export const ViaFormEngineMenuOpensItsOnlyGroup: Story = {
|
|
283
|
+
parameters: {
|
|
284
|
+
docs: {
|
|
285
|
+
description: {
|
|
286
|
+
story:
|
|
287
|
+
'The untyped field\'s ⋮ holds nothing but the "Set Custom Value" section, so that section is already open: the data types are one click from the menu, not two. Where a menu holds more than one group the sections stay shut, because there the click is the choice.',
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
render: () => {
|
|
292
|
+
const [val, setVal] = useState<any>({});
|
|
293
|
+
return (
|
|
294
|
+
<FormEngine
|
|
295
|
+
name='autoFieldMenuDemo'
|
|
296
|
+
options={{
|
|
297
|
+
myAutoField: {
|
|
298
|
+
type: 'auto',
|
|
299
|
+
ui_type: 'auto',
|
|
300
|
+
display_name: 'My Auto Field',
|
|
301
|
+
preselected: true,
|
|
302
|
+
},
|
|
303
|
+
}}
|
|
304
|
+
value={val}
|
|
305
|
+
onChange={(_n, v) => setVal(v)}
|
|
306
|
+
/>
|
|
307
|
+
);
|
|
308
|
+
},
|
|
309
|
+
async play({ canvasElement }) {
|
|
310
|
+
const canvas = within(canvasElement);
|
|
264
311
|
await expect(
|
|
265
|
-
await canvas.findByText('
|
|
312
|
+
await canvas.findByText('My Auto Field', undefined, { timeout: 10000 })
|
|
266
313
|
).toBeInTheDocument();
|
|
314
|
+
|
|
315
|
+
const menu = await waitFor(
|
|
316
|
+
() => {
|
|
317
|
+
const el = canvasElement.querySelector('.template-more');
|
|
318
|
+
expect(el).toBeTruthy();
|
|
319
|
+
return el as HTMLElement;
|
|
320
|
+
},
|
|
321
|
+
{ timeout: 10000 }
|
|
322
|
+
);
|
|
323
|
+
await userEvent.click(menu);
|
|
324
|
+
|
|
325
|
+
// The section still names its rows...
|
|
326
|
+
await waitFor(
|
|
327
|
+
() => expect(document.body.textContent).toContain('Set Custom Value'),
|
|
328
|
+
{ timeout: 10000 }
|
|
329
|
+
);
|
|
330
|
+
// ...and the rows are there to click, with no header click in between.
|
|
331
|
+
await waitFor(() => expect(document.body.textContent).toContain('Ordered key-value pair'), {
|
|
332
|
+
timeout: 10000,
|
|
333
|
+
});
|
|
267
334
|
},
|
|
268
335
|
};
|
|
269
336
|
|