@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.
Files changed (215) hide show
  1. package/.claude/CLAUDE.md +25 -0
  2. package/.tasks/INDEX.md +1 -0
  3. package/.tasks/STORY_NETWORK_ISOLATION.md +98 -0
  4. package/README.md +1 -1
  5. package/design/FORM_ENGINE_EXPRESSIONS.md +110 -0
  6. package/design/RENDER_EXPRESSION_TRANSPORT.md +18 -0
  7. package/dist/components/dpqlEditor/dpqlProbe.d.ts +37 -0
  8. package/dist/components/dpqlEditor/dpqlProbe.d.ts.map +1 -0
  9. package/dist/components/dpqlEditor/dpqlProbe.js +203 -0
  10. package/dist/components/dpqlEditor/dpqlProbe.js.map +1 -0
  11. package/dist/components/dpqlEditor/index.d.ts +4 -0
  12. package/dist/components/dpqlEditor/index.d.ts.map +1 -1
  13. package/dist/components/dpqlEditor/index.js +5 -1
  14. package/dist/components/dpqlEditor/index.js.map +1 -1
  15. package/dist/components/dpqlEditor/types.d.ts +29 -2
  16. package/dist/components/dpqlEditor/types.d.ts.map +1 -1
  17. package/dist/components/dpqlEditor/useDpqlProbe.d.ts +4 -0
  18. package/dist/components/dpqlEditor/useDpqlProbe.d.ts.map +1 -0
  19. package/dist/components/dpqlEditor/useDpqlProbe.js +29 -0
  20. package/dist/components/dpqlEditor/useDpqlProbe.js.map +1 -0
  21. package/dist/components/dpqlEditor/useDpqlSession.d.ts +5 -2
  22. package/dist/components/dpqlEditor/useDpqlSession.d.ts.map +1 -1
  23. package/dist/components/dpqlEditor/useDpqlSession.js +8 -2
  24. package/dist/components/dpqlEditor/useDpqlSession.js.map +1 -1
  25. package/dist/components/form/engine/CompactRow.d.ts.map +1 -1
  26. package/dist/components/form/engine/CompactRow.js +186 -21
  27. package/dist/components/form/engine/CompactRow.js.map +1 -1
  28. package/dist/components/form/engine/CompactToolbar.d.ts.map +1 -1
  29. package/dist/components/form/engine/CompactToolbar.js +5 -2
  30. package/dist/components/form/engine/CompactToolbar.js.map +1 -1
  31. package/dist/components/form/engine/FormEngine.d.ts +159 -11
  32. package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
  33. package/dist/components/form/engine/FormEngine.js +376 -75
  34. package/dist/components/form/engine/FormEngine.js.map +1 -1
  35. package/dist/components/form/engine/FormFieldsSkeleton.d.ts +48 -0
  36. package/dist/components/form/engine/FormFieldsSkeleton.d.ts.map +1 -0
  37. package/dist/components/form/engine/FormFieldsSkeleton.js +38 -0
  38. package/dist/components/form/engine/FormFieldsSkeleton.js.map +1 -0
  39. package/dist/components/form/engine/OptionFieldMessages.d.ts +17 -2
  40. package/dist/components/form/engine/OptionFieldMessages.d.ts.map +1 -1
  41. package/dist/components/form/engine/OptionFieldMessages.js +61 -11
  42. package/dist/components/form/engine/OptionFieldMessages.js.map +1 -1
  43. package/dist/components/form/engine/_structuredData/SchemaDataView.d.ts.map +1 -1
  44. package/dist/components/form/engine/_structuredData/SchemaDataView.js +7 -1
  45. package/dist/components/form/engine/_structuredData/SchemaDataView.js.map +1 -1
  46. package/dist/components/form/engine/compactToolbarContext.d.ts +1 -0
  47. package/dist/components/form/engine/compactToolbarContext.d.ts.map +1 -1
  48. package/dist/components/form/engine/readFirst.d.ts +42 -1
  49. package/dist/components/form/engine/readFirst.d.ts.map +1 -1
  50. package/dist/components/form/engine/readFirst.js +74 -7
  51. package/dist/components/form/engine/readFirst.js.map +1 -1
  52. package/dist/components/form/engine/rendererTypes.d.ts +13 -0
  53. package/dist/components/form/engine/rendererTypes.d.ts.map +1 -1
  54. package/dist/components/form/engine/rendererTypes.js.map +1 -1
  55. package/dist/components/form/engine/rowMenuContext.d.ts +78 -0
  56. package/dist/components/form/engine/rowMenuContext.d.ts.map +1 -0
  57. package/dist/components/form/engine/rowMenuContext.js +103 -0
  58. package/dist/components/form/engine/rowMenuContext.js.map +1 -0
  59. package/dist/components/form/engine/typeChoices.d.ts +37 -0
  60. package/dist/components/form/engine/typeChoices.d.ts.map +1 -0
  61. package/dist/components/form/engine/typeChoices.js +25 -0
  62. package/dist/components/form/engine/typeChoices.js.map +1 -0
  63. package/dist/components/form/expressions/ExpressionField.d.ts +7 -1
  64. package/dist/components/form/expressions/ExpressionField.d.ts.map +1 -1
  65. package/dist/components/form/expressions/ExpressionField.js +181 -33
  66. package/dist/components/form/expressions/ExpressionField.js.map +1 -1
  67. package/dist/components/form/expressions/argumentPresence.d.ts +97 -0
  68. package/dist/components/form/expressions/argumentPresence.d.ts.map +1 -0
  69. package/dist/components/form/expressions/argumentPresence.js +203 -0
  70. package/dist/components/form/expressions/argumentPresence.js.map +1 -0
  71. package/dist/components/form/expressions/builder/argumentLabel.d.ts.map +1 -1
  72. package/dist/components/form/expressions/builder/argumentLabel.js +9 -4
  73. package/dist/components/form/expressions/builder/argumentLabel.js.map +1 -1
  74. package/dist/components/form/expressions/builder/index.d.ts +15 -2
  75. package/dist/components/form/expressions/builder/index.d.ts.map +1 -1
  76. package/dist/components/form/expressions/builder/index.js +19 -57
  77. package/dist/components/form/expressions/builder/index.js.map +1 -1
  78. package/dist/components/form/expressions/dpqlMockLsp.d.ts +17 -0
  79. package/dist/components/form/expressions/dpqlMockLsp.d.ts.map +1 -1
  80. package/dist/components/form/expressions/dpqlMockLsp.js +75 -9
  81. package/dist/components/form/expressions/dpqlMockLsp.js.map +1 -1
  82. package/dist/components/form/expressions/renderExpressionToText.d.ts.map +1 -1
  83. package/dist/components/form/expressions/renderExpressionToText.js +94 -19
  84. package/dist/components/form/expressions/renderExpressionToText.js.map +1 -1
  85. package/dist/components/form/fields/Field.d.ts.map +1 -1
  86. package/dist/components/form/fields/Field.js +3 -1
  87. package/dist/components/form/fields/Field.js.map +1 -1
  88. package/dist/components/form/fields/allowed-values/AllowedValues.d.ts +11 -1
  89. package/dist/components/form/fields/allowed-values/AllowedValues.d.ts.map +1 -1
  90. package/dist/components/form/fields/allowed-values/AllowedValues.js +14 -2
  91. package/dist/components/form/fields/allowed-values/AllowedValues.js.map +1 -1
  92. package/dist/components/form/fields/auto/AutoFormField.d.ts.map +1 -1
  93. package/dist/components/form/fields/auto/AutoFormField.js +13 -2
  94. package/dist/components/form/fields/auto/AutoFormField.js.map +1 -1
  95. package/dist/components/form/fields/rich-text/RichText.d.ts +11 -0
  96. package/dist/components/form/fields/rich-text/RichText.d.ts.map +1 -1
  97. package/dist/components/form/fields/rich-text/RichText.js +84 -5
  98. package/dist/components/form/fields/rich-text/RichText.js.map +1 -1
  99. package/dist/components/form/fields/template/ReadOnlyTemplateTag.d.ts +2 -1
  100. package/dist/components/form/fields/template/ReadOnlyTemplateTag.d.ts.map +1 -1
  101. package/dist/components/form/fields/template/ReadOnlyTemplateTag.js +3 -2
  102. package/dist/components/form/fields/template/ReadOnlyTemplateTag.js.map +1 -1
  103. package/dist/components/form/fields/template/TemplateField.d.ts +31 -1
  104. package/dist/components/form/fields/template/TemplateField.d.ts.map +1 -1
  105. package/dist/components/form/fields/template/TemplateField.js +507 -40
  106. package/dist/components/form/fields/template/TemplateField.js.map +1 -1
  107. package/dist/components/form/index.d.ts +1 -0
  108. package/dist/components/form/index.d.ts.map +1 -1
  109. package/dist/components/form/index.js +1 -0
  110. package/dist/components/form/index.js.map +1 -1
  111. package/dist/components/smartEditor/__fixtures__/mockLspServer.d.ts +1 -0
  112. package/dist/components/smartEditor/__fixtures__/mockLspServer.d.ts.map +1 -1
  113. package/dist/components/smartEditor/__fixtures__/mockLspServer.js +6 -1
  114. package/dist/components/smartEditor/__fixtures__/mockLspServer.js.map +1 -1
  115. package/dist/helpers/dpqlDetection.d.ts +58 -0
  116. package/dist/helpers/dpqlDetection.d.ts.map +1 -0
  117. package/dist/helpers/dpqlDetection.js +164 -0
  118. package/dist/helpers/dpqlDetection.js.map +1 -0
  119. package/dist/helpers/optionUiTypes.d.ts +1 -1
  120. package/dist/helpers/options.d.ts +37 -0
  121. package/dist/helpers/options.d.ts.map +1 -1
  122. package/dist/helpers/options.js +78 -1
  123. package/dist/helpers/options.js.map +1 -1
  124. package/dist/helpers/templateItems.d.ts +47 -0
  125. package/dist/helpers/templateItems.d.ts.map +1 -0
  126. package/dist/helpers/templateItems.js +88 -0
  127. package/dist/helpers/templateItems.js.map +1 -0
  128. package/dist/helpers/templates.d.ts +21 -0
  129. package/dist/helpers/templates.d.ts.map +1 -1
  130. package/dist/helpers/templates.js +49 -3
  131. package/dist/helpers/templates.js.map +1 -1
  132. package/dist/helpers/validations.d.ts.map +1 -1
  133. package/dist/helpers/validations.js +56 -9
  134. package/dist/helpers/validations.js.map +1 -1
  135. package/dist/hooks/useArgSchema.d.ts +2 -1
  136. package/dist/hooks/useArgSchema.d.ts.map +1 -1
  137. package/dist/hooks/useFetch/useFetch.d.ts +3 -1
  138. package/dist/hooks/useFetch/useFetch.d.ts.map +1 -1
  139. package/dist/hooks/useFetch/useFetch.js +47 -9
  140. package/dist/hooks/useFetch/useFetch.js.map +1 -1
  141. package/dist/hooks/useTemplates.d.ts +20 -0
  142. package/dist/hooks/useTemplates.d.ts.map +1 -1
  143. package/dist/hooks/useTemplates.js +34 -2
  144. package/dist/hooks/useTemplates.js.map +1 -1
  145. package/dist/index.d.ts +2 -1
  146. package/dist/index.d.ts.map +1 -1
  147. package/dist/index.js +5 -1
  148. package/dist/index.js.map +1 -1
  149. package/dist/stories/storyNetwork.d.ts +36 -0
  150. package/dist/stories/storyNetwork.d.ts.map +1 -0
  151. package/dist/stories/storyNetwork.js +82 -0
  152. package/dist/stories/storyNetwork.js.map +1 -0
  153. package/dist/utils/fetch.d.ts +151 -1
  154. package/dist/utils/fetch.d.ts.map +1 -1
  155. package/dist/utils/fetch.js +256 -44
  156. package/dist/utils/fetch.js.map +1 -1
  157. package/dist/utils/lspClient.d.ts +6 -0
  158. package/dist/utils/lspClient.d.ts.map +1 -1
  159. package/dist/utils/lspClient.js +62 -11
  160. package/dist/utils/lspClient.js.map +1 -1
  161. package/dist/utils/websocket.d.ts +6 -0
  162. package/dist/utils/websocket.d.ts.map +1 -1
  163. package/dist/utils/websocket.js +15 -7
  164. package/dist/utils/websocket.js.map +1 -1
  165. package/package.json +2 -2
  166. package/src/components/dpqlEditor/DpqlEditor.stories.tsx +0 -2
  167. package/src/components/dpqlEditor/dpqlProbe.ts +165 -0
  168. package/src/components/dpqlEditor/index.ts +4 -0
  169. package/src/components/dpqlEditor/types.ts +26 -2
  170. package/src/components/dpqlEditor/useDpqlProbe.ts +33 -0
  171. package/src/components/dpqlEditor/useDpqlSession.ts +20 -4
  172. package/src/components/form/engine/CompactRow.tsx +231 -11
  173. package/src/components/form/engine/CompactToolbar.tsx +10 -3
  174. package/src/components/form/engine/FormEngine.stories.tsx +331 -30
  175. package/src/components/form/engine/FormEngine.tsx +504 -87
  176. package/src/components/form/engine/FormFieldsSkeleton.tsx +76 -0
  177. package/src/components/form/engine/OptionFieldMessages.tsx +89 -11
  178. package/src/components/form/engine/_structuredData/SchemaDataView.tsx +11 -0
  179. package/src/components/form/engine/compactToolbarContext.ts +1 -1
  180. package/src/components/form/engine/readFirst.ts +86 -7
  181. package/src/components/form/engine/rendererTypes.ts +12 -0
  182. package/src/components/form/engine/rowMenuContext.ts +159 -0
  183. package/src/components/form/engine/typeChoices.ts +55 -0
  184. package/src/components/form/expressions/ExpressionField.stories.tsx +828 -31
  185. package/src/components/form/expressions/ExpressionField.tsx +234 -29
  186. package/src/components/form/expressions/argumentPresence.ts +200 -0
  187. package/src/components/form/expressions/builder/argumentLabel.tsx +10 -4
  188. package/src/components/form/expressions/builder/index.tsx +31 -75
  189. package/src/components/form/expressions/dpqlMockLsp.ts +81 -1
  190. package/src/components/form/expressions/renderExpressionToText.ts +106 -17
  191. package/src/components/form/fields/Field.tsx +3 -2
  192. package/src/components/form/fields/allowed-values/AllowedValues.tsx +13 -2
  193. package/src/components/form/fields/auto/AutoFormField.tsx +12 -2
  194. package/src/components/form/fields/rich-text/RichText.tsx +112 -19
  195. package/src/components/form/fields/template/ReadOnlyTemplateTag.tsx +4 -2
  196. package/src/components/form/fields/template/TemplateField.tsx +667 -95
  197. package/src/components/form/index.tsx +1 -0
  198. package/src/components/log/Log.stories.tsx +2 -1
  199. package/src/components/smartEditor/__fixtures__/mockLspServer.ts +6 -1
  200. package/src/helpers/dpqlDetection.ts +209 -0
  201. package/src/helpers/options.ts +70 -0
  202. package/src/helpers/templateItems.ts +81 -0
  203. package/src/helpers/templates.ts +51 -2
  204. package/src/helpers/validations.ts +59 -9
  205. package/src/hooks/useFetch/useFetch.stories.tsx +6 -5
  206. package/src/hooks/useFetch/useFetch.tsx +53 -7
  207. package/src/hooks/useStorage/useStorage.stories.tsx +2 -1
  208. package/src/hooks/useTemplates.ts +41 -0
  209. package/src/hooks/useWebSocket/useWebsocket.stories.tsx +2 -1
  210. package/src/index.tsx +14 -1
  211. package/src/stores/currentUser/currentUser.stories.tsx +2 -1
  212. package/src/stories/storyNetwork.ts +92 -0
  213. package/src/utils/fetch.ts +311 -21
  214. package/src/utils/lspClient.ts +68 -11
  215. package/src/utils/websocket.ts +20 -9
package/.claude/CLAUDE.md CHANGED
@@ -167,6 +167,7 @@ yarn build:test # Type-check without emit
167
167
 
168
168
  - `yarn precheck` runs: lint → test → build:test:prod
169
169
  - `pre-push` hook enforces: `build:test:prod`, `lint`, `test`
170
+ - CI (`.github/workflows/tests.yml`) runs precheck, build and the full story suite for PRs into `develop` **and** for every push to `develop`. GitHub skips `pull_request` workflows while a PR has merge conflicts, so a conflicting PR gets no test runs until it is rebased or merged up
170
171
  - Branch naming: always start with the issue number, e.g. `feature/49_pooled-connections`
171
172
 
172
173
  ### Versioning — every PR to `develop` MUST bump the version
@@ -201,6 +202,26 @@ yarn build:test # Type-check without emit
201
202
  - Stories are co-located alongside source: `src/hooks/useFetch/useFetch.stories.tsx`
202
203
  - Storybook port: **6008**
203
204
  - Story + interaction tests run on **Vitest browser mode** (Playwright/Chromium): `yarn test:stories [file-substring]`. Run `yarn install-playwright` once first.
205
+ - **Story network — mocks must follow the instance, and stories must not share connections.**
206
+ - Build every mock URL with `storyApiUrl(path)` / `storySocketUrl(path)` from
207
+ `src/stories/storyNetwork.ts`, never a hard-coded `https://hq.qoretechnologies.com:8092/…`.
208
+ Both mock layers match on host (storybook-addon-mock keys on `host + pathname`,
209
+ mock-socket on the URL minus its query), so a hard-coded host silently stops matching
210
+ when `REACT_APP_QORUS_INSTANCE` is overridden and the request goes to the real network.
211
+ - Requests components make on their own (FormEngine's `system/qorus-type-info`, the
212
+ expression catalogue `useExpressions` fetches whenever functions are allowed, the
213
+ reconnect probe `system/pid`) are mocked for every story via `GLOBAL_STORY_MOCK_DATA`
214
+ (`parameters.mockAddonConfigs.globalMockData`). Add new ones there, not per story file.
215
+ - A mock `response` must be an object, array or function — a bare number or string fails
216
+ the addon's validation and the entry is silently never matched (`TStoryMockResponse`
217
+ enforces this at compile time). The addon compares query-parameter *keys* only, so
218
+ `system?action=a&context=x` also answers `system?action=b&context=y`.
219
+ - `.storybook/preview.tsx` resets the shared LSP connections and the expression render
220
+ client before every story. Do not add per-file `ReqraftWebSocketsManager.closeAll()`
221
+ calls for LSP isolation.
222
+ - CI reaches the live hq instance with a valid token, so a story that leaks a real
223
+ request can pass in CI and hang locally. When a story passes in CI but times out on
224
+ your machine, look for an unmocked request before anything else.
204
225
  - **Visual regression via Qlip** (not Chromatic): the story run captures snapshots through the `qlip` Vitest plugin. Upload is gated on `QLIP_UPLOAD_TOKEN` (`vitest.config.ts`), so **local runs capture but never upload** — PNGs land in `qlip/screenshots/<ts>/stories/auto/<StoryId>.png` (gitignored); read the PNG to verify a visual change. CI (`.github/workflows/tests.yml`) sets the token and uploads a build for dashboard review. The full visual-change flow + the `qlip` MCP review skill live in the imported `stacks/frontend/FRONTEND.md`.
205
226
 
206
227
  ## Code Patterns & Conventions
@@ -260,6 +281,10 @@ yarn build:test # Type-check without emit
260
281
  | `query()` returning stale data | Pass `cache: false`; POST/DELETE bypass cache automatically |
261
282
  | Heartbeat noise in logs | Filter `ev.data === 'pong'` — already done inside `useReqraftWebSocket` |
262
283
  | Storage path collisions | Use `includeAppPrefix: true` in `useReqraftStorage` to namespace per app |
284
+ | Story shows FormEngine's loading skeleton / "Connecting to language server…" forever | An unmocked request or a connection inherited from an earlier story — see "Story network" above |
285
+ | "No Preview — Sorry, but you either have no stories…" in a story failure | Not a registration failure: addon-vitest injects Storybook's hidden preview body into every test page, and a failed query's DOM dump prints it. Read the assertion above the dump |
286
+ | A story run stops reporting and never times out (no failure, no output) | The page's main thread is blocked, so the in-page 30s test timer cannot fire — usually a synchronous render loop inside `act()`. Find the story with `--reporter=verbose` (the last ✓ is the one before it), then give Chromium `launchOptions: { args: ['--remote-debugging-port=9333'] }` in `vitest.config.ts` locally, enable `Debugger` over CDP BEFORE the freeze, and `Debugger.pause` once `Runtime.evaluate` stops answering to get the stack. Revert the config afterwards |
287
+ | `yarn test:stories` hangs before any test runs (the port message is incidental) | Seen when the checkout path contains `+` — e.g. an auto-named `.claude/worktrees/fix+name` — while the same tree at a `+`-free path runs normally. Use a path without `+` |
263
288
 
264
289
  ## File Reference
265
290
 
package/.tasks/INDEX.md CHANGED
@@ -58,6 +58,7 @@ needs phase 1.
58
58
  | [RENDER_EXPRESSION_LSP](./RENDER_EXPRESSION_LSP.md) | done pending user verify, branch `feature/render-expression-lsp` — server deployed + live-verified end-to-end 2026-06-10 (final gate 201/201 storybook, 268/268 jest, browser proof of server rendering) | **8th.** Server "Explain" over the LSP: new `dpql/renderExpression` in `QorusLspWebSocketHandler.qc` + `useRenderExpression` swap. Closes EXPRESSION_BUILDER_REPORT Phase D. Design: `design/RENDER_EXPRESSION_TRANSPORT.md`. |
59
59
  | [FIELD_STACK_REPORT](./FIELD_STACK_REPORT.md) | done pending user verify (uncommitted) — final gate 234/234 storybook (incl. 37 ported Template/Auto plays), 268/268 jest | **9th.** Verbatim re-port of the IDE's `Field/template.tsx` + `Field/auto.tsx` (the operand layer under the ported builder) + their IDE stories, replacing the from-scratch TemplateField/AutoFormField; closes the `isFunction` operand-flow gap (behavioral difference #4 in EXPRESSION_BUILDER_REPORT). Supersedes the from-scratch implementation from REQRAFT_AUTO_FIELD (its stories/assertions are kept and reconciled). |
60
60
  | [BRANCH_REMEDIATION](./BRANCH_REMEDIATION.md) | done pending user verify (uncommitted) — jest 268→356, plays 241→255, lint/tsc clean | **10th.** Burn-down of the 2026-06-10 full-branch audit: tests/stories/API/comment-voice remediation + behavior fixes (Select modal-click collapse, edge-triggered Url, ReqraftLspClient rename, CI auth wiring). Ledger: gitignored `SWEEP.local.md`. |
61
+ | [STORY_NETWORK_ISOLATION](./STORY_NETWORK_ISOLATION.md) | done pending user verify, folded into `fix/per-field-templates` (PR #115), not pushed | **Standalone.** ExpressionField stories failed off-network on `develop` (passed in CI only because CI reaches the live instance): LSP connections leaking across stories, an unhandled socket give-up in `LspSharedConnection`, unmocked background requests, and mock URLs hard-coded to the hq host. |
61
62
  | [TEMPLATE_TOKEN_GRAMMAR](./TEMPLATE_TOKEN_GRAMMAR.md) | committed `0ef167c`, in PR (branch `bugfix/template-token-grammar`) — user-verified 2026-08-27; 13/13 unit, 21/21 template plays, qlip PNGs read | **11th (bugfix).** Braced `$data:{…}` state-output refs rehydrate as raw text: canonical token grammar + strict whole-token check + TemplateField renders whole-token values as the picker chip. Twin fix applied in qorus-ide the same day; ships as a normal per-PR patch release (`0.10.37`). |
62
63
  | [EXPRESSION_FIELD_SEAMS](./EXPRESSION_FIELD_SEAMS.md) | committed `f5fb2ee`, pushed (branch `bugfix/116_expression-field-seams`) — user-verified 2026-09-07; PR not yet opened | **12th (bugfix).** The `extraActions` seam stopped one hop short of the `ExpressionField` shell hosts mount, and the builder's operand `TemplateField` mounts dropped it again — qorus-ide lost its AI-assist button on every expression card when it adopted the shell ([#116](https://github.com/qoretechnologies/toolkit-react/issues/116)). Forward it through both hops; pin with a nested-operand story; ships as a per-PR patch (`0.10.51`; re-based onto `4860e6e` after #114 published 0.10.50). |
63
64
  | [EXPORT_EXPRESSION_BUILDER](./EXPORT_EXPRESSION_BUILDER.md) | committed `0eea9e7`, pushed, PR open (branch `feature/119_export-expression-builder`) | **13th (surface fix).** The barrel exported the `ExpressionField` shell but not the bare `ExpressionBuilder`, so a host mirroring the library's root/nested rule had to deep-import `dist/…/builder` ([#119](https://github.com/qoretechnologies/toolkit-react/issues/119)). Export + a barrel test; ships as `0.10.53`. |
@@ -0,0 +1,98 @@
1
+ # Story network isolation — ExpressionField stories fail off-network
2
+
3
+ **Status:** done pending user verify — folded into `fix/per-field-templates`
4
+ (PR #115) so every open reqraft fix ships in one PR; not yet pushed.
5
+ Originally built and verified on `develop` (stories 479/479, unit 930/930,
6
+ off-network), then re-verified on the combined branch.
7
+
8
+ ## Symptom
9
+
10
+ `yarn test:stories ExpressionField` failed 6 of 8 on `develop` locally, while the
11
+ same file passed in CI (run 34250300001, 2026-09-08). Siblings failed the same
12
+ way: FormEngineRemote 11/11, AutoFormField 1, SchemaDefinitionField 2,
13
+ useWebsocket 3, TemplateField 1.
14
+
15
+ ## Root causes
16
+
17
+ 1. **An LSP connection outlived the story that opened it.** `useRenderExpression`
18
+ holds the endpoint's `LspSharedConnection` for the page lifetime. `Default`
19
+ (no mock LSP) dialled the real instance; `Empty` / `Text Mode` then joined that
20
+ connection's never-settling handshake, so their mock LSP was never dialled
21
+ ("Connecting to language server…" forever). Each passes when run alone.
22
+ 2. **The shared connection had no way out of a dead socket** (product bug). No
23
+ `onReconnectFailed` handling: a first handshake whose socket gave up stayed
24
+ pending forever for every later client; one that had completed kept reporting
25
+ the dead socket as ready. `close()` also abandoned an in-flight handshake, and
26
+ `release()` could evict a connection that had replaced its own after a reset.
27
+ 3. **Requests components make on their own were unmocked.** FormEngine gates on
28
+ `system/qorus-type-info`; TemplateField's "Use Expression" waits on the
29
+ expression catalogue; every `ReqraftWebSocket` reconnect awaits a `system/pid`
30
+ probe. Against an unreachable instance none settles — the form stays a
31
+ skeleton, the menu entry never appears, the socket never reconnects or gives
32
+ up. CI's live instance answered all three, which is why CI stayed green.
33
+ 4. **Mock URLs hard-coded the hq host.** Both mock layers match on host, so
34
+ overriding `REACT_APP_QORUS_INSTANCE` silently disabled every mock.
35
+
36
+ ## Fix
37
+
38
+ - `src/utils/lspClient.ts` — give-up / close settle the handshake and forget the
39
+ socket; retry paths guarded by identity; `release()` only evicts itself.
40
+ 4 new unit tests + the disconnect-mid-handshake test now asserts the rejection.
41
+ - `src/stories/storyNetwork.ts` — `STORY_QORUS_INSTANCE`, `storyApiUrl`,
42
+ `storySocketUrl`, `GLOBAL_STORY_MOCK_DATA` (types catalogue, empty expression
43
+ catalogue, reconnect probe),
44
+ `TStoryMockResponse` (the addon silently ignores non-object responses).
45
+ - `.storybook/preview.tsx` — `mockAddonConfigs.globalMockData`, per-story LSP
46
+ reset in a project `beforeEach`; `.storybook/vitest.setup.ts` mirrors the
47
+ addon's global + story merge.
48
+ - All mock URLs moved to the helpers; FormEngine's duplicate types mock and
49
+ DpqlEditor's ad-hoc `closeAll()` removed.
50
+
51
+ ### Reconciled with `fix/per-field-templates`
52
+
53
+ - That branch had already met the leak from the fixture side:
54
+ `createMockLspServer` reset the shared connections on create and close. The
55
+ project-level `beforeEach` covers the same ground plus stories that never
56
+ create a mock server (and the render client), so the fixture resets were
57
+ dropped in favour of the one mechanism.
58
+ - Its `query()` strips a path's leading slash and uses a `noApiPrefix` url
59
+ verbatim; `buildReqraftApiUrl` follows those rules, so the doubled-slash mock
60
+ spellings are gone.
61
+ - **A page-freezing render loop in the row menu** (from `a21640b`), reachable
62
+ only once FormEngine rendered offline: `FormEngine › Compact Expressions`
63
+ froze the tab and hung the whole suite with no output. The row kept ONE
64
+ `{key, items}` slot, and an expression's operands — separate TemplateFields
65
+ in the same row — published different keys into it, each overwrite
66
+ re-rendering the row and both operands. The same slot also kept an unmounted
67
+ editor's handlers when a reopened row offered the same key. Fixed in
68
+ `rowMenuContext.ts` (per-publisher registration via `useId`, withdrawn on
69
+ unmount, clicks delegated to the latest handler) and `TemplateField` (claims
70
+ the channel and hides it from what it renders, so operands keep their own
71
+ menu). Unit tests reproduce the loop and both stale-handler cases.
72
+ - `ExpressionField › Field Menu Stays In The Toolbar` had gone stale on that
73
+ branch: `a21640b` moved a row field's menu into the row's ⋮ (one menu per
74
+ control) after the story was written. The story now asserts that single
75
+ menu — the row's `.options-readfirst-more`, with no `.template-more` beside
76
+ it — still sits level with the toolbar.
77
+
78
+ ## Not causes (checked)
79
+
80
+ - "No Preview" in failure output: addon-vitest injects Storybook's hidden preview
81
+ body into every test page; a failed query's DOM dump prints it.
82
+ - A `+` in the checkout path hangs `yarn test:stories` before any test runs
83
+ (mechanism not traced); unrelated to the stories.
84
+
85
+ ## Follow-ups (not done)
86
+
87
+ - `users?action=current` is still unmocked for most stories — harmless today
88
+ (`waitForStorage={false}`), but CI renders a real user while local runs render
89
+ none. Candidate for a default mock once permission-gated snapshots are checked.
90
+ - Unmocked requests still pass through to the network. Failing them loudly for
91
+ non-live stories would make this class impossible rather than documented.
92
+ - `ReqraftWebSocketsManager.closeAll()` deletes entries by `socket.url` rather than
93
+ the pool key, and closing a CONNECTING socket reports code 1006, which schedules
94
+ a reconnect. Not exercised by this fix.
95
+ - ~~`tests.yml` runs only on `pull_request`~~ — done on this branch: it now also
96
+ runs on pushes to `develop` (the push trigger had been dropped in 2021 with no
97
+ recorded reason). Conflicting PRs still get no `pull_request` runs until they
98
+ merge up.
package/README.md CHANGED
@@ -148,7 +148,7 @@ client.onNotification('qonsole/sessionStateChanged', (params) => { /* … */ });
148
148
  client.disconnect();
149
149
  ```
150
150
 
151
- Clients on the same LSP endpoint share ONE underlying WebSocket — each `ReqraftLspClient` is a per-document facade over the shared connection, multiplexed by document URI (the server keys language sessions per document), so N editors cost one socket. Auto-reconnect re-opens every document on the shared socket, 15s request timeout, request/response correlation by `id`, pending requests rejected on close. Document URIs should be opaque and client-generated — per the Qonsole LSP contract they must not contain session tokens, usernames, sandbox identifiers, or other secrets that end up in server logs.
151
+ Clients on the same LSP endpoint share ONE underlying WebSocket — each `ReqraftLspClient` is a per-document facade over the shared connection, multiplexed by document URI (the server keys language sessions per document), so N editors cost one socket. Auto-reconnect re-opens every document on the shared socket, 15s request timeout, request/response correlation by `id`, pending requests rejected on close. A `connect()` never outlives its socket: once the socket exhausts `maxReconnectTries`, a handshake still waiting rejects, and the next `connect()` — from that client or any other — dials again, so an editor opened after an outage connects once the server is back, without a page reload. Document URIs should be opaque and client-generated — per the Qonsole LSP contract they must not contain session tokens, usernames, sandbox identifiers, or other secrets that end up in server logs.
152
152
 
153
153
  ## Community
154
154
 
@@ -124,6 +124,116 @@ templates/return-type, and bind a provider only when the field supplies
124
124
  one. Worst case, text mode still parses/serializes correctly with no
125
125
  provider; only completions are reduced.
126
126
 
127
+ ## Detecting an expression typed as plain text
128
+
129
+ *Added 2026-09-06. As-built.*
130
+
131
+ An author who never opens the expression view still types DPQL into the
132
+ ordinary editor. `TemplateField` notices, and what it does next depends on
133
+ the field's own type.
134
+
135
+ **A successful parse means nothing.** This is the finding the whole design
136
+ rests on, and it is counter-intuitive enough to be worth stating plainly.
137
+ Verified against the live LSP: `dpql/parse` returns `success: true` for
138
+ `hello`, for `42`, for `2026-09-06` and for `@a`. A bare literal is a valid
139
+ DPQL program, so "does it parse?" cannot separate an expression from a
140
+ value. What separates them is the shape of the AST that comes back:
141
+
142
+ | typed text | `exp` | verdict |
143
+ |---|---|---|
144
+ | `hello`, `42`, `2026-09-06`, `@a` | `value` | a literal |
145
+ | `$local:count` | `template` | a template |
146
+ | `@a > 5`, `"a" + "b"`, `toInt("5")`, `@n LIKE "a%"` | `>`, `+`, `toInt`, `like` | an expression |
147
+
148
+ Text that is not DPQL at all is safe on its own: `hello world`, `x@y.com`,
149
+ `a - b test` and `{"a":1}` all fail to parse outright.
150
+
151
+ **Three stages, in `helpers/dpqlDetection.ts`:**
152
+
153
+ 1. `mightBeDpqlExpression` — a cheap lexical pass whose ONLY job is to keep
154
+ a server round-trip off every keystroke. It decides nothing. `-` and `/`
155
+ require surrounding spaces so a date or a path never triggers a probe.
156
+ 2. `dpql/parse` on the shared probe document (below), debounced 400 ms.
157
+ 3. `classifyTypedText` — `switch` when the text could not be a literal of
158
+ the field's declared type (the form's own `validateField` answers that),
159
+ `offer` when it could, `none` otherwise.
160
+
161
+ **Why the asymmetry.** Rewriting the string `a + b` into a concatenation on
162
+ a `string` field would destroy a value the author meant, so a type that
163
+ accepts any text is only ever OFFERED the switch. Text that could not be a
164
+ literal there was already an error the moment it was typed, so switching
165
+ costs nothing and explains the error. Every switch carries an Undo that
166
+ restores the exact text, and a declined text is never asked about again.
167
+
168
+ **Reachability.** The auto-switch only applies where the editor accepts free
169
+ text — a constrained `string` (identifier / `validation_regex`), `binary`,
170
+ `hash`. `int` and `float` render `input[type=number]`, which drops `@`, `>`
171
+ and spaces before any handler runs, so detection there is unreachable by
172
+ design of the editor and the ⋮ menu remains the way in. Pinned by a test.
173
+
174
+ **Template precedence.** `isValueTemplate` is deliberately loose (starts
175
+ with `$`, contains `:`), so `$local:count + 1` was being swallowed into
176
+ template mode with its arithmetic as dead text. A value that is not one
177
+ complete template token and carries an operator now stays in the plain
178
+ editor, where detection can reach it. A token standing alone
179
+ (`$local:count`, `$data:{1.field}`) still flips, unchanged.
180
+
181
+ **The probe document** (`components/dpqlEditor/dpqlProbe.ts`). One shared,
182
+ lazily-opened DPQL document for the whole app, reference-counted by
183
+ `useDpqlProbe` and closed when the last field using it unmounts. It is
184
+ cheap: `ReqraftLspClient` multiplexes every document over ONE socket per
185
+ endpoint and `initialize` runs once for it, so a probe is one extra
186
+ `didOpen` plus one server-side session — not a connection. It is still
187
+ shared rather than per-field, because a form with forty expression-capable
188
+ options would otherwise open forty sessions to ask the same question.
189
+
190
+ The document binds NO provider context; verified live, a context-less
191
+ `didOpen` still builds a session that `dpql/parse` answers on. It sends no
192
+ `target_type` either: detection asks whether the text is an expression, not
193
+ whether it is the right type — a mismatch is something to show the author
194
+ inside the editor, never a reason to refuse to open it. A probe never
195
+ throws; an unreachable instance simply leaves the typed text alone.
196
+
197
+ ## Return-type checking
198
+
199
+ *Added 2026-09-06. As-built.*
200
+
201
+ `dpql/parse` has always been able to answer "can this expression's result
202
+ satisfy type X?" — it takes a `target_type` and returns `inferred_type`,
203
+ `type_compatible`, `auto_coercible` and a `suggested_fix`. Nothing in the
204
+ front end ever sent one, so it never answered.
205
+
206
+ `ExpressionField` now sends the field's own type with every parse, and
207
+ shows what comes back. Three outcomes, not two:
208
+
209
+ | the expression | shown |
210
+ |---|---|
211
+ | already fits | nothing |
212
+ | converts, and the conversion is TOTAL (a number used as text) | nothing |
213
+ | converts, but the conversion may FAIL (text used as a number) | a warning, with the conversion offered |
214
+ | cannot convert at all | a danger message, with the conversion offered |
215
+
216
+ The middle row is why the server grew `coercion_may_fail`: `canAutoCoerce()`
217
+ admits both kinds, and reporting a conversion that cannot fail is what
218
+ teaches authors to ignore the reports that matter.
219
+
220
+ `auto` and `any` are never sent as a target — they accept anything, so the
221
+ answer would always be "compatible" and the round trip would buy nothing. A
222
+ field offering a CHOICE of return types sends nothing either, rather than
223
+ asking about one of them arbitrarily.
224
+
225
+ **What the field cannot decide.** Whether text really denotes a number
226
+ depends on the text, which does not exist until the expression runs. So the
227
+ warning is a warning, not a refusal, and the value is checked again
228
+ server-side at run time — see `design/test-interface-object.md` in the qorus
229
+ repo. For that to be possible the FormEngine's stored envelope carries the
230
+ field's `type` beside `is_expression` and the AST, which is the type the
231
+ author was shown.
232
+
233
+ The same analysis lives in two server handlers (the LSP's `dpql/parse` and
234
+ the Creator WS equivalent); both were changed together. Deduplicating them
235
+ is a separate cleanup.
236
+
127
237
  ## Out of scope
128
238
 
129
239
  - `server_expression_handling` server-function-vs-qorus-function
@@ -88,3 +88,21 @@ errors: -32602 missing/invalid expression; -32803 render failure;
88
88
  and `serverRendering` reports `false`.
89
89
  - Consumers (`builder/renderTemplate.tsx`, `ExpressionField.tsx`) are
90
90
  untouched.
91
+
92
+ ## Lifetime of the shared render socket
93
+
94
+ The module-level render client never releases its connection: the first
95
+ expression rendered on a page holds the endpoint's `LspSharedConnection` for
96
+ the rest of the page. Two consequences are designed for:
97
+
98
+ - **A socket that gives up is forgotten.** When the socket exhausts its
99
+ reconnect attempts, the connection fails any handshake still waiting and
100
+ drops the socket; the next `connect()` from any client on that endpoint
101
+ dials afresh. Without this, a first handshake that never completed held
102
+ every later editor on "Connecting to language server…" for the life of the
103
+ page, and one that had completed kept reporting the dead socket as ready.
104
+ - **Storybook resets it per story.** Stories run in one page, so the
105
+ connection an earlier story opened would otherwise serve a later story —
106
+ including one that starts its own mock LSP, which is then never dialled.
107
+ `.storybook/preview.tsx` runs `_resetRenderExpressionTransportForTests()`
108
+ and `_resetSharedLspConnectionsForTests()` in a project-level `beforeEach`.
@@ -0,0 +1,37 @@
1
+ /** What a probe answers. Never throws — a failure is `success: false`. */
2
+ export interface IDpqlProbeResult {
3
+ success: boolean;
4
+ expression: Record<string, any> | null;
5
+ diagnostics: Array<{
6
+ severity?: number | string;
7
+ message?: string;
8
+ code?: string | number;
9
+ }>;
10
+ }
11
+ declare class DpqlProbe {
12
+ private client;
13
+ private opened;
14
+ private users;
15
+ private readonly cache;
16
+ /** Register a consumer; the document is still not opened until it probes. */
17
+ acquire(): void;
18
+ /** Drop a consumer, closing the shared document when the last one goes. */
19
+ release(): void;
20
+ /**
21
+ * Parse `text` on the shared probe document. Resolves to `FAILED` rather
22
+ * than rejecting, for any reason at all — a field must never break because
23
+ * the instance is unreachable.
24
+ */
25
+ parse(text: string): Promise<IDpqlProbeResult>;
26
+ /** Open (once) and return the shared document's client. */
27
+ private open;
28
+ private remember;
29
+ private close;
30
+ }
31
+ /**
32
+ * The process-wide probe. Exported for tests, which reset it between cases;
33
+ * application code goes through `useDpqlProbe`.
34
+ */
35
+ export declare const dpqlProbe: DpqlProbe;
36
+ export {};
37
+ //# sourceMappingURL=dpqlProbe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dpqlProbe.d.ts","sourceRoot":"","sources":["../../../src/components/dpqlEditor/dpqlProbe.ts"],"names":[],"mappings":"AAgCA,0EAA0E;AAC1E,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC;IACvC,WAAW,EAAE,KAAK,CAAC;QAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC,CAAC;CAC9F;AAaD,cAAM,SAAS;IACb,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,MAAM,CAA0C;IACxD,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAuC;IAE7D,6EAA6E;IAC7E,OAAO,IAAI,IAAI;IAIf,2EAA2E;IAC3E,OAAO,IAAI,IAAI;IAQf;;;;OAIG;IACG,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAgCpD,2DAA2D;IAC3D,OAAO,CAAC,IAAI;IAyBZ,OAAO,CAAC,QAAQ;IAYhB,OAAO,CAAC,KAAK;CAad;AAED;;;GAGG;AACH,eAAO,MAAM,SAAS,WAAkB,CAAC"}
@@ -0,0 +1,203 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __generator = (this && this.__generator) || function (thisArg, body) {
12
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
13
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.dpqlProbe = void 0;
40
+ // Copyright 2026 Qore Technologies, s.r.o.
41
+ /**
42
+ * One shared, lazily-opened DPQL document used to ask the server "is this
43
+ * text really an expression?" on behalf of plain form fields that have no
44
+ * editor of their own.
45
+ *
46
+ * ## Why a singleton, and why it is cheap
47
+ *
48
+ * `ReqraftLspClient` multiplexes every document over ONE socket per endpoint
49
+ * (`LspSharedConnection`), and `initialize` runs once for that socket. A
50
+ * probe is therefore not a new connection — it is one extra `didOpen`, and
51
+ * one `QorusDpqlActionSession` in the server's `uriToDpqlSession` map. Even
52
+ * so it is a single shared document rather than one per field: a form with
53
+ * forty expression-capable options would otherwise open forty of them to ask
54
+ * the same question.
55
+ *
56
+ * Nothing is opened until a field actually probes, so a form whose author
57
+ * never types anything expression-shaped costs nothing at all.
58
+ *
59
+ * The document is opened with NO provider context. Verified against the live
60
+ * server: `textDocument/didOpen` with `languageId: 'dpql'` and no metadata
61
+ * still builds a session, and `dpql/parse` answers on it. Record-field
62
+ * references (`@name`) do not resolve to a provider's fields without that
63
+ * context, which is fine — detection asks about the SHAPE of the expression,
64
+ * and the editor the author lands in binds the real context.
65
+ *
66
+ * No `target_type` is sent. Detection asks "is this an expression", not "is
67
+ * it the right type"; a type mismatch is a thing to show the author INSIDE
68
+ * the expression editor, not a reason to refuse to open it.
69
+ */
70
+ var lspClient_1 = require("../../utils/lspClient");
71
+ var FAILED = { success: false, expression: null, diagnostics: [] };
72
+ /** Opaque, non-secret document URI — the server keys its session by this. */
73
+ var PROBE_URI = 'dpql://probe/detection';
74
+ /**
75
+ * How many parsed texts to remember. Typing walks through many prefixes of
76
+ * the same expression, and an author re-typing a value should not re-ask.
77
+ */
78
+ var MAX_CACHE_ENTRIES = 200;
79
+ var DpqlProbe = /** @class */ (function () {
80
+ function DpqlProbe() {
81
+ this.client = null;
82
+ this.opened = null;
83
+ this.users = 0;
84
+ this.cache = new Map();
85
+ }
86
+ /** Register a consumer; the document is still not opened until it probes. */
87
+ DpqlProbe.prototype.acquire = function () {
88
+ this.users += 1;
89
+ };
90
+ /** Drop a consumer, closing the shared document when the last one goes. */
91
+ DpqlProbe.prototype.release = function () {
92
+ this.users = Math.max(0, this.users - 1);
93
+ if (this.users === 0) {
94
+ this.close();
95
+ }
96
+ };
97
+ /**
98
+ * Parse `text` on the shared probe document. Resolves to `FAILED` rather
99
+ * than rejecting, for any reason at all — a field must never break because
100
+ * the instance is unreachable.
101
+ */
102
+ DpqlProbe.prototype.parse = function (text) {
103
+ return __awaiter(this, void 0, void 0, function () {
104
+ var cached, result, client, response, _a;
105
+ var _b, _c, _d;
106
+ return __generator(this, function (_e) {
107
+ switch (_e.label) {
108
+ case 0:
109
+ cached = this.cache.get(text);
110
+ if (cached) {
111
+ return [2 /*return*/, cached];
112
+ }
113
+ result = FAILED;
114
+ _e.label = 1;
115
+ case 1:
116
+ _e.trys.push([1, 4, , 5]);
117
+ return [4 /*yield*/, this.open()];
118
+ case 2:
119
+ client = _e.sent();
120
+ return [4 /*yield*/, client.customRequest('dpql/parse', {
121
+ uri: PROBE_URI,
122
+ text: text,
123
+ })];
124
+ case 3:
125
+ response = _e.sent();
126
+ result = {
127
+ success: (_b = response === null || response === void 0 ? void 0 : response.success) !== null && _b !== void 0 ? _b : false,
128
+ expression: (_c = response === null || response === void 0 ? void 0 : response.expression) !== null && _c !== void 0 ? _c : null,
129
+ diagnostics: (_d = response === null || response === void 0 ? void 0 : response.diagnostics) !== null && _d !== void 0 ? _d : [],
130
+ };
131
+ return [3 /*break*/, 5];
132
+ case 4:
133
+ _a = _e.sent();
134
+ // Unreachable instance, closed socket, request timeout: the field
135
+ // simply learns nothing and leaves the typed text exactly as it is.
136
+ result = FAILED;
137
+ return [3 /*break*/, 5];
138
+ case 5:
139
+ this.remember(text, result);
140
+ return [2 /*return*/, result];
141
+ }
142
+ });
143
+ });
144
+ };
145
+ /** Open (once) and return the shared document's client. */
146
+ DpqlProbe.prototype.open = function () {
147
+ var _this = this;
148
+ if (this.opened) {
149
+ return this.opened;
150
+ }
151
+ this.opened = (function () { return __awaiter(_this, void 0, void 0, function () {
152
+ var client;
153
+ return __generator(this, function (_a) {
154
+ switch (_a.label) {
155
+ case 0:
156
+ client = new lspClient_1.ReqraftLspClient({ languageId: 'dpql', uri: PROBE_URI });
157
+ return [4 /*yield*/, client.connect()];
158
+ case 1:
159
+ _a.sent();
160
+ // `didOpen` is a notification on the same socket as the request that
161
+ // follows it, so the server has the session by the time the parse
162
+ // arrives — no readiness poll needed.
163
+ client.didOpen('');
164
+ this.client = client;
165
+ return [2 /*return*/, client];
166
+ }
167
+ });
168
+ }); })().catch(function (error) {
169
+ // Allow a later probe to retry a connection that failed once.
170
+ _this.opened = null;
171
+ throw error;
172
+ });
173
+ return this.opened;
174
+ };
175
+ DpqlProbe.prototype.remember = function (text, result) {
176
+ if (this.cache.size >= MAX_CACHE_ENTRIES) {
177
+ var oldest = this.cache.keys().next();
178
+ if (!oldest.done) {
179
+ this.cache.delete(oldest.value);
180
+ }
181
+ }
182
+ this.cache.set(text, result);
183
+ };
184
+ DpqlProbe.prototype.close = function () {
185
+ var client = this.client;
186
+ this.client = null;
187
+ this.opened = null;
188
+ this.cache.clear();
189
+ try {
190
+ client === null || client === void 0 ? void 0 : client.disconnect();
191
+ }
192
+ catch (_a) {
193
+ // best-effort teardown
194
+ }
195
+ };
196
+ return DpqlProbe;
197
+ }());
198
+ /**
199
+ * The process-wide probe. Exported for tests, which reset it between cases;
200
+ * application code goes through `useDpqlProbe`.
201
+ */
202
+ exports.dpqlProbe = new DpqlProbe();
203
+ //# sourceMappingURL=dpqlProbe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dpqlProbe.js","sourceRoot":"","sources":["../../../src/components/dpqlEditor/dpqlProbe.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,9 @@
1
1
  export { DpqlEditor } from './DpqlEditor';
2
2
  export type { IDpqlEditorProps, IDpqlEditorRef, IDpqlParseResult, ISlateElement, ISlateText, TSlateNode, } from './types';
3
+ export { dpqlProbe } from './dpqlProbe';
4
+ export type { IDpqlProbeResult } from './dpqlProbe';
5
+ export { useDpqlProbe } from './useDpqlProbe';
6
+ export type { TDpqlProbeFn } from './useDpqlProbe';
3
7
  export { useDpqlSession } from './useDpqlSession';
4
8
  export type { IDpqlFieldMeta, IUseDpqlSessionOptions, IUseDpqlSessionResult, } from './useDpqlSession';
5
9
  export { dpqlSlateConverter, getTagLabel, isTagCompletion, plainTextToSlate, slateSelectionToOffset, slateToPlainText, } from './dpqlHelpers';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/dpqlEditor/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EACV,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,UAAU,EACV,UAAU,GACX,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,YAAY,EACV,cAAc,EACd,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,kBAAkB,CAAC;AAI1B,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,eAAe,EACf,gBAAgB,EAChB,sBAAsB,EACtB,gBAAgB,GACjB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,sBAAsB,EACtB,eAAe,EACf,eAAe,EACf,mBAAmB,GACpB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/dpqlEditor/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EACV,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,UAAU,EACV,UAAU,GACX,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,YAAY,EACV,cAAc,EACd,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,kBAAkB,CAAC;AAI1B,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,eAAe,EACf,gBAAgB,EAChB,sBAAsB,EACtB,gBAAgB,GACjB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,sBAAsB,EACtB,eAAe,EACf,eAAe,EACf,mBAAmB,GACpB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC"}
@@ -2,9 +2,13 @@
2
2
  // Copyright 2026 Qore Technologies, s.r.o.
3
3
  // Public surface of the DpqlEditor component.
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
- exports.dpqlCompletionInserter = exports.makeDpqlTagRenderer = exports.TEMPLATE_COLORS = exports.FIELD_REF_COLOR = exports.DEFAULT_TEMPLATE_COLOR = exports.slateToPlainText = exports.slateSelectionToOffset = exports.plainTextToSlate = exports.isTagCompletion = exports.getTagLabel = exports.dpqlSlateConverter = exports.useDpqlSession = exports.DpqlEditor = void 0;
5
+ exports.dpqlCompletionInserter = exports.makeDpqlTagRenderer = exports.TEMPLATE_COLORS = exports.FIELD_REF_COLOR = exports.DEFAULT_TEMPLATE_COLOR = exports.slateToPlainText = exports.slateSelectionToOffset = exports.plainTextToSlate = exports.isTagCompletion = exports.getTagLabel = exports.dpqlSlateConverter = exports.useDpqlSession = exports.useDpqlProbe = exports.dpqlProbe = exports.DpqlEditor = void 0;
6
6
  var DpqlEditor_1 = require("./DpqlEditor");
7
7
  Object.defineProperty(exports, "DpqlEditor", { enumerable: true, get: function () { return DpqlEditor_1.DpqlEditor; } });
8
+ var dpqlProbe_1 = require("./dpqlProbe");
9
+ Object.defineProperty(exports, "dpqlProbe", { enumerable: true, get: function () { return dpqlProbe_1.dpqlProbe; } });
10
+ var useDpqlProbe_1 = require("./useDpqlProbe");
11
+ Object.defineProperty(exports, "useDpqlProbe", { enumerable: true, get: function () { return useDpqlProbe_1.useDpqlProbe; } });
8
12
  var useDpqlSession_1 = require("./useDpqlSession");
9
13
  Object.defineProperty(exports, "useDpqlSession", { enumerable: true, get: function () { return useDpqlSession_1.useDpqlSession; } });
10
14
  // `useDpqlSyntaxHighlighting` removed in 0.10.0 — syntax highlighting
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/dpqlEditor/index.ts"],"names":[],"mappings":";AAAA,2CAA2C;AAC3C,8CAA8C;;;AAE9C,2CAA0C;AAAjC,wGAAA,UAAU,OAAA;AASnB,mDAAkD;AAAzC,gHAAA,cAAc,OAAA;AAMvB,sEAAsE;AACtE,mEAAmE;AACnE,oCAAoC;AACpC,6CAOuB;AANrB,iHAAA,kBAAkB,OAAA;AAClB,0GAAA,WAAW,OAAA;AACX,8GAAA,eAAe,OAAA;AACf,+GAAA,gBAAgB,OAAA;AAChB,qHAAA,sBAAsB,OAAA;AACtB,+GAAA,gBAAgB,OAAA;AAElB,uCAKoB;AAJlB,kHAAA,sBAAsB,OAAA;AACtB,2GAAA,eAAe,OAAA;AACf,2GAAA,eAAe,OAAA;AACf,+GAAA,mBAAmB,OAAA;AAErB,+CAAwD;AAA/C,sHAAA,sBAAsB,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/dpqlEditor/index.ts"],"names":[],"mappings":";AAAA,2CAA2C;AAC3C,8CAA8C;;;AAE9C,2CAA0C;AAAjC,wGAAA,UAAU,OAAA;AASnB,yCAAwC;AAA/B,sGAAA,SAAS,OAAA;AAElB,+CAA8C;AAArC,4GAAA,YAAY,OAAA;AAErB,mDAAkD;AAAzC,gHAAA,cAAc,OAAA;AAMvB,sEAAsE;AACtE,mEAAmE;AACnE,oCAAoC;AACpC,6CAOuB;AANrB,iHAAA,kBAAkB,OAAA;AAClB,0GAAA,WAAW,OAAA;AACX,8GAAA,eAAe,OAAA;AACf,+GAAA,gBAAgB,OAAA;AAChB,qHAAA,sBAAsB,OAAA;AACtB,+GAAA,gBAAgB,OAAA;AAElB,uCAKoB;AAJlB,kHAAA,sBAAsB,OAAA;AACtB,2GAAA,eAAe,OAAA;AACf,2GAAA,eAAe,OAAA;AACf,+GAAA,mBAAmB,OAAA;AAErB,+CAAwD;AAA/C,sHAAA,sBAAsB,OAAA"}