@lowdefy/server-dev 6.1.0 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (624) hide show
  1. package/client/App.jsx +52 -24
  2. package/client/Inspector.jsx +73 -30
  3. package/client/JourneyObserver.jsx +47 -0
  4. package/client/Page.jsx +54 -22
  5. package/client/Recorder.jsx +73 -0
  6. package/client/Routing.jsx +37 -13
  7. package/client/clientBuildImports.test.mjs +2 -0
  8. package/client/devStreamConnections.test.mjs +40 -0
  9. package/client/feedback/FeedbackMount.jsx +2 -2
  10. package/client/feedback/FeedbackOverlay.jsx +7 -4
  11. package/client/feedback/feedbackStyles.js +3 -0
  12. package/client/journeyObserver/startJourneyObserver.js +101 -0
  13. package/client/journeyObserver/startJourneyObserver.test.mjs +149 -0
  14. package/client/openInEditor/OpenInEditorListener.jsx +2 -0
  15. package/client/recorder/buildEngineRecord.js +37 -0
  16. package/client/recorder/buildInteractionRecord.js +64 -0
  17. package/client/recorder/buildTraceEvent.js +57 -0
  18. package/client/recorder/buildTraceEvent.test.mjs +81 -0
  19. package/client/recorder/chunkRecords.js +69 -0
  20. package/client/recorder/chunkRecords.test.mjs +59 -0
  21. package/client/recorder/collectEventActions.js +30 -0
  22. package/client/recorder/collectEventEndpoints.js +40 -0
  23. package/client/recorder/collectEventRequests.js +39 -0
  24. package/client/recorder/createPairingBuffer.js +187 -0
  25. package/client/recorder/createPairingBuffer.test.mjs +405 -0
  26. package/client/recorder/createPasswordRedactor.js +84 -0
  27. package/client/recorder/createRecordBatcher.js +54 -0
  28. package/client/recorder/createRecordBatcher.test.mjs +76 -0
  29. package/client/recorder/createRecorder.js +216 -0
  30. package/client/recorder/createRecorder.test.mjs +318 -0
  31. package/client/recorder/createRecordingSession.js +61 -0
  32. package/client/recorder/createRecordingSession.test.mjs +72 -0
  33. package/client/recorder/diffStateWrites.js +77 -0
  34. package/client/recorder/diffStateWrites.test.mjs +66 -0
  35. package/client/recorder/isEventCall.js +30 -0
  36. package/client/recorder/jsonType.js +39 -0
  37. package/client/recorder/keyChord.js +47 -0
  38. package/client/recorder/keyChord.test.mjs +50 -0
  39. package/client/recorder/sendRecords.js +40 -0
  40. package/client/recorder/toJsonSafe.js +29 -0
  41. package/lib/client/BuildingPage.jsx +3 -1
  42. package/lib/client/ErrorBar.jsx +17 -13
  43. package/lib/client/RedirectingPage.jsx +56 -0
  44. package/lib/client/auth/Auth.jsx +11 -6
  45. package/lib/client/auth/AuthConfigured.jsx +222 -36
  46. package/lib/client/auth/AuthNotConfigured.js +45 -4
  47. package/lib/client/auth/authNotConfiguredParity.test.mjs +61 -0
  48. package/lib/client/auth/authTemplateSync.test.mjs +40 -0
  49. package/lib/client/auth/betterAuthSessionContract.test.mjs +181 -0
  50. package/lib/client/auth/createFetchCheckingBuild.js +45 -0
  51. package/lib/client/bundleBuildId.js +25 -0
  52. package/lib/client/collectVisibleBlockIds.js +51 -0
  53. package/lib/client/getCspNonce.js +25 -0
  54. package/lib/client/getPagePath.js +36 -0
  55. package/lib/client/getPagePath.test.mjs +51 -0
  56. package/lib/client/utils/formatErrorsForCopy.js +30 -0
  57. package/lib/client/utils/formatErrorsForCopy.test.mjs +39 -0
  58. package/lib/client/utils/getErrorBarColor.js +25 -0
  59. package/lib/client/utils/getErrorBarColor.test.mjs +34 -0
  60. package/lib/client/utils/request.js +8 -1
  61. package/lib/client/utils/usePageConfig.js +89 -25
  62. package/lib/client/utils/usePageConfig.test.mjs +198 -13
  63. package/lib/configTree/buildConfigTree.mjs +58 -0
  64. package/lib/configTree/buildConfigTree.test.mjs +77 -0
  65. package/lib/configTree/collectConfigText.mjs +101 -0
  66. package/lib/configTree/collectConfigText.test.mjs +116 -0
  67. package/lib/configTree/prepareScratchServer.mjs +46 -0
  68. package/lib/configTree/runConfigTreeBuild.mjs +88 -0
  69. package/lib/configTree/runConfigTreeBuild.skipPluginChecks.test.mjs +74 -0
  70. package/lib/configTree/runConfigTreeBuild.test.mjs +202 -0
  71. package/lib/docs/JourneyStepError.js +30 -0
  72. package/lib/docs/acquireBrowserSlot.js +30 -0
  73. package/lib/docs/addNestedEndpoints.js +54 -0
  74. package/lib/docs/addNestedEndpoints.test.mjs +185 -0
  75. package/lib/docs/addWriteFlags.js +77 -0
  76. package/lib/docs/buildEditedPages.js +50 -0
  77. package/lib/docs/buildWaitHeader.js +21 -0
  78. package/lib/docs/captureAnnotatedScreenshot.js +23 -12
  79. package/lib/docs/checkAgentFiles.js +148 -0
  80. package/lib/docs/checkAgentFiles.test.mjs +225 -0
  81. package/lib/docs/checkStepReferences.js +239 -0
  82. package/lib/docs/checkStepReferences.test.mjs +173 -0
  83. package/lib/docs/checkpointStore.js +3 -0
  84. package/lib/docs/clientErrorStore.js +18 -1
  85. package/lib/docs/clientErrorStore.test.mjs +46 -0
  86. package/lib/docs/collectExercised.js +52 -0
  87. package/lib/docs/createBrowserLifecycle.js +102 -0
  88. package/lib/docs/createBrowserLifecycle.test.mjs +212 -0
  89. package/lib/docs/createDocsMcpServer.js +345 -388
  90. package/lib/docs/createDocsMcpServer.test.mjs +644 -4
  91. package/lib/docs/createDocsMcpServerRunJourney.test.mjs +148 -0
  92. package/lib/docs/createJourneyActors.chromium.test.mjs +227 -0
  93. package/lib/docs/createJourneyActors.js +213 -0
  94. package/lib/docs/createJourneyActors.test.mjs +79 -0
  95. package/lib/docs/createJourneyObservations.js +80 -0
  96. package/lib/docs/createLeftOriginError.js +26 -0
  97. package/lib/docs/createNetworkCounter.js +101 -0
  98. package/lib/docs/dataSets/checkSharedCollectionIds.js +78 -0
  99. package/lib/docs/dataSets/dataSessionRegistry.js +25 -0
  100. package/lib/docs/dataSets/dataSetJourneys.chromium.mongodb.test.mjs +727 -0
  101. package/lib/docs/dataSets/describeDataSetResult.js +25 -0
  102. package/lib/docs/dataSets/describeDatabaseTarget.js +46 -0
  103. package/lib/docs/dataSets/describeTargetKey.js +29 -0
  104. package/lib/docs/dataSets/deserializeFixture.js +34 -0
  105. package/lib/docs/dataSets/drainSessionWork.js +37 -0
  106. package/lib/docs/dataSets/drainSessionWork.test.mjs +64 -0
  107. package/lib/docs/dataSets/duplicateIndexName.js +24 -0
  108. package/lib/docs/dataSets/getDataStore.js +106 -0
  109. package/lib/docs/dataSets/getDataStore.mongodb.test.mjs +63 -0
  110. package/lib/docs/dataSets/getDataStore.test.mjs +195 -0
  111. package/lib/docs/dataSets/groupDataSetByCollection.js +52 -0
  112. package/lib/docs/dataSets/insertFixture.js +57 -0
  113. package/lib/docs/dataSets/insertGeneratedDocuments.js +41 -0
  114. package/lib/docs/dataSets/isDataStoreGone.js +29 -0
  115. package/lib/docs/dataSets/loadDataSetCollection.js +42 -0
  116. package/lib/docs/dataSets/mergeCollectionIndexes.js +52 -0
  117. package/lib/docs/dataSets/mergeCollectionIndexes.test.mjs +59 -0
  118. package/lib/docs/dataSets/openDataSession.js +133 -0
  119. package/lib/docs/dataSets/openDataSession.mongodb.test.mjs +493 -0
  120. package/lib/docs/dataSets/pickDataStorePort.js +58 -0
  121. package/lib/docs/dataSets/pickDataStorePort.test.mjs +63 -0
  122. package/lib/docs/dataSets/readConnectionArtifacts.js +46 -0
  123. package/lib/docs/dataSets/readDataSession.js +41 -0
  124. package/lib/docs/dataSets/readDataSession.test.mjs +57 -0
  125. package/lib/docs/dataSets/readDataSet.js +83 -0
  126. package/lib/docs/dataSets/readDataSet.test.mjs +251 -0
  127. package/lib/docs/dataSets/registerDataStoreShutdown.js +47 -0
  128. package/lib/docs/dataSets/removeDataStoreFiles.js +32 -0
  129. package/lib/docs/dataSets/resolveDataSetCollection.js +46 -0
  130. package/lib/docs/dataSets/resolveJourneyDataSet.js +106 -0
  131. package/lib/docs/dataSets/resolveJourneyDataSet.test.mjs +130 -0
  132. package/lib/docs/dataSets/sessionDatabaseName.js +32 -0
  133. package/lib/docs/dataSets/sweepOrphanDatabases.js +36 -0
  134. package/lib/docs/dataSets/test/app/lowdefy.yaml +225 -0
  135. package/lib/docs/dataSets/test/app/modules/archive/module.lowdefy.yaml +30 -0
  136. package/lib/docs/dataSets/test/app/tests/data/shop.yaml +12 -0
  137. package/lib/docs/dataSets/test/buildTestApp.mjs +43 -0
  138. package/lib/docs/devEventBus.js +136 -0
  139. package/lib/docs/devEventBus.test.mjs +269 -0
  140. package/lib/docs/devToolDefinitions.js +529 -0
  141. package/lib/docs/docSources.js +21 -0
  142. package/lib/docs/docs.test.mjs +171 -15
  143. package/lib/docs/docsIndex.test.mjs +377 -0
  144. package/lib/docs/evalOperator.js +5 -3
  145. package/lib/docs/evalOperatorHeadless.js +84 -12
  146. package/lib/docs/evalOperatorHeadless.test.mjs +7 -1
  147. package/lib/docs/evalOperatorInTab.js +2 -1
  148. package/lib/docs/findConfig.js +95 -49
  149. package/lib/docs/fitResponse.js +81 -0
  150. package/lib/docs/fitResponse.test.mjs +128 -0
  151. package/lib/docs/formatFeedback.js +26 -5
  152. package/lib/docs/formatFeedback.test.mjs +48 -0
  153. package/lib/docs/getAppMap.js +7 -1
  154. package/lib/docs/getBrowser.js +150 -80
  155. package/lib/docs/getBrowser.test.mjs +407 -16
  156. package/lib/docs/getBuildId.js +61 -0
  157. package/lib/docs/getBuildId.test.mjs +84 -0
  158. package/lib/docs/getBuildStatus.js +81 -7
  159. package/lib/docs/getBuildStatus.test.mjs +229 -0
  160. package/lib/docs/getBuildStatusAfterEdits.js +35 -0
  161. package/lib/docs/getBuildStatusAfterEdits.test.mjs +76 -0
  162. package/lib/docs/getCoreDoc.js +9 -1
  163. package/lib/docs/getDoc.js +100 -0
  164. package/lib/docs/getDocsIndex.js +152 -0
  165. package/lib/docs/getHazards.js +115 -0
  166. package/lib/docs/getHazards.test.mjs +175 -0
  167. package/lib/docs/getMailOutboxDirectory.js +26 -0
  168. package/lib/docs/getOverview.js +79 -13
  169. package/lib/docs/getOverviewBasePath.test.mjs +37 -0
  170. package/lib/docs/getPageBuildStatus.js +46 -0
  171. package/lib/docs/getPluginDoc.js +21 -23
  172. package/lib/docs/getSchema.js +33 -1
  173. package/lib/docs/getStaleStatus.js +36 -0
  174. package/lib/docs/getStaleStatus.test.mjs +74 -0
  175. package/lib/docs/guardJourneyOrigin.js +39 -0
  176. package/lib/docs/inspectState.js +5 -3
  177. package/lib/docs/inspectStateFromTab.js +2 -2
  178. package/lib/docs/inspectStateHeadless.js +89 -27
  179. package/lib/docs/inspectStateHeadless.test.mjs +20 -1
  180. package/lib/docs/installHeadlessShell.failure.test.mjs +45 -0
  181. package/lib/docs/installHeadlessShell.js +123 -0
  182. package/lib/docs/installHeadlessShell.test.mjs +70 -0
  183. package/lib/docs/installHeadlessShell.timeout.test.mjs +72 -0
  184. package/lib/docs/isHeadlessShellIncomplete.js +34 -0
  185. package/lib/docs/isHeadlessShellIncomplete.test.mjs +63 -0
  186. package/lib/docs/isPageReady.js +77 -0
  187. package/lib/docs/isPageReady.test.mjs +235 -0
  188. package/lib/docs/isRunErrorEntry.js +25 -0
  189. package/lib/docs/journeyMutantCookie.chromium.test.mjs +150 -0
  190. package/lib/docs/journeyRunRecording.js +35 -0
  191. package/lib/docs/journeyRunRecording.test.mjs +39 -0
  192. package/lib/docs/keepEventStreamAlive.js +68 -0
  193. package/lib/docs/keepEventStreamAlive.test.mjs +77 -0
  194. package/lib/docs/launchBrowser.js +83 -0
  195. package/lib/docs/launchBrowser.test.mjs +175 -0
  196. package/lib/docs/listModuleDocEntries.js +69 -0
  197. package/lib/docs/listPackageDocFiles.js +67 -0
  198. package/lib/docs/listPluginDocEntries.js +111 -0
  199. package/lib/docs/listPlugins.js +9 -58
  200. package/lib/docs/listTypes.js +14 -6
  201. package/lib/docs/loadFrameworkHazards.js +40 -0
  202. package/lib/docs/loadFrameworkHazards.test.mjs +43 -0
  203. package/lib/docs/loadState.js +103 -35
  204. package/lib/docs/makeModuleManifestDoc.js +174 -0
  205. package/lib/docs/matchExercisedPages.js +35 -0
  206. package/lib/docs/matchJourneyRoute.js +69 -0
  207. package/lib/docs/matchJourneyRoute.test.mjs +178 -0
  208. package/lib/docs/mergeNetworkSnapshots.js +49 -0
  209. package/lib/docs/nextJourneySequence.js +30 -0
  210. package/lib/docs/noBrowserError.js +22 -0
  211. package/lib/docs/observe/TRANSIENT_SELECTOR.js +21 -0
  212. package/lib/docs/observe/claimExpectedErrors.js +67 -0
  213. package/lib/docs/observe/createJourneyAppErrors.js +236 -0
  214. package/lib/docs/observe/createJourneyAppErrors.test.mjs +385 -0
  215. package/lib/docs/observe/evaluateInvariants.js +183 -0
  216. package/lib/docs/observe/evaluateInvariants.test.mjs +299 -0
  217. package/lib/docs/observe/findingKey.js +29 -0
  218. package/lib/docs/observe/hasNoEffect.js +31 -0
  219. package/lib/docs/observe/hasNoEffect.test.mjs +37 -0
  220. package/lib/docs/observe/installStepObserver.js +72 -0
  221. package/lib/docs/observe/isAppApiUrl.js +33 -0
  222. package/lib/docs/observe/parseAppApiUrl.js +37 -0
  223. package/lib/docs/observe/readStepObserver.js +42 -0
  224. package/lib/docs/observe/relativeSource.js +29 -0
  225. package/lib/docs/observe/resolveConfigKeySource.js +33 -0
  226. package/lib/docs/observe/takeJourneyEvents.js +36 -0
  227. package/lib/docs/observe/toAppErrorFailure.js +61 -0
  228. package/lib/docs/observe/usesSearchStage.js +45 -0
  229. package/lib/docs/observe/waitForClientErrorReports.js +31 -0
  230. package/lib/docs/observe/watchJourneyContext.js +75 -0
  231. package/lib/docs/observe/withAppErrors.js +31 -0
  232. package/lib/docs/observe/withAppErrors.test.mjs +101 -0
  233. package/lib/docs/openJourney.js +94 -0
  234. package/lib/docs/openJourneyEmail.js +41 -0
  235. package/lib/docs/openJourneyEmail.test.mjs +187 -0
  236. package/lib/docs/pageBlocks.js +47 -0
  237. package/lib/docs/pageBuildStatus.test.mjs +103 -0
  238. package/lib/{client/setPageId.js → docs/parseModuleSource.js} +11 -12
  239. package/lib/docs/rails/authEngineActions.js +53 -0
  240. package/lib/docs/rails/authEngineActions.test.mjs +69 -0
  241. package/lib/docs/rails/authEngineMethods.js +52 -0
  242. package/lib/docs/rails/authEngineMethods.test.mjs +36 -0
  243. package/lib/docs/rails/checkAuthActionsRan.js +66 -0
  244. package/lib/docs/rails/checkAuthActionsRan.test.mjs +149 -0
  245. package/lib/docs/rails/checkClickRails.js +56 -0
  246. package/lib/docs/rails/createDataSetRails.js +82 -0
  247. package/lib/docs/rails/createDataSetRails.test.mjs +172 -0
  248. package/lib/docs/rails/findExternalBlocks.js +147 -0
  249. package/lib/docs/rails/findExternalBlocks.test.mjs +254 -0
  250. package/lib/docs/rails/installAuthActionRecorder.js +56 -0
  251. package/lib/docs/rails/readAuthActionRecorder.js +32 -0
  252. package/lib/docs/rails/readBuiltArtifact.js +30 -0
  253. package/lib/docs/readConnectionPlugins.js +26 -0
  254. package/lib/docs/readDevAuthMode.js +32 -0
  255. package/lib/docs/readDocEntry.js +40 -0
  256. package/lib/docs/readDocTitle.js +26 -0
  257. package/lib/docs/readJourneyEmailMatch.js +57 -0
  258. package/lib/docs/readJourneyEmailMatch.test.mjs +110 -0
  259. package/lib/docs/readJourneySession.js +56 -0
  260. package/lib/docs/readMailOutbox.js +37 -0
  261. package/lib/docs/readPagePath.js +27 -0
  262. package/lib/docs/readPluginPackages.js +99 -0
  263. package/lib/docs/readProxyBuildWait.js +38 -0
  264. package/lib/docs/readRoutineSteps.js +45 -0
  265. package/lib/docs/registerShownTextEngine.js +118 -0
  266. package/lib/docs/requestRestart.js +30 -0
  267. package/lib/docs/requestRestart.test.mjs +49 -0
  268. package/lib/docs/resolvePageInstance.js +46 -0
  269. package/lib/docs/resolvePageInstance.test.mjs +81 -0
  270. package/lib/docs/resolveSource.js +11 -7
  271. package/lib/docs/resolveSource.test.mjs +1 -1
  272. package/lib/docs/resolveToolCaller.js +62 -0
  273. package/lib/docs/reviewPage.js +51 -0
  274. package/lib/docs/reviewPageBuilds.js +56 -0
  275. package/lib/docs/reviewPageBuilds.test.mjs +207 -0
  276. package/lib/docs/runCheck.js +75 -0
  277. package/lib/docs/runCheck.test.mjs +91 -0
  278. package/lib/docs/runCheckChild.js +49 -0
  279. package/lib/docs/runEndpoint.js +185 -0
  280. package/lib/docs/runEndpoint.test.mjs +509 -0
  281. package/lib/docs/runErrorBuffers.js +99 -0
  282. package/lib/docs/runErrorBuffers.test.mjs +95 -0
  283. package/lib/docs/runJourney.js +347 -0
  284. package/lib/docs/runJourney.test.mjs +2383 -0
  285. package/lib/docs/runJourneyAsUsers.js +44 -0
  286. package/lib/docs/runJourneyDataSet.test.mjs +274 -0
  287. package/lib/docs/runJourneySteps.js +1320 -0
  288. package/lib/docs/runRequest.js +100 -59
  289. package/lib/docs/runRequest.test.mjs +190 -0
  290. package/lib/docs/saveAnnotatedScreenshot.js +1 -1
  291. package/lib/docs/scaffoldPage.js +3 -1
  292. package/lib/docs/screenshotPage.js +163 -11
  293. package/lib/docs/screenshotPage.test.mjs +263 -1
  294. package/lib/docs/screenshotPageClip.test.mjs +4 -0
  295. package/lib/docs/searchDocs.js +82 -30
  296. package/lib/docs/searchDocs.test.mjs +100 -0
  297. package/lib/docs/searchIcons.js +90 -0
  298. package/lib/docs/searchIcons.test.mjs +127 -0
  299. package/lib/docs/selectExercised.js +39 -0
  300. package/lib/docs/selectFinalState.js +66 -0
  301. package/lib/docs/selectFinalState.test.mjs +51 -0
  302. package/lib/docs/serverErrorStore.js +54 -0
  303. package/lib/docs/serverErrorStore.test.mjs +50 -0
  304. package/lib/docs/setupTestFixtures.mjs +133 -39
  305. package/lib/docs/shownTextSelector.js +28 -0
  306. package/lib/docs/snapshotState.js +13 -5
  307. package/lib/docs/splitSearchTerms.js +62 -0
  308. package/lib/docs/splitSearchTerms.test.mjs +50 -0
  309. package/lib/docs/stateCheckpoints.test.mjs +77 -0
  310. package/lib/docs/tabAvailable.js +6 -5
  311. package/lib/docs/tabChannel.js +127 -21
  312. package/lib/docs/tabChannel.test.mjs +187 -29
  313. package/lib/docs/unsettledPageNote.js +24 -0
  314. package/lib/docs/validateJourneyMail.js +36 -0
  315. package/lib/docs/validateJourneyTimeout.js +38 -0
  316. package/lib/docs/validateJourneyTimeout.test.mjs +29 -0
  317. package/lib/docs/validateRecordingParam.js +42 -0
  318. package/lib/docs/validateStateSelection.js +33 -0
  319. package/lib/docs/validateStateSelection.test.mjs +33 -0
  320. package/lib/docs/validateViewport.js +51 -0
  321. package/lib/docs/validateViewport.test.mjs +54 -0
  322. package/lib/docs/waitForBuild.js +50 -0
  323. package/lib/docs/waitForBuild.test.mjs +75 -0
  324. package/lib/docs/waitForJourneyEmail.js +77 -0
  325. package/lib/docs/withBrowserSlot.js +45 -0
  326. package/lib/docs/withBrowserSlot.test.mjs +75 -0
  327. package/lib/docs/withDataSession.js +53 -0
  328. package/lib/server/applyDataSetRedirect.js +92 -0
  329. package/lib/server/applyDataSetRedirect.test.mjs +446 -0
  330. package/lib/server/applyDataSetRedirectTenantReach.test.mjs +207 -0
  331. package/lib/server/applyDataSetRedirectWalled.test.mjs +134 -0
  332. package/lib/server/auth/createSystemContext.js +73 -0
  333. package/lib/server/auth/{getAuthConfig.js → getAuth.js} +15 -9
  334. package/lib/server/auth/getHeadlessUser.js +9 -8
  335. package/lib/server/auth/getHeadlessUser.test.mjs +41 -0
  336. package/lib/server/auth/getMockUser.js +18 -14
  337. package/lib/server/auth/{session.js → getStrategies.js} +17 -16
  338. package/lib/server/auth/headlessUser.js +3 -2
  339. package/lib/server/auth/journeyActor.js +35 -0
  340. package/lib/server/auth/resolveHeadlessUser.test.mjs +0 -4
  341. package/lib/server/checkPageRecord.js +44 -0
  342. package/lib/server/contextMapBudget.js +24 -0
  343. package/lib/server/contextMapBudget.test.mjs +98 -0
  344. package/lib/server/continueJitKeys.js +28 -0
  345. package/lib/server/continueJitKeys.test.mjs +43 -0
  346. package/lib/server/createDataSessionEndedError.js +33 -0
  347. package/lib/server/createDataSetRefusal.js +26 -0
  348. package/lib/server/createLowdefyContext.js +168 -41
  349. package/lib/server/createLowdefyContext.test.mjs +331 -8
  350. package/lib/server/getClientAddress.js +51 -0
  351. package/lib/server/getClientAddress.test.mjs +56 -0
  352. package/lib/server/getPageJitEnrichment.test.mjs +29 -11
  353. package/lib/server/hashConfigContent.js +26 -0
  354. package/lib/server/importAppCode.test.mjs +57 -0
  355. package/lib/server/isRoutineStep.js +27 -0
  356. package/lib/server/jitPageBuilder.js +309 -126
  357. package/lib/server/jitPageBuilder.skipPluginChecks.test.mjs +78 -0
  358. package/lib/server/jitPageBuilder.test.mjs +218 -0
  359. package/lib/server/journeyCookies/JOURNEY_COOKIES.js +37 -0
  360. package/lib/server/journeyCookies/forwardJourneyCookies.js +46 -0
  361. package/lib/server/journeyCookies/readJourneyCookie.js +52 -0
  362. package/lib/server/journeyCookies/writeJourneyCookie.js +46 -0
  363. package/lib/server/journeyCookies.js +22 -0
  364. package/lib/server/journeyCookies.test.mjs +145 -0
  365. package/lib/server/loadDynamicJsMap.js +47 -0
  366. package/lib/server/log/createHandleError.js +38 -2
  367. package/lib/server/log/createHandleError.test.mjs +125 -2
  368. package/lib/server/log/createLogger.js +3 -0
  369. package/lib/server/log/createLogger.test.mjs +15 -0
  370. package/lib/server/log/logRequest.js +2 -3
  371. package/lib/server/mutantDataSetWrappers.test.mjs +115 -0
  372. package/lib/server/mutants/applyMutant.js +39 -0
  373. package/lib/server/mutants/artifactPatterns.js +25 -0
  374. package/lib/server/mutants/buildAnchor.js +45 -0
  375. package/lib/server/mutants/collectSharedIds.js +41 -0
  376. package/lib/server/mutants/configPathSegments.js +38 -0
  377. package/lib/server/mutants/createMutantReadConfigFile.js +42 -0
  378. package/lib/server/mutants/describeEvent.js +22 -0
  379. package/lib/server/mutants/enumerateArtifact.js +56 -0
  380. package/lib/server/mutants/findNodeByKey.js +45 -0
  381. package/lib/server/mutants/findNodeByPointer.js +49 -0
  382. package/lib/server/mutants/getArtifactKind.js +32 -0
  383. package/lib/server/mutants/groupMutantCopies.js +56 -0
  384. package/lib/server/mutants/hashArtifact.js +25 -0
  385. package/lib/server/mutants/hashMutantTarget.js +34 -0
  386. package/lib/server/mutants/listMutants.js +122 -0
  387. package/lib/server/mutants/listMutants.test.mjs +502 -0
  388. package/lib/server/mutants/mutantId.js +30 -0
  389. package/lib/server/mutants/mutantRuns.js +57 -0
  390. package/lib/server/mutants/mutantRuns.test.mjs +136 -0
  391. package/lib/server/mutants/operators/dropAction.js +47 -0
  392. package/lib/server/mutants/operators/dropBlock.js +44 -0
  393. package/lib/server/mutants/operators/dropPayload.js +52 -0
  394. package/lib/server/mutants/operators/dropStep.js +44 -0
  395. package/lib/server/mutants/operators/flipVisible.js +53 -0
  396. package/lib/server/mutants/operators/index.js +37 -0
  397. package/lib/server/mutants/operators/retargetLink.js +72 -0
  398. package/lib/server/mutants/operators/skipValidate.js +77 -0
  399. package/lib/server/mutants/operators/swapIf.js +112 -0
  400. package/lib/server/mutants/operators.test.mjs +396 -0
  401. package/lib/server/mutants/positionFreePath.js +38 -0
  402. package/lib/server/mutants/removeFromParent.js +28 -0
  403. package/lib/server/mutants/test/fixtures.mjs +173 -0
  404. package/lib/server/mutants/test/keyArtifact.mjs +63 -0
  405. package/lib/server/mutants/validateMutantParam.js +69 -0
  406. package/lib/server/mutants/validateMutantsBody.js +88 -0
  407. package/lib/server/mutants/walkArtifact.js +179 -0
  408. package/lib/server/pageBuildRecords.js +131 -0
  409. package/lib/server/pageBuildRecords.test.mjs +258 -0
  410. package/lib/server/pageCache.mjs +22 -7
  411. package/lib/server/pageCache.test.mjs +22 -7
  412. package/lib/server/pageRebuilds.test.mjs +543 -0
  413. package/lib/server/pruneJitMaps.js +36 -0
  414. package/lib/server/pruneJitMaps.test.mjs +55 -0
  415. package/lib/server/readInstanceRecord.js +33 -0
  416. package/lib/server/readInstanceRecord.test.mjs +99 -0
  417. package/lib/server/readMergedMaps.js +86 -0
  418. package/lib/server/readMergedMaps.test.mjs +79 -0
  419. package/lib/server/recording/appendRecords.js +33 -0
  420. package/lib/server/recording/errorRecordingStamp.js +34 -0
  421. package/lib/server/recording/errorRecordingStamp.test.mjs +31 -0
  422. package/lib/server/recording/isRecordingEnabled.js +31 -0
  423. package/lib/server/recording/readRecordingCookie.js +71 -0
  424. package/lib/server/recording/readRecordingCookie.test.mjs +73 -0
  425. package/lib/server/recording/recordingCookiePayload.js +33 -0
  426. package/lib/server/recording/servedBuilds.js +46 -0
  427. package/lib/server/recording/servedBuilds.test.mjs +42 -0
  428. package/lib/server/sinkExternalConnections.js +95 -0
  429. package/lib/server/sinkExternalConnections.test.mjs +135 -0
  430. package/lib/server/skipStaleMapWrites.js +62 -0
  431. package/lib/server/skipStaleMapWrites.test.mjs +113 -0
  432. package/lib/server/startIdleGc.js +67 -0
  433. package/lib/server/startIdleGc.test.mjs +138 -0
  434. package/lib/server/testFixtures/importAppCodeTwice.mjs +70 -0
  435. package/lib/server/trackSessionWork.js +30 -0
  436. package/lib/vite/createDevServerExclude.js +40 -0
  437. package/lib/vite/createDevServerExclude.test.mjs +79 -0
  438. package/manager/getContext.mjs +25 -3
  439. package/manager/processes/initialBuild.mjs +17 -1
  440. package/manager/processes/initialBuild.test.mjs +104 -0
  441. package/manager/processes/lowdefyBuild.mjs +31 -7
  442. package/manager/processes/lowdefyBuild.skipPluginChecks.test.mjs +72 -0
  443. package/manager/processes/lowdefyBuild.test.mjs +136 -1
  444. package/manager/processes/optimizeDependencies.mjs +59 -0
  445. package/manager/processes/restartServer.mjs +34 -6
  446. package/manager/processes/restartServer.test.mjs +162 -0
  447. package/manager/processes/shutdownServer.mjs +11 -0
  448. package/manager/processes/shutdownServer.test.mjs +45 -0
  449. package/manager/processes/startFirstServer.mjs +46 -0
  450. package/manager/processes/startFirstServer.test.mjs +326 -0
  451. package/manager/processes/startMailSink.mjs +135 -0
  452. package/manager/processes/startMailSink.test.mjs +146 -0
  453. package/manager/processes/startProxy.mjs +129 -12
  454. package/manager/processes/startProxy.test.mjs +366 -0
  455. package/manager/processes/startServer.mjs +47 -6
  456. package/manager/processes/startServer.test.mjs +155 -0
  457. package/manager/processes/startWatchers.mjs +6 -4
  458. package/manager/processes/syncServer.mjs +69 -0
  459. package/manager/processes/syncServer.test.mjs +141 -0
  460. package/manager/processes/waitForDevServer.mjs +48 -0
  461. package/manager/processes/waitForDevServer.test.mjs +112 -0
  462. package/manager/run.mjs +128 -43
  463. package/manager/utils/BatchChanges.mjs +21 -5
  464. package/manager/utils/BatchChanges.test.mjs +77 -0
  465. package/manager/utils/acquireDevInstance.mjs +99 -0
  466. package/manager/utils/acquireDevInstance.test.mjs +160 -0
  467. package/manager/utils/createBuildActivity.mjs +67 -0
  468. package/manager/utils/createBuildActivity.test.mjs +58 -0
  469. package/manager/utils/createChangeSignal.mjs +39 -0
  470. package/manager/utils/createChangeSignal.test.mjs +62 -0
  471. package/manager/utils/createCustomPluginMessagesMap.mjs +23 -33
  472. package/manager/utils/createCustomPluginTypesMap.mjs +24 -30
  473. package/manager/utils/createCustomPluginTypesMap.test.mjs +94 -0
  474. package/manager/utils/createDotPathIgnore.mjs +44 -0
  475. package/manager/utils/createDotPathIgnore.test.mjs +65 -0
  476. package/manager/utils/createIdleGc.mjs +51 -0
  477. package/manager/utils/createIdleGc.test.mjs +77 -0
  478. package/manager/utils/createRequestActivity.mjs +100 -0
  479. package/manager/utils/createRequestActivity.test.mjs +121 -0
  480. package/manager/utils/createServerArtifactTracker.mjs +83 -0
  481. package/manager/utils/createServerArtifactTracker.test.mjs +78 -0
  482. package/manager/utils/createServerEnv.mjs +39 -0
  483. package/manager/utils/filesHaveSameBytes.mjs +41 -0
  484. package/manager/utils/findBuildFilesOutsideWatch.mjs +59 -0
  485. package/manager/utils/findBuildFilesOutsideWatch.test.mjs +111 -0
  486. package/manager/utils/importFresh.mjs +46 -0
  487. package/manager/utils/importFresh.test.mjs +72 -0
  488. package/manager/utils/importFreshWorker.mjs +20 -0
  489. package/manager/utils/killTaggedBrowser.mjs +35 -0
  490. package/manager/utils/pagePathChanged.mjs +56 -0
  491. package/manager/utils/pruneRecordings.mjs +85 -0
  492. package/manager/utils/pruneRecordings.test.mjs +117 -0
  493. package/manager/utils/publishBuildDirectory.mjs +121 -0
  494. package/manager/utils/publishBuildDirectory.order.test.mjs +74 -0
  495. package/manager/utils/publishBuildDirectory.test.mjs +262 -0
  496. package/manager/utils/readBasePath.mjs +27 -0
  497. package/manager/utils/readBasePath.test.mjs +43 -0
  498. package/manager/utils/readBuildStatusWait.mjs +67 -0
  499. package/manager/utils/readManagerVersion.mjs +30 -0
  500. package/manager/utils/readManagerVersion.test.mjs +35 -0
  501. package/manager/utils/readPluginDefinitions.mjs +36 -0
  502. package/manager/utils/resolveDevAuthUrl.mjs +58 -0
  503. package/manager/utils/resolveDevAuthUrl.test.mjs +107 -0
  504. package/manager/utils/resolvePorts.mjs +49 -0
  505. package/manager/utils/setupWatcher.mjs +19 -11
  506. package/manager/utils/startRecordingPruner.mjs +45 -0
  507. package/manager/utils/startRecordingPruner.test.mjs +66 -0
  508. package/manager/utils/waitForChildExit.mjs +53 -0
  509. package/manager/utils/waitForChildExit.test.mjs +70 -0
  510. package/manager/utils/waitForServer.mjs +44 -0
  511. package/manager/utils/writeBuildStatus.mjs +1 -1
  512. package/manager/watchers/createPluginSourceIgnore.mjs +77 -0
  513. package/manager/watchers/envWatcher.mjs +9 -2
  514. package/manager/watchers/envWatcher.test.mjs +87 -0
  515. package/manager/watchers/listServerSidePackages.mjs +47 -0
  516. package/manager/watchers/listServerSidePackages.test.mjs +38 -0
  517. package/manager/watchers/lowdefyBuildWatcher.mjs +106 -22
  518. package/manager/watchers/lowdefyBuildWatcher.test.mjs +309 -0
  519. package/manager/watchers/pluginSourceWatcher.mjs +79 -0
  520. package/manager/watchers/pluginSourceWatcher.test.mjs +222 -0
  521. package/manager/watchers/restartRequestWatcher.mjs +71 -0
  522. package/manager/watchers/restartRequestWatcher.test.mjs +111 -0
  523. package/manager/watchers/selectLocalPluginPackages.mjs +48 -0
  524. package/manager/watchers/selectLocalPluginPackages.test.mjs +73 -0
  525. package/manager/watchers/serverPackageWatcher.mjs +38 -0
  526. package/package.json +60 -47
  527. package/package.original.json +60 -47
  528. package/src/app.js +131 -20
  529. package/src/html/renderDevPage.js +63 -11
  530. package/src/html/renderDevPage.test.mjs +138 -0
  531. package/src/lib/getRequestPath.js +29 -0
  532. package/src/middleware/createSameOriginGuard.js +37 -0
  533. package/src/middleware/createSameOriginGuard.test.mjs +102 -0
  534. package/src/middleware/errorHandler.js +51 -1
  535. package/src/middleware/errorHandler.test.mjs +173 -3
  536. package/src/middleware/isRebindingSafeHost.js +44 -0
  537. package/src/middleware/isRebindingSafeHost.test.mjs +32 -0
  538. package/src/middleware/localDevToolsOnly.js +38 -0
  539. package/src/middleware/localDevToolsOnly.test.mjs +61 -0
  540. package/src/middleware/noStore.js +27 -0
  541. package/src/middleware/noStore.test.mjs +48 -0
  542. package/src/middleware/requestTimeout.js +38 -0
  543. package/src/middleware/staleFlag.js +76 -0
  544. package/src/middleware/staleFlag.test.mjs +127 -0
  545. package/src/middleware/stampCaller.js +38 -0
  546. package/src/middleware/stampCaller.test.mjs +64 -0
  547. package/src/routes/agent.js +15 -3
  548. package/src/routes/auth.js +29 -20
  549. package/src/routes/auth.test.mjs +30 -56
  550. package/src/routes/clientError.js +13 -13
  551. package/src/routes/cron.js +3 -1
  552. package/src/routes/cronForward.js +3 -1
  553. package/src/routes/detached.js +22 -9
  554. package/src/routes/devInspect.js +17 -8
  555. package/src/routes/devInspect.test.mjs +44 -7
  556. package/src/routes/devRecording.js +113 -0
  557. package/src/routes/devRecording.test.mjs +253 -0
  558. package/src/routes/docs/buildStatus.js +10 -2
  559. package/src/routes/docs/buildStatus.test.mjs +46 -0
  560. package/src/routes/docs/content.js +5 -3
  561. package/src/routes/docs/evalOperator.js +15 -1
  562. package/src/routes/docs/events.js +50 -0
  563. package/src/routes/docs/events.test.mjs +117 -0
  564. package/src/routes/docs/find.js +9 -1
  565. package/src/routes/docs/icons.js +30 -0
  566. package/src/routes/docs/inspectState.js +16 -1
  567. package/src/routes/docs/journey.js +194 -0
  568. package/src/routes/docs/journey.test.mjs +401 -0
  569. package/src/routes/docs/journeyRefusals.test.mjs +71 -0
  570. package/src/routes/docs/loadState.js +4 -2
  571. package/src/routes/docs/mcp.js +34 -3
  572. package/src/routes/docs/mcp.test.mjs +130 -0
  573. package/src/routes/docs/mutants.js +78 -0
  574. package/src/routes/docs/mutants.test.mjs +180 -0
  575. package/src/routes/docs/parsePathParamsParam.js +41 -0
  576. package/src/routes/docs/parsePathParamsParam.test.mjs +42 -0
  577. package/src/routes/docs/parseUserParam.js +27 -21
  578. package/src/routes/docs/parseUserParam.test.mjs +17 -5
  579. package/src/routes/docs/restart.js +35 -0
  580. package/src/routes/docs/restart.test.mjs +64 -0
  581. package/src/routes/docs/runEndpoint.js +54 -0
  582. package/src/routes/docs/runEndpoint.test.mjs +165 -0
  583. package/src/routes/docs/runRequest.js +25 -8
  584. package/src/routes/docs/runRequest.test.mjs +92 -0
  585. package/src/routes/docs/screenshot.js +50 -2
  586. package/src/routes/docs/screenshot.test.mjs +117 -0
  587. package/src/routes/docs/search.js +15 -2
  588. package/src/routes/docs/snapshotState.js +17 -3
  589. package/src/routes/endpoints.js +27 -26
  590. package/src/routes/endpointsTextAnswer.test.mjs +77 -0
  591. package/src/routes/feedback.js +10 -13
  592. package/src/routes/jitPage.js +99 -35
  593. package/src/routes/jitPage.test.mjs +248 -0
  594. package/src/routes/mcp.js +82 -5
  595. package/src/routes/mcp.test.mjs +215 -0
  596. package/src/routes/mcpMemberToken.test.mjs +125 -0
  597. package/src/routes/mcpProtectedResourceMetadata.js +34 -0
  598. package/src/routes/mcpProtectedResourceMetadata.test.mjs +65 -0
  599. package/src/routes/mountDevPageRoutes.js +25 -0
  600. package/src/routes/mountDevPageRoutes.test.mjs +30 -0
  601. package/src/routes/mountOauthDiscovery.js +44 -0
  602. package/src/routes/mountOauthDiscovery.test.mjs +79 -0
  603. package/src/routes/normalizeMcpProtocolVersionHeader.js +46 -0
  604. package/src/routes/normalizeMcpProtocolVersionHeader.test.mjs +62 -0
  605. package/src/routes/reload.js +16 -0
  606. package/src/routes/request.js +6 -4
  607. package/src/routes/request.test.mjs +9 -5
  608. package/src/routes/requestBody.test.mjs +159 -0
  609. package/src/routes/usage.js +6 -2
  610. package/src/routes/user.js +27 -0
  611. package/src/routes/websocket.js +29 -8
  612. package/src/routes/websocket.test.mjs +65 -0
  613. package/src/routes/wellKnownFallback.js +30 -0
  614. package/src/routes/wellKnownFallback.test.mjs +32 -0
  615. package/src/websocket/devWebSocket.js +41 -26
  616. package/src/websocket/devWebSocket.test.mjs +157 -0
  617. package/vite.config.js +53 -23
  618. package/lib/server/auth/getDevSession.js +0 -70
  619. package/lib/server/auth/getDevSession.test.mjs +0 -138
  620. package/manager/processes/warnAuthUrlPortMismatch.mjs +0 -47
  621. package/manager/processes/warnAuthUrlPortMismatch.test.mjs +0 -93
  622. package/manager/watchers/moduleBuildWatcher.mjs +0 -79
  623. package/manager/watchers/serverArtifactWatcher.mjs +0 -120
  624. package/src/routes/apiPage.js +0 -37
@@ -0,0 +1,127 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { jest } from '@jest/globals';
18
+
19
+ // searchIcons reads the dev build's iconAliases.json and customTypesMap.json,
20
+ // which only exist in a built server directory, and loads installed icon set
21
+ // plugins through the build's loader. Lucide's names are real.
22
+ const mockReadBuildArtifact = jest.fn();
23
+ jest.unstable_mockModule('./readBuildArtifact.js', () => ({
24
+ default: mockReadBuildArtifact,
25
+ }));
26
+
27
+ const buildDev = await import('@lowdefy/build/dev');
28
+ const mockLoadIconSets = jest.fn();
29
+ jest.unstable_mockModule('@lowdefy/build/dev', () => ({
30
+ ...buildDev,
31
+ loadIconSets: mockLoadIconSets,
32
+ }));
33
+
34
+ const { default: searchIcons } = await import('./searchIcons.js');
35
+
36
+ function layer(names) {
37
+ return { names: new Set(names) };
38
+ }
39
+
40
+ function mockArtifacts({ aliases = null, iconSets = {} } = {}) {
41
+ mockReadBuildArtifact.mockImplementation(({ name }) => {
42
+ if (name === 'iconAliases.json') return aliases;
43
+ if (name === 'customTypesMap.json') return { iconSets };
44
+ return null;
45
+ });
46
+ }
47
+
48
+ beforeEach(() => {
49
+ mockReadBuildArtifact.mockReset();
50
+ mockLoadIconSets.mockReset();
51
+ mockLoadIconSets.mockResolvedValue({ lucide: [layer([])] });
52
+ });
53
+
54
+ test('searchIcons lists matching semantic names, then canonical Lucide names, then aliases', async () => {
55
+ mockArtifacts({ aliases: { delete: 'Trash', edit: 'Pencil' } });
56
+ const result = await searchIcons({ query: 'trash' });
57
+ expect(result.semantic).toEqual([{ name: 'delete', icon: 'Trash' }]);
58
+ expect(result.icons[0]).toBe('Trash');
59
+ expect(result.icons).toContain('Trash2');
60
+ // Trash2 is a canonical name in Lucide 1.x; aliases come after every canonical name.
61
+ const { canonical } = buildDev.getLucideIconNames();
62
+ const firstAlias = result.icons.findIndex((name) => !canonical.includes(name));
63
+ if (firstAlias !== -1) {
64
+ expect(result.icons.slice(firstAlias).every((name) => !canonical.includes(name))).toBe(true);
65
+ }
66
+ });
67
+
68
+ test('searchIcons ranks canonical names before Lucide alias names', async () => {
69
+ mockArtifacts();
70
+ const result = await searchIcons({ query: 'house', limit: 100 });
71
+ expect(result.icons).toContain('House');
72
+ const home = await searchIcons({ query: 'home', limit: 100 });
73
+ // Home is an alias of House.
74
+ expect(home.icons).toContain('Home');
75
+ expect(home.icons.indexOf('Home')).toBeGreaterThan(home.icons.indexOf('HousePlus'));
76
+ });
77
+
78
+ test('searchIcons includes theme aliases from the build', async () => {
79
+ mockArtifacts({ aliases: { invoice: 'Receipt' } });
80
+ const result = await searchIcons({ query: 'invoice' });
81
+ expect(result.semantic).toEqual([{ name: 'invoice', icon: 'Receipt' }]);
82
+ });
83
+
84
+ test('searchIcons falls back to the built-in names before the first build', async () => {
85
+ mockArtifacts();
86
+ const result = await searchIcons({ query: 'external link' });
87
+ expect(result.semantic).toEqual([{ name: 'external-link', icon: 'ExternalLink' }]);
88
+ });
89
+
90
+ test('searchIcons lists installed set names qualified, after Lucide names', async () => {
91
+ const iconSets = { tabler: [{ package: '@acme/icons-tabler', version: '1.0.0' }] };
92
+ mockArtifacts({ iconSets });
93
+ mockLoadIconSets.mockResolvedValue({
94
+ lucide: [layer(['Pencil']), layer(['PencilInvoice'])],
95
+ tabler: [layer(['PencilBolt', 'Other'])],
96
+ });
97
+ const result = await searchIcons({ query: 'pencil', limit: 100 });
98
+ expect(result.icons[0]).toBe('Pencil');
99
+ // An icon a plugin adds to lucide is listed unqualified; other sets qualified.
100
+ expect(result.icons.slice(-2)).toEqual(['PencilInvoice', 'tabler:PencilBolt']);
101
+ expect(mockLoadIconSets).toHaveBeenCalledWith({
102
+ context: { directories: { server: process.cwd() }, typesMap: { iconSets } },
103
+ });
104
+ });
105
+
106
+ test('searchIcons requires every query word to match', async () => {
107
+ mockArtifacts();
108
+ const result = await searchIcons({ query: 'arrow right circle', limit: 50 });
109
+ expect(result.icons.length).toBeGreaterThan(0);
110
+ result.icons.forEach((name) => {
111
+ const lower = name.toLowerCase();
112
+ expect(lower).toContain('arrow');
113
+ expect(lower).toContain('right');
114
+ expect(lower).toContain('circle');
115
+ });
116
+ });
117
+
118
+ test('searchIcons caps icon names at the limit', async () => {
119
+ mockArtifacts();
120
+ expect((await searchIcons({ query: 'user', limit: 5 })).icons).toHaveLength(5);
121
+ });
122
+
123
+ test('searchIcons throws without a query', async () => {
124
+ await expect(searchIcons({ query: ' ' })).rejects.toThrow(
125
+ 'searchIcons requires a "query" string.'
126
+ );
127
+ });
@@ -0,0 +1,39 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { type } from '@lowdefy/helpers';
18
+
19
+ // What the lowdefy_run_journey tool returns of a run's `exercised` block.
20
+ // The full block lists every request, endpoint, event and rendered block, which
21
+ // is hundreds of lines for a short journey, so the tool returns a count of each
22
+ // by default. `selection` true returns the block whole, false leaves it out.
23
+ // Only the tool's response is trimmed: the REST route, and so `lowdefy test`'s
24
+ // exercised.json that the journey lints read, always carries the whole block.
25
+ function selectExercised({ exercised, selection }) {
26
+ if (selection === true || type.isNone(exercised)) {
27
+ return { exercised };
28
+ }
29
+ if (selection === false) {
30
+ return {};
31
+ }
32
+ const counts = {};
33
+ Object.entries(exercised).forEach(([key, value]) => {
34
+ if (type.isArray(value)) counts[key] = value.length;
35
+ });
36
+ return { exercised: { counts, note: 'Pass exercised: true for the full block.' } };
37
+ }
38
+
39
+ export default selectExercised;
@@ -0,0 +1,66 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { get, type } from '@lowdefy/helpers';
18
+
19
+ // The final state is returned whole when it is small. A page holding request
20
+ // results or long lists can hold hundreds of KB, more than an agent's context
21
+ // should take on every journey, so above this it is summarised instead.
22
+ const DEFAULT_STATE_LIMIT = 10_000;
23
+
24
+ function summariseState({ state, characters }) {
25
+ const keys = Object.fromEntries(
26
+ Object.entries(state)
27
+ .map(([key, value]) => [key, (JSON.stringify(value) ?? '').length])
28
+ .sort(([, a], [, b]) => b - a)
29
+ );
30
+ return {
31
+ characters,
32
+ keys,
33
+ note: `The final state is ${characters} characters, over the ${DEFAULT_STATE_LIMIT} returned by default. keys lists each top-level key with its size. Pass state: ["path", ...] for the paths you need, or state: true for all of it.`,
34
+ };
35
+ }
36
+
37
+ // Picks what the journey result carries of the final page state:
38
+ // false nothing
39
+ // true the whole state
40
+ // [path, ...] { [path]: value } for each path, null where it is undefined
41
+ // undefined the whole state when it is at most DEFAULT_STATE_LIMIT
42
+ // characters of JSON, otherwise `stateOmitted`, a summary of
43
+ // its size and top-level keys
44
+ // A state that could not be read ({ error }) is returned as is, since the
45
+ // error is what the caller needs to see.
46
+ function selectFinalState({ state, selection }) {
47
+ if (selection === false) {
48
+ return {};
49
+ }
50
+ if (selection === true || !type.isObject(state) || !type.isUndefined(state.error)) {
51
+ return { state };
52
+ }
53
+ if (type.isArray(selection)) {
54
+ return {
55
+ state: Object.fromEntries(selection.map((path) => [path, get(state, path) ?? null])),
56
+ };
57
+ }
58
+ const characters = JSON.stringify(state).length;
59
+ if (characters <= DEFAULT_STATE_LIMIT) {
60
+ return { state };
61
+ }
62
+ return { stateOmitted: summariseState({ state, characters }) };
63
+ }
64
+
65
+ export { DEFAULT_STATE_LIMIT };
66
+ export default selectFinalState;
@@ -0,0 +1,51 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import selectFinalState, { DEFAULT_STATE_LIMIT } from './selectFinalState.js';
18
+
19
+ test('selectFinalState returns a small state whole by default', () => {
20
+ expect(selectFinalState({ state: { a: 1 } })).toEqual({ state: { a: 1 } });
21
+ });
22
+
23
+ test('selectFinalState summarises a state over the limit, largest key first', () => {
24
+ const state = { small: 1, large: 'x'.repeat(DEFAULT_STATE_LIMIT) };
25
+ const { state: returned, stateOmitted } = selectFinalState({ state });
26
+ expect(returned).toBeUndefined();
27
+ expect(stateOmitted.characters).toEqual(JSON.stringify(state).length);
28
+ expect(Object.entries(stateOmitted.keys)).toEqual([
29
+ ['large', DEFAULT_STATE_LIMIT + 2],
30
+ ['small', 1],
31
+ ]);
32
+ });
33
+
34
+ test('selectFinalState returns the paths asked for, null where undefined', () => {
35
+ const state = { form: { name: 'Ada', tags: [] }, flag: false };
36
+ expect(selectFinalState({ state, selection: ['form.name', 'flag', 'form.missing'] })).toEqual({
37
+ state: { 'form.name': 'Ada', flag: false, 'form.missing': null },
38
+ });
39
+ });
40
+
41
+ test('selectFinalState returns nothing for false and everything for true', () => {
42
+ const state = { large: 'x'.repeat(DEFAULT_STATE_LIMIT * 2) };
43
+ expect(selectFinalState({ state, selection: false })).toEqual({});
44
+ expect(selectFinalState({ state, selection: true })).toEqual({ state });
45
+ });
46
+
47
+ test('selectFinalState passes a state read error through whatever was selected', () => {
48
+ const state = { error: 'Could not read final state: Target closed' };
49
+ expect(selectFinalState({ state, selection: ['a'] })).toEqual({ state });
50
+ expect(selectFinalState({ state })).toEqual({ state });
51
+ });
@@ -0,0 +1,54 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { publish } from './devEventBus.js';
18
+ import getBuildId from './getBuildId.js';
19
+ import isRunErrorEntry from './isRunErrorEntry.js';
20
+ import { recordRunError } from './runErrorBuffers.js';
21
+
22
+ // Module-level ring buffer of recent server-side errors (request, endpoint, MCP
23
+ // and agent tool failures) — feeds the getBuildStatus feedback endpoint so
24
+ // agents can see server errors, with their config source, without tailing
25
+ // server logs. Deliberately in-memory only: entries are lost on server
26
+ // restart, which is fine since this is a live-session debugging aid.
27
+ const MAX_ENTRIES = 50;
28
+
29
+ const entries = [];
30
+
31
+ // Each entry is stamped with the build it happened under (see getBuildId).
32
+ // An entry a journey run caused goes to that run's own
33
+ // buffer only (see runErrorBuffers), or is dropped when the run has ended:
34
+ // kept out of this ring and the event bus, it never reaches build-status or
35
+ // an agent's event stream, and a run that trips many errors cannot evict the
36
+ // developer's own. The run reports its errors in its own result.
37
+ function push(entry) {
38
+ const stamped = { ...entry, buildId: getBuildId() };
39
+ if (isRunErrorEntry(stamped)) {
40
+ recordRunError({ ...stamped, store: 'server' });
41
+ return;
42
+ }
43
+ entries.push(stamped);
44
+ if (entries.length > MAX_ENTRIES) {
45
+ entries.shift();
46
+ }
47
+ publish({ type: 'server_error', ...stamped });
48
+ }
49
+
50
+ function list() {
51
+ return [...entries];
52
+ }
53
+
54
+ export default { push, list };
@@ -0,0 +1,50 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+ import { jest } from '@jest/globals';
17
+
18
+ const mockPublish = jest.fn();
19
+ jest.unstable_mockModule('./devEventBus.js', () => ({
20
+ publish: mockPublish,
21
+ }));
22
+ jest.unstable_mockModule('./getBuildId.js', () => ({
23
+ default: () => '2026-01-01T00:00:00.000Z',
24
+ }));
25
+
26
+ const { default: serverErrorStore } = await import('./serverErrorStore.js');
27
+
28
+ const buildId = '2026-01-01T00:00:00.000Z';
29
+
30
+ test('serverErrorStore push adds an entry stamped with the current build, list returns it, and it is published as a server_error event', () => {
31
+ serverErrorStore.push({ message: 'first' });
32
+ expect(serverErrorStore.list()).toEqual([{ message: 'first', buildId }]);
33
+ expect(mockPublish).toHaveBeenCalledWith({ type: 'server_error', message: 'first', buildId });
34
+ });
35
+
36
+ test('serverErrorStore list returns a copy that does not alias the store', () => {
37
+ const entries = serverErrorStore.list();
38
+ entries.push({ message: 'not stored' });
39
+ expect(serverErrorStore.list()).toEqual([{ message: 'first', buildId }]);
40
+ });
41
+
42
+ test('serverErrorStore caps at 50 entries and drops the oldest', () => {
43
+ for (let i = 0; i < 60; i++) {
44
+ serverErrorStore.push({ index: i });
45
+ }
46
+ const entries = serverErrorStore.list();
47
+ expect(entries.length).toEqual(50);
48
+ expect(entries[0].index).toEqual(10);
49
+ expect(entries[49].index).toEqual(59);
50
+ });
@@ -33,10 +33,15 @@ function setupTestFixtures() {
33
33
 
34
34
  // lib/build/config.js reads this at import time (screenshotPage → basePath).
35
35
  write('build/config.json', {});
36
+ write('build/routes.json', [{ pageId: 'home', path: 'home' }]);
36
37
 
37
38
  write('build/plugins/availableTypes.json', {
38
39
  actions: {
39
- SetState: { package: '@lowdefy/actions-core', originalTypeName: 'SetState', version: '5.0.0' },
40
+ SetState: {
41
+ package: '@lowdefy/actions-core',
42
+ originalTypeName: 'SetState',
43
+ version: '5.0.0',
44
+ },
40
45
  },
41
46
  agents: {},
42
47
  auth: { adapters: {}, callbacks: {}, events: {}, providers: {} },
@@ -58,7 +63,11 @@ function setupTestFixtures() {
58
63
  },
59
64
  server: {
60
65
  _get: { package: '@lowdefy/operators-js', originalTypeName: '_get', version: '5.0.0' },
61
- _secret: { package: '@lowdefy/operators-js', originalTypeName: '_secret', version: '5.0.0' },
66
+ _secret: {
67
+ package: '@lowdefy/operators-js',
68
+ originalTypeName: '_secret',
69
+ version: '5.0.0',
70
+ },
62
71
  },
63
72
  },
64
73
  requests: {
@@ -105,23 +114,52 @@ function setupTestFixtures() {
105
114
  TestBlock: { type: 'object', properties: {} },
106
115
  });
107
116
  write('build/plugins/blockMetas.json', {
108
- Button: { category: 'display' },
109
- TestBlock: { category: 'input', valueType: 'string' },
117
+ Button: { category: 'display', hazards: [] },
118
+ TestBlock: {
119
+ category: 'input',
120
+ valueType: 'string',
121
+ hazards: [
122
+ { id: 'test-block-hazard', message: 'TestBlock does something surprising.', see: null },
123
+ // Same id as a framework hazard — getHazards must de-duplicate, keeping
124
+ // the plugin's own wording.
125
+ {
126
+ id: 'visible-false-prunes-state',
127
+ message: 'Plugin wording for visible: false.',
128
+ see: null,
129
+ },
130
+ ],
131
+ },
110
132
  });
111
133
  write('build/plugins/actionSchemas.json', { SetState: { type: 'object' } });
112
134
  write('build/plugins/operatorSchemas.json', { _get: { params: { type: 'object' } } });
135
+ write('build/plugins/operatorMetas.json', {
136
+ _get: {
137
+ hazards: [
138
+ { id: 'get-fixture-hazard', message: '_get fixture hazard.', see: 'operators/_get' },
139
+ ],
140
+ },
141
+ });
113
142
  write('build/plugins/connectionSchemas.json', {
114
143
  AxiosHttp: { schema: { type: 'object' }, requests: ['AxiosHttp'] },
115
144
  });
116
145
  write('build/plugins/requestSchemas.json', {
117
146
  AxiosHttp: { schema: { type: 'object' }, meta: { checkRead: false, checkWrite: false } },
118
147
  ReadOnlyRequest: { schema: { type: 'object' }, meta: { checkRead: true, checkWrite: false } },
119
- WriteRequest: { schema: { type: 'object' }, meta: { checkRead: true, checkWrite: true } },
148
+ WriteRequest: {
149
+ schema: { type: 'object' },
150
+ meta: {
151
+ checkRead: true,
152
+ checkWrite: true,
153
+ hazards: [{ id: 'write-request-hazard', message: 'Writes are final.', see: null }],
154
+ },
155
+ },
120
156
  // No "meta" at all — some custom plugins don't declare it; treated as unknown.
121
157
  UnknownMetaRequest: { schema: { type: 'object' } },
122
158
  });
123
159
  write('build/customTypesMap.json', {
124
- blocks: { TestBlock: { package: 'test-plugin', originalTypeName: 'TestBlock', version: '1.0.0' } },
160
+ blocks: {
161
+ TestBlock: { package: 'test-plugin', originalTypeName: 'TestBlock', version: '1.0.0' },
162
+ },
125
163
  });
126
164
  write('build/installedPluginPackages.json', [
127
165
  '@lowdefy/blocks-antd',
@@ -139,7 +177,7 @@ function setupTestFixtures() {
139
177
  );
140
178
 
141
179
  // Feedback-loop fixtures — build status, and a JIT-built page's registry,
142
- // keyMap and refMap entries (see packages/build/src/build/addKeys.js for
180
+ // key map and ref map entries (see packages/build/src/build/addKeys.js for
143
181
  // the key path format: `[index:id]` / `[index:id:Type]`).
144
182
  write('build/buildStatus.json', {
145
183
  status: 'ok',
@@ -155,12 +193,12 @@ function setupTestFixtures() {
155
193
  unbuilt: { pageId: 'unbuilt', auth: null, refId: 'ref-unbuilt', refPath: 'pages/unbuilt.yaml' },
156
194
  });
157
195
 
158
- // Mirrors the real dev keyMap shape: the skeleton build keys the whole
159
- // config (`root.pages[N:id]` page stubs only), while each JIT page build
160
- // runs addKeys on the page object itself — so page CONTENT keys are
161
- // `root.blocks[...]` with no page segment, one `root`-keyed subtree per
162
- // built page, distinguishable only via the ~k_parent chain. Tree roots
163
- // reference a parent id that is never written to keyMap.
196
+ // Mirrors the real dev key map shape: the skeleton build keys the whole
197
+ // config (`root.pages[N:id]` page stubs only) in keyMap.json, while each JIT
198
+ // page build runs addKeys on the page object itself and writes its keys to a
199
+ // jitMaps/ file — so page CONTENT keys are `root.blocks[...]` with no page
200
+ // segment, one `root`-keyed subtree per built page, distinguishable only via
201
+ // the ~k_parent chain. Tree roots reference a parent id that is never written.
164
202
  write('build/keyMap.json', {
165
203
  'key-root': { key: 'root', '~k_parent': 'key-root-parent' },
166
204
  'key-pages': { key: 'root.pages', '~k_parent': 'key-root', '~r': 'ref-home' },
@@ -176,32 +214,6 @@ function setupTestFixtures() {
176
214
  '~r': 'ref-other',
177
215
  '~l': 1,
178
216
  },
179
- // JIT subtree for page "home".
180
- 'key-home': { key: 'root', '~k_parent': 'key-home-jit-parent', '~r': 'ref-home', '~l': 1 },
181
- 'key-button': {
182
- key: 'root.blocks[2:my_button:Button]',
183
- '~k_parent': 'key-home',
184
- '~r': 'ref-home',
185
- '~l': 5,
186
- },
187
- // List item block — config ids inside lists carry the `$` placeholder;
188
- // runtime block ids have array indices applied (`my_list.0.item_title`).
189
- 'key-list-item': {
190
- key: 'root.blocks[3:my_list:List].blocks[0:my_list.$.item_title:Title]',
191
- '~k_parent': 'key-home',
192
- '~r': 'ref-home',
193
- '~l': 9,
194
- },
195
- // JIT subtree for page "other" — holds a block id that also exists on
196
- // home, with an IDENTICAL key shape. findConfig must scope pageId scans
197
- // via the ~k_parent chain, not the key path.
198
- 'key-other': { key: 'root', '~k_parent': 'key-other-jit-parent', '~r': 'ref-other', '~l': 1 },
199
- 'key-other-button': {
200
- key: 'root.blocks[0:my_button:Button]',
201
- '~k_parent': 'key-other',
202
- '~r': 'ref-other',
203
- '~l': 4,
204
- },
205
217
  // Skeleton-built page (like the default 404) — content is keyed inside
206
218
  // the global config tree with the page segment inline, and chains to the
207
219
  // shared config root rather than a page-specific one.
@@ -219,6 +231,63 @@ function setupTestFixtures() {
219
231
  },
220
232
  });
221
233
 
234
+ // JIT subtree for page "home".
235
+ write('build/jitMaps/abc123-1-1.json', {
236
+ keyMap: {
237
+ 'key-home': { key: 'root', '~k_parent': 'key-home-jit-parent', '~r': 'ref-home', '~l': 1 },
238
+ 'key-button': {
239
+ key: 'root.blocks[2:my_button:Button]',
240
+ '~k_parent': 'key-home',
241
+ '~r': 'ref-home',
242
+ '~l': 5,
243
+ },
244
+ // List item block — config ids inside lists carry the `$` placeholder;
245
+ // runtime block ids have array indices applied (`my_list.0.item_title`).
246
+ 'key-list-item': {
247
+ key: 'root.blocks[3:my_list:List].blocks[0:my_list.$.item_title:Title]',
248
+ '~k_parent': 'key-home',
249
+ '~r': 'ref-home',
250
+ '~l': 9,
251
+ },
252
+ },
253
+ refMap: {},
254
+ });
255
+
256
+ // JIT subtree for page "other" — holds a block id that also exists on
257
+ // home, with an IDENTICAL key shape. findConfig must scope pageId scans
258
+ // via the ~k_parent chain, not the key path.
259
+ write('build/jitMaps/abc123-1-2.json', {
260
+ keyMap: {
261
+ 'key-other': {
262
+ key: 'root',
263
+ '~k_parent': 'key-other-jit-parent',
264
+ '~r': 'ref-other',
265
+ '~l': 1,
266
+ },
267
+ 'key-other-button': {
268
+ key: 'root.blocks[0:my_button:Button]',
269
+ '~k_parent': 'key-other',
270
+ '~r': 'ref-other',
271
+ '~l': 4,
272
+ },
273
+ // Requests over a tenant-walled and a shared connection — findConfig
274
+ // resolves connectionId from the per-request artifact for hazards.
275
+ 'key-other-req-tenant': {
276
+ key: 'root.requests[0:req-tenant:WriteRequest]',
277
+ '~k_parent': 'key-other',
278
+ '~r': 'ref-other',
279
+ '~l': 12,
280
+ },
281
+ 'key-other-req-shared': {
282
+ key: 'root.requests[1:req-shared:WriteRequest]',
283
+ '~k_parent': 'key-other',
284
+ '~r': 'ref-other',
285
+ '~l': 18,
286
+ },
287
+ },
288
+ refMap: {},
289
+ });
290
+
222
291
  write('build/refMap.json', {
223
292
  'ref-home': { parent: null, path: 'pages/home.yaml' },
224
293
  'ref-other': { parent: null, path: 'pages/other.yaml' },
@@ -227,6 +296,9 @@ function setupTestFixtures() {
227
296
 
228
297
  write('build/pages/home.json', {
229
298
  pageId: 'home',
299
+ // The skeleton build keys the page's auth, chaining to the global root —
300
+ // a real artifact carries this before any JIT-keyed block.
301
+ auth: { public: true, '~k': 'key-home-stub' },
230
302
  blocks: [
231
303
  { id: 'my_button', type: 'Button', '~k': 'key-button' },
232
304
  {
@@ -285,6 +357,28 @@ function setupTestFixtures() {
285
357
  properties: {},
286
358
  });
287
359
 
360
+ write('build/pages/other/requests/req-tenant.json', {
361
+ id: 'request:other:req-tenant',
362
+ requestId: 'req-tenant',
363
+ pageId: 'other',
364
+ connectionId: 'tenant_db',
365
+ type: 'WriteRequest',
366
+ payload: {},
367
+ properties: {},
368
+ });
369
+ write('build/pages/other/requests/req-shared.json', {
370
+ id: 'request:other:req-shared',
371
+ requestId: 'req-shared',
372
+ pageId: 'other',
373
+ connectionId: 'shared_db',
374
+ type: 'WriteRequest',
375
+ payload: {},
376
+ properties: {},
377
+ });
378
+ // Index of walled connections (packages/build/src/build/full/writeConnections.js)
379
+ // — shared connections are never listed here.
380
+ write('build/tenantConnections.json', [{ connectionId: 'tenant_db', type: 'MongoDBCollection' }]);
381
+
288
382
  write('build/connectionIds.json', ['axios']);
289
383
  write('build/connections/axios.json', {
290
384
  id: 'connection:axios',
@@ -0,0 +1,28 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ // The name the journey runner's selector engine is registered under
18
+ // (registerShownTextEngine.js).
19
+ const SHOWN_TEXT_ENGINE = 'lowdefy-shown-text';
20
+
21
+ // The selector for a `containing` target. The body is a JSON string, so `>>` or
22
+ // quotes in the text never split the selector.
23
+ function shownTextSelector(text) {
24
+ return `${SHOWN_TEXT_ENGINE}=${JSON.stringify(text)}`;
25
+ }
26
+
27
+ export { SHOWN_TEXT_ENGINE };
28
+ export default shownTextSelector;