@lowdefy/server-dev 6.1.0 → 7.0.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 (623) 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 +67 -0
  99. package/lib/docs/dataSets/dataSessionRegistry.js +25 -0
  100. package/lib/docs/dataSets/dataSetJourneys.chromium.mongodb.test.mjs +679 -0
  101. package/lib/docs/dataSets/describeDataSetResult.js +25 -0
  102. package/lib/docs/dataSets/describeDatabaseTarget.js +46 -0
  103. package/lib/docs/dataSets/deserializeFixture.js +34 -0
  104. package/lib/docs/dataSets/drainSessionWork.js +37 -0
  105. package/lib/docs/dataSets/drainSessionWork.test.mjs +64 -0
  106. package/lib/docs/dataSets/duplicateIndexName.js +24 -0
  107. package/lib/docs/dataSets/findCollectionCollisions.js +52 -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 +44 -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 +119 -0
  119. package/lib/docs/dataSets/openDataSession.mongodb.test.mjs +343 -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 +68 -0
  126. package/lib/docs/dataSets/readDataSet.test.mjs +220 -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/sweepOrphanDatabases.js +33 -0
  133. package/lib/docs/dataSets/test/app/lowdefy.yaml +203 -0
  134. package/lib/docs/dataSets/test/app/modules/archive/module.lowdefy.yaml +30 -0
  135. package/lib/docs/dataSets/test/app/tests/data/shop.yaml +8 -0
  136. package/lib/docs/dataSets/test/buildTestApp.mjs +43 -0
  137. package/lib/docs/devEventBus.js +136 -0
  138. package/lib/docs/devEventBus.test.mjs +269 -0
  139. package/lib/docs/devToolDefinitions.js +529 -0
  140. package/lib/docs/docSources.js +21 -0
  141. package/lib/docs/docs.test.mjs +171 -15
  142. package/lib/docs/docsIndex.test.mjs +377 -0
  143. package/lib/docs/evalOperator.js +5 -3
  144. package/lib/docs/evalOperatorHeadless.js +84 -12
  145. package/lib/docs/evalOperatorHeadless.test.mjs +7 -1
  146. package/lib/docs/evalOperatorInTab.js +2 -1
  147. package/lib/docs/findConfig.js +95 -49
  148. package/lib/docs/fitResponse.js +81 -0
  149. package/lib/docs/fitResponse.test.mjs +128 -0
  150. package/lib/docs/formatFeedback.js +26 -5
  151. package/lib/docs/formatFeedback.test.mjs +48 -0
  152. package/lib/docs/getAppMap.js +7 -1
  153. package/lib/docs/getBrowser.js +150 -80
  154. package/lib/docs/getBrowser.test.mjs +407 -16
  155. package/lib/docs/getBuildId.js +61 -0
  156. package/lib/docs/getBuildId.test.mjs +84 -0
  157. package/lib/docs/getBuildStatus.js +54 -6
  158. package/lib/docs/getBuildStatus.test.mjs +133 -0
  159. package/lib/docs/getBuildStatusAfterEdits.js +35 -0
  160. package/lib/docs/getBuildStatusAfterEdits.test.mjs +76 -0
  161. package/lib/docs/getCoreDoc.js +9 -1
  162. package/lib/docs/getDoc.js +100 -0
  163. package/lib/docs/getDocsIndex.js +152 -0
  164. package/lib/docs/getHazards.js +115 -0
  165. package/lib/docs/getHazards.test.mjs +175 -0
  166. package/lib/docs/getMailOutboxDirectory.js +26 -0
  167. package/lib/docs/getOverview.js +79 -13
  168. package/lib/docs/getOverviewBasePath.test.mjs +37 -0
  169. package/lib/docs/getPageBuildStatus.js +46 -0
  170. package/lib/docs/getPluginDoc.js +21 -23
  171. package/lib/docs/getSchema.js +33 -1
  172. package/lib/docs/getStaleStatus.js +36 -0
  173. package/lib/docs/getStaleStatus.test.mjs +74 -0
  174. package/lib/docs/guardJourneyOrigin.js +39 -0
  175. package/lib/docs/inspectState.js +5 -3
  176. package/lib/docs/inspectStateFromTab.js +2 -2
  177. package/lib/docs/inspectStateHeadless.js +89 -27
  178. package/lib/docs/inspectStateHeadless.test.mjs +20 -1
  179. package/lib/docs/installHeadlessShell.failure.test.mjs +45 -0
  180. package/lib/docs/installHeadlessShell.js +123 -0
  181. package/lib/docs/installHeadlessShell.test.mjs +70 -0
  182. package/lib/docs/installHeadlessShell.timeout.test.mjs +72 -0
  183. package/lib/docs/isHeadlessShellIncomplete.js +34 -0
  184. package/lib/docs/isHeadlessShellIncomplete.test.mjs +63 -0
  185. package/lib/docs/isPageReady.js +77 -0
  186. package/lib/docs/isPageReady.test.mjs +235 -0
  187. package/lib/docs/isRunErrorEntry.js +25 -0
  188. package/lib/docs/journeyMutantCookie.chromium.test.mjs +150 -0
  189. package/lib/docs/journeyRunRecording.js +35 -0
  190. package/lib/docs/journeyRunRecording.test.mjs +39 -0
  191. package/lib/docs/keepEventStreamAlive.js +68 -0
  192. package/lib/docs/keepEventStreamAlive.test.mjs +77 -0
  193. package/lib/docs/launchBrowser.js +83 -0
  194. package/lib/docs/launchBrowser.test.mjs +175 -0
  195. package/lib/docs/listModuleDocEntries.js +69 -0
  196. package/lib/docs/listPackageDocFiles.js +67 -0
  197. package/lib/docs/listPluginDocEntries.js +111 -0
  198. package/lib/docs/listPlugins.js +9 -58
  199. package/lib/docs/listTypes.js +14 -6
  200. package/lib/docs/loadFrameworkHazards.js +40 -0
  201. package/lib/docs/loadFrameworkHazards.test.mjs +43 -0
  202. package/lib/docs/loadState.js +103 -35
  203. package/lib/docs/makeModuleManifestDoc.js +174 -0
  204. package/lib/docs/matchExercisedPages.js +35 -0
  205. package/lib/docs/matchJourneyRoute.js +69 -0
  206. package/lib/docs/matchJourneyRoute.test.mjs +178 -0
  207. package/lib/docs/mergeNetworkSnapshots.js +49 -0
  208. package/lib/docs/nextJourneySequence.js +30 -0
  209. package/lib/docs/noBrowserError.js +22 -0
  210. package/lib/docs/observe/TRANSIENT_SELECTOR.js +21 -0
  211. package/lib/docs/observe/claimExpectedErrors.js +67 -0
  212. package/lib/docs/observe/createJourneyAppErrors.js +236 -0
  213. package/lib/docs/observe/createJourneyAppErrors.test.mjs +385 -0
  214. package/lib/docs/observe/evaluateInvariants.js +183 -0
  215. package/lib/docs/observe/evaluateInvariants.test.mjs +299 -0
  216. package/lib/docs/observe/findingKey.js +29 -0
  217. package/lib/docs/observe/hasNoEffect.js +31 -0
  218. package/lib/docs/observe/hasNoEffect.test.mjs +37 -0
  219. package/lib/docs/observe/installStepObserver.js +72 -0
  220. package/lib/docs/observe/isAppApiUrl.js +33 -0
  221. package/lib/docs/observe/parseAppApiUrl.js +37 -0
  222. package/lib/docs/observe/readStepObserver.js +42 -0
  223. package/lib/docs/observe/relativeSource.js +29 -0
  224. package/lib/docs/observe/resolveConfigKeySource.js +33 -0
  225. package/lib/docs/observe/takeJourneyEvents.js +36 -0
  226. package/lib/docs/observe/toAppErrorFailure.js +61 -0
  227. package/lib/docs/observe/usesSearchStage.js +45 -0
  228. package/lib/docs/observe/waitForClientErrorReports.js +31 -0
  229. package/lib/docs/observe/watchJourneyContext.js +75 -0
  230. package/lib/docs/observe/withAppErrors.js +31 -0
  231. package/lib/docs/observe/withAppErrors.test.mjs +101 -0
  232. package/lib/docs/openJourney.js +94 -0
  233. package/lib/docs/openJourneyEmail.js +41 -0
  234. package/lib/docs/openJourneyEmail.test.mjs +187 -0
  235. package/lib/docs/pageBlocks.js +47 -0
  236. package/lib/docs/pageBuildStatus.test.mjs +103 -0
  237. package/lib/{client/setPageId.js → docs/parseModuleSource.js} +11 -12
  238. package/lib/docs/rails/authEngineActions.js +53 -0
  239. package/lib/docs/rails/authEngineActions.test.mjs +69 -0
  240. package/lib/docs/rails/authEngineMethods.js +52 -0
  241. package/lib/docs/rails/authEngineMethods.test.mjs +36 -0
  242. package/lib/docs/rails/checkAuthActionsRan.js +66 -0
  243. package/lib/docs/rails/checkAuthActionsRan.test.mjs +149 -0
  244. package/lib/docs/rails/checkClickRails.js +56 -0
  245. package/lib/docs/rails/createDataSetRails.js +82 -0
  246. package/lib/docs/rails/createDataSetRails.test.mjs +172 -0
  247. package/lib/docs/rails/findExternalBlocks.js +147 -0
  248. package/lib/docs/rails/findExternalBlocks.test.mjs +254 -0
  249. package/lib/docs/rails/installAuthActionRecorder.js +56 -0
  250. package/lib/docs/rails/readAuthActionRecorder.js +32 -0
  251. package/lib/docs/rails/readBuiltArtifact.js +30 -0
  252. package/lib/docs/readConnectionPlugins.js +26 -0
  253. package/lib/docs/readDevAuthMode.js +32 -0
  254. package/lib/docs/readDocEntry.js +40 -0
  255. package/lib/docs/readDocTitle.js +26 -0
  256. package/lib/docs/readJourneyEmailMatch.js +57 -0
  257. package/lib/docs/readJourneyEmailMatch.test.mjs +110 -0
  258. package/lib/docs/readJourneySession.js +56 -0
  259. package/lib/docs/readMailOutbox.js +37 -0
  260. package/lib/docs/readPagePath.js +27 -0
  261. package/lib/docs/readPluginPackages.js +99 -0
  262. package/lib/docs/readProxyBuildWait.js +38 -0
  263. package/lib/docs/readRoutineSteps.js +45 -0
  264. package/lib/docs/registerShownTextEngine.js +118 -0
  265. package/lib/docs/requestRestart.js +30 -0
  266. package/lib/docs/requestRestart.test.mjs +49 -0
  267. package/lib/docs/resolvePageInstance.js +46 -0
  268. package/lib/docs/resolvePageInstance.test.mjs +81 -0
  269. package/lib/docs/resolveSource.js +11 -7
  270. package/lib/docs/resolveSource.test.mjs +1 -1
  271. package/lib/docs/resolveToolCaller.js +62 -0
  272. package/lib/docs/reviewPage.js +51 -0
  273. package/lib/docs/reviewPageBuilds.js +56 -0
  274. package/lib/docs/reviewPageBuilds.test.mjs +207 -0
  275. package/lib/docs/runCheck.js +75 -0
  276. package/lib/docs/runCheck.test.mjs +91 -0
  277. package/lib/docs/runCheckChild.js +49 -0
  278. package/lib/docs/runEndpoint.js +185 -0
  279. package/lib/docs/runEndpoint.test.mjs +509 -0
  280. package/lib/docs/runErrorBuffers.js +99 -0
  281. package/lib/docs/runErrorBuffers.test.mjs +95 -0
  282. package/lib/docs/runJourney.js +347 -0
  283. package/lib/docs/runJourney.test.mjs +2383 -0
  284. package/lib/docs/runJourneyAsUsers.js +44 -0
  285. package/lib/docs/runJourneyDataSet.test.mjs +274 -0
  286. package/lib/docs/runJourneySteps.js +1320 -0
  287. package/lib/docs/runRequest.js +100 -59
  288. package/lib/docs/runRequest.test.mjs +190 -0
  289. package/lib/docs/saveAnnotatedScreenshot.js +1 -1
  290. package/lib/docs/scaffoldPage.js +3 -1
  291. package/lib/docs/screenshotPage.js +163 -11
  292. package/lib/docs/screenshotPage.test.mjs +263 -1
  293. package/lib/docs/screenshotPageClip.test.mjs +4 -0
  294. package/lib/docs/searchDocs.js +82 -30
  295. package/lib/docs/searchDocs.test.mjs +100 -0
  296. package/lib/docs/searchIcons.js +90 -0
  297. package/lib/docs/searchIcons.test.mjs +127 -0
  298. package/lib/docs/selectExercised.js +39 -0
  299. package/lib/docs/selectFinalState.js +66 -0
  300. package/lib/docs/selectFinalState.test.mjs +51 -0
  301. package/lib/docs/serverErrorStore.js +54 -0
  302. package/lib/docs/serverErrorStore.test.mjs +50 -0
  303. package/lib/docs/setupTestFixtures.mjs +133 -39
  304. package/lib/docs/shownTextSelector.js +28 -0
  305. package/lib/docs/snapshotState.js +13 -5
  306. package/lib/docs/splitSearchTerms.js +62 -0
  307. package/lib/docs/splitSearchTerms.test.mjs +50 -0
  308. package/lib/docs/stateCheckpoints.test.mjs +77 -0
  309. package/lib/docs/tabAvailable.js +6 -5
  310. package/lib/docs/tabChannel.js +127 -21
  311. package/lib/docs/tabChannel.test.mjs +187 -29
  312. package/lib/docs/unsettledPageNote.js +24 -0
  313. package/lib/docs/validateJourneyMail.js +36 -0
  314. package/lib/docs/validateJourneyTimeout.js +38 -0
  315. package/lib/docs/validateJourneyTimeout.test.mjs +29 -0
  316. package/lib/docs/validateRecordingParam.js +42 -0
  317. package/lib/docs/validateStateSelection.js +33 -0
  318. package/lib/docs/validateStateSelection.test.mjs +33 -0
  319. package/lib/docs/validateViewport.js +51 -0
  320. package/lib/docs/validateViewport.test.mjs +54 -0
  321. package/lib/docs/waitForBuild.js +50 -0
  322. package/lib/docs/waitForBuild.test.mjs +75 -0
  323. package/lib/docs/waitForJourneyEmail.js +77 -0
  324. package/lib/docs/withBrowserSlot.js +45 -0
  325. package/lib/docs/withBrowserSlot.test.mjs +75 -0
  326. package/lib/docs/withDataSession.js +53 -0
  327. package/lib/server/applyDataSetRedirect.js +83 -0
  328. package/lib/server/applyDataSetRedirect.test.mjs +340 -0
  329. package/lib/server/applyDataSetRedirectTenantReach.test.mjs +204 -0
  330. package/lib/server/applyDataSetRedirectWalled.test.mjs +131 -0
  331. package/lib/server/auth/createSystemContext.js +73 -0
  332. package/lib/server/auth/{getAuthConfig.js → getAuth.js} +15 -9
  333. package/lib/server/auth/getHeadlessUser.js +9 -8
  334. package/lib/server/auth/getHeadlessUser.test.mjs +41 -0
  335. package/lib/server/auth/getMockUser.js +18 -14
  336. package/lib/server/auth/{session.js → getStrategies.js} +17 -16
  337. package/lib/server/auth/headlessUser.js +3 -2
  338. package/lib/server/auth/journeyActor.js +35 -0
  339. package/lib/server/auth/resolveHeadlessUser.test.mjs +0 -4
  340. package/lib/server/checkPageRecord.js +44 -0
  341. package/lib/server/contextMapBudget.js +24 -0
  342. package/lib/server/contextMapBudget.test.mjs +98 -0
  343. package/lib/server/continueJitKeys.js +28 -0
  344. package/lib/server/continueJitKeys.test.mjs +43 -0
  345. package/lib/server/createDataSessionEndedError.js +33 -0
  346. package/lib/server/createDataSetRefusal.js +26 -0
  347. package/lib/server/createLowdefyContext.js +168 -41
  348. package/lib/server/createLowdefyContext.test.mjs +328 -8
  349. package/lib/server/getClientAddress.js +51 -0
  350. package/lib/server/getClientAddress.test.mjs +56 -0
  351. package/lib/server/getPageJitEnrichment.test.mjs +29 -11
  352. package/lib/server/hashConfigContent.js +26 -0
  353. package/lib/server/importAppCode.test.mjs +57 -0
  354. package/lib/server/isRoutineStep.js +27 -0
  355. package/lib/server/jitPageBuilder.js +309 -126
  356. package/lib/server/jitPageBuilder.skipPluginChecks.test.mjs +78 -0
  357. package/lib/server/jitPageBuilder.test.mjs +218 -0
  358. package/lib/server/journeyCookies/JOURNEY_COOKIES.js +37 -0
  359. package/lib/server/journeyCookies/forwardJourneyCookies.js +46 -0
  360. package/lib/server/journeyCookies/readJourneyCookie.js +52 -0
  361. package/lib/server/journeyCookies/writeJourneyCookie.js +46 -0
  362. package/lib/server/journeyCookies.js +22 -0
  363. package/lib/server/journeyCookies.test.mjs +145 -0
  364. package/lib/server/loadDynamicJsMap.js +47 -0
  365. package/lib/server/log/createHandleError.js +38 -2
  366. package/lib/server/log/createHandleError.test.mjs +125 -2
  367. package/lib/server/log/createLogger.js +3 -0
  368. package/lib/server/log/createLogger.test.mjs +15 -0
  369. package/lib/server/log/logRequest.js +2 -3
  370. package/lib/server/mutantDataSetWrappers.test.mjs +112 -0
  371. package/lib/server/mutants/applyMutant.js +39 -0
  372. package/lib/server/mutants/artifactPatterns.js +25 -0
  373. package/lib/server/mutants/buildAnchor.js +45 -0
  374. package/lib/server/mutants/collectSharedIds.js +41 -0
  375. package/lib/server/mutants/configPathSegments.js +38 -0
  376. package/lib/server/mutants/createMutantReadConfigFile.js +42 -0
  377. package/lib/server/mutants/describeEvent.js +22 -0
  378. package/lib/server/mutants/enumerateArtifact.js +56 -0
  379. package/lib/server/mutants/findNodeByKey.js +45 -0
  380. package/lib/server/mutants/findNodeByPointer.js +49 -0
  381. package/lib/server/mutants/getArtifactKind.js +32 -0
  382. package/lib/server/mutants/groupMutantCopies.js +56 -0
  383. package/lib/server/mutants/hashArtifact.js +25 -0
  384. package/lib/server/mutants/hashMutantTarget.js +34 -0
  385. package/lib/server/mutants/listMutants.js +122 -0
  386. package/lib/server/mutants/listMutants.test.mjs +502 -0
  387. package/lib/server/mutants/mutantId.js +30 -0
  388. package/lib/server/mutants/mutantRuns.js +57 -0
  389. package/lib/server/mutants/mutantRuns.test.mjs +136 -0
  390. package/lib/server/mutants/operators/dropAction.js +47 -0
  391. package/lib/server/mutants/operators/dropBlock.js +44 -0
  392. package/lib/server/mutants/operators/dropPayload.js +52 -0
  393. package/lib/server/mutants/operators/dropStep.js +44 -0
  394. package/lib/server/mutants/operators/flipVisible.js +53 -0
  395. package/lib/server/mutants/operators/index.js +37 -0
  396. package/lib/server/mutants/operators/retargetLink.js +72 -0
  397. package/lib/server/mutants/operators/skipValidate.js +77 -0
  398. package/lib/server/mutants/operators/swapIf.js +112 -0
  399. package/lib/server/mutants/operators.test.mjs +396 -0
  400. package/lib/server/mutants/positionFreePath.js +38 -0
  401. package/lib/server/mutants/removeFromParent.js +28 -0
  402. package/lib/server/mutants/test/fixtures.mjs +173 -0
  403. package/lib/server/mutants/test/keyArtifact.mjs +63 -0
  404. package/lib/server/mutants/validateMutantParam.js +69 -0
  405. package/lib/server/mutants/validateMutantsBody.js +88 -0
  406. package/lib/server/mutants/walkArtifact.js +179 -0
  407. package/lib/server/pageBuildRecords.js +131 -0
  408. package/lib/server/pageBuildRecords.test.mjs +258 -0
  409. package/lib/server/pageCache.mjs +22 -7
  410. package/lib/server/pageCache.test.mjs +22 -7
  411. package/lib/server/pageRebuilds.test.mjs +543 -0
  412. package/lib/server/pruneJitMaps.js +36 -0
  413. package/lib/server/pruneJitMaps.test.mjs +55 -0
  414. package/lib/server/readInstanceRecord.js +33 -0
  415. package/lib/server/readInstanceRecord.test.mjs +99 -0
  416. package/lib/server/readMergedMaps.js +86 -0
  417. package/lib/server/readMergedMaps.test.mjs +79 -0
  418. package/lib/server/recording/appendRecords.js +33 -0
  419. package/lib/server/recording/errorRecordingStamp.js +34 -0
  420. package/lib/server/recording/errorRecordingStamp.test.mjs +31 -0
  421. package/lib/server/recording/isRecordingEnabled.js +31 -0
  422. package/lib/server/recording/readRecordingCookie.js +71 -0
  423. package/lib/server/recording/readRecordingCookie.test.mjs +73 -0
  424. package/lib/server/recording/recordingCookiePayload.js +33 -0
  425. package/lib/server/recording/servedBuilds.js +46 -0
  426. package/lib/server/recording/servedBuilds.test.mjs +42 -0
  427. package/lib/server/sinkExternalConnections.js +95 -0
  428. package/lib/server/sinkExternalConnections.test.mjs +135 -0
  429. package/lib/server/skipStaleMapWrites.js +62 -0
  430. package/lib/server/skipStaleMapWrites.test.mjs +113 -0
  431. package/lib/server/startIdleGc.js +67 -0
  432. package/lib/server/startIdleGc.test.mjs +138 -0
  433. package/lib/server/testFixtures/importAppCodeTwice.mjs +70 -0
  434. package/lib/server/trackSessionWork.js +30 -0
  435. package/lib/vite/createDevServerExclude.js +40 -0
  436. package/lib/vite/createDevServerExclude.test.mjs +79 -0
  437. package/manager/getContext.mjs +24 -2
  438. package/manager/processes/initialBuild.mjs +14 -0
  439. package/manager/processes/initialBuild.test.mjs +101 -0
  440. package/manager/processes/lowdefyBuild.mjs +30 -7
  441. package/manager/processes/lowdefyBuild.skipPluginChecks.test.mjs +62 -0
  442. package/manager/processes/lowdefyBuild.test.mjs +136 -1
  443. package/manager/processes/optimizeDependencies.mjs +59 -0
  444. package/manager/processes/restartServer.mjs +34 -6
  445. package/manager/processes/restartServer.test.mjs +162 -0
  446. package/manager/processes/shutdownServer.mjs +11 -0
  447. package/manager/processes/shutdownServer.test.mjs +45 -0
  448. package/manager/processes/startFirstServer.mjs +46 -0
  449. package/manager/processes/startFirstServer.test.mjs +326 -0
  450. package/manager/processes/startMailSink.mjs +135 -0
  451. package/manager/processes/startMailSink.test.mjs +146 -0
  452. package/manager/processes/startProxy.mjs +129 -12
  453. package/manager/processes/startProxy.test.mjs +366 -0
  454. package/manager/processes/startServer.mjs +47 -6
  455. package/manager/processes/startServer.test.mjs +155 -0
  456. package/manager/processes/startWatchers.mjs +6 -4
  457. package/manager/processes/syncServer.mjs +69 -0
  458. package/manager/processes/syncServer.test.mjs +141 -0
  459. package/manager/processes/waitForDevServer.mjs +48 -0
  460. package/manager/processes/waitForDevServer.test.mjs +112 -0
  461. package/manager/run.mjs +128 -43
  462. package/manager/utils/BatchChanges.mjs +21 -5
  463. package/manager/utils/BatchChanges.test.mjs +77 -0
  464. package/manager/utils/acquireDevInstance.mjs +99 -0
  465. package/manager/utils/acquireDevInstance.test.mjs +160 -0
  466. package/manager/utils/createBuildActivity.mjs +67 -0
  467. package/manager/utils/createBuildActivity.test.mjs +58 -0
  468. package/manager/utils/createChangeSignal.mjs +39 -0
  469. package/manager/utils/createChangeSignal.test.mjs +62 -0
  470. package/manager/utils/createCustomPluginMessagesMap.mjs +23 -33
  471. package/manager/utils/createCustomPluginTypesMap.mjs +24 -30
  472. package/manager/utils/createCustomPluginTypesMap.test.mjs +94 -0
  473. package/manager/utils/createDotPathIgnore.mjs +44 -0
  474. package/manager/utils/createDotPathIgnore.test.mjs +65 -0
  475. package/manager/utils/createIdleGc.mjs +51 -0
  476. package/manager/utils/createIdleGc.test.mjs +77 -0
  477. package/manager/utils/createRequestActivity.mjs +100 -0
  478. package/manager/utils/createRequestActivity.test.mjs +121 -0
  479. package/manager/utils/createServerArtifactTracker.mjs +83 -0
  480. package/manager/utils/createServerArtifactTracker.test.mjs +78 -0
  481. package/manager/utils/createServerEnv.mjs +39 -0
  482. package/manager/utils/filesHaveSameBytes.mjs +41 -0
  483. package/manager/utils/findBuildFilesOutsideWatch.mjs +59 -0
  484. package/manager/utils/findBuildFilesOutsideWatch.test.mjs +111 -0
  485. package/manager/utils/importFresh.mjs +46 -0
  486. package/manager/utils/importFresh.test.mjs +72 -0
  487. package/manager/utils/importFreshWorker.mjs +20 -0
  488. package/manager/utils/killTaggedBrowser.mjs +35 -0
  489. package/manager/utils/pagePathChanged.mjs +56 -0
  490. package/manager/utils/pruneRecordings.mjs +85 -0
  491. package/manager/utils/pruneRecordings.test.mjs +117 -0
  492. package/manager/utils/publishBuildDirectory.mjs +121 -0
  493. package/manager/utils/publishBuildDirectory.order.test.mjs +74 -0
  494. package/manager/utils/publishBuildDirectory.test.mjs +262 -0
  495. package/manager/utils/readBasePath.mjs +27 -0
  496. package/manager/utils/readBasePath.test.mjs +43 -0
  497. package/manager/utils/readBuildStatusWait.mjs +67 -0
  498. package/manager/utils/readManagerVersion.mjs +30 -0
  499. package/manager/utils/readManagerVersion.test.mjs +35 -0
  500. package/manager/utils/readPluginDefinitions.mjs +36 -0
  501. package/manager/utils/resolveDevAuthUrl.mjs +58 -0
  502. package/manager/utils/resolveDevAuthUrl.test.mjs +107 -0
  503. package/manager/utils/resolvePorts.mjs +49 -0
  504. package/manager/utils/setupWatcher.mjs +19 -11
  505. package/manager/utils/startRecordingPruner.mjs +45 -0
  506. package/manager/utils/startRecordingPruner.test.mjs +66 -0
  507. package/manager/utils/waitForChildExit.mjs +53 -0
  508. package/manager/utils/waitForChildExit.test.mjs +70 -0
  509. package/manager/utils/waitForServer.mjs +44 -0
  510. package/manager/utils/writeBuildStatus.mjs +1 -1
  511. package/manager/watchers/createPluginSourceIgnore.mjs +77 -0
  512. package/manager/watchers/envWatcher.mjs +9 -2
  513. package/manager/watchers/envWatcher.test.mjs +87 -0
  514. package/manager/watchers/listServerSidePackages.mjs +47 -0
  515. package/manager/watchers/listServerSidePackages.test.mjs +38 -0
  516. package/manager/watchers/lowdefyBuildWatcher.mjs +106 -22
  517. package/manager/watchers/lowdefyBuildWatcher.test.mjs +309 -0
  518. package/manager/watchers/pluginSourceWatcher.mjs +79 -0
  519. package/manager/watchers/pluginSourceWatcher.test.mjs +222 -0
  520. package/manager/watchers/restartRequestWatcher.mjs +71 -0
  521. package/manager/watchers/restartRequestWatcher.test.mjs +111 -0
  522. package/manager/watchers/selectLocalPluginPackages.mjs +48 -0
  523. package/manager/watchers/selectLocalPluginPackages.test.mjs +73 -0
  524. package/manager/watchers/serverPackageWatcher.mjs +38 -0
  525. package/package.json +60 -47
  526. package/package.original.json +60 -47
  527. package/src/app.js +131 -20
  528. package/src/html/renderDevPage.js +63 -11
  529. package/src/html/renderDevPage.test.mjs +138 -0
  530. package/src/lib/getRequestPath.js +29 -0
  531. package/src/middleware/createSameOriginGuard.js +37 -0
  532. package/src/middleware/createSameOriginGuard.test.mjs +102 -0
  533. package/src/middleware/errorHandler.js +51 -1
  534. package/src/middleware/errorHandler.test.mjs +173 -3
  535. package/src/middleware/isRebindingSafeHost.js +44 -0
  536. package/src/middleware/isRebindingSafeHost.test.mjs +32 -0
  537. package/src/middleware/localDevToolsOnly.js +38 -0
  538. package/src/middleware/localDevToolsOnly.test.mjs +61 -0
  539. package/src/middleware/noStore.js +27 -0
  540. package/src/middleware/noStore.test.mjs +48 -0
  541. package/src/middleware/requestTimeout.js +38 -0
  542. package/src/middleware/staleFlag.js +76 -0
  543. package/src/middleware/staleFlag.test.mjs +127 -0
  544. package/src/middleware/stampCaller.js +38 -0
  545. package/src/middleware/stampCaller.test.mjs +64 -0
  546. package/src/routes/agent.js +15 -3
  547. package/src/routes/auth.js +29 -20
  548. package/src/routes/auth.test.mjs +30 -56
  549. package/src/routes/clientError.js +13 -13
  550. package/src/routes/cron.js +3 -1
  551. package/src/routes/cronForward.js +3 -1
  552. package/src/routes/detached.js +22 -9
  553. package/src/routes/devInspect.js +17 -8
  554. package/src/routes/devInspect.test.mjs +44 -7
  555. package/src/routes/devRecording.js +113 -0
  556. package/src/routes/devRecording.test.mjs +253 -0
  557. package/src/routes/docs/buildStatus.js +10 -2
  558. package/src/routes/docs/buildStatus.test.mjs +46 -0
  559. package/src/routes/docs/content.js +5 -3
  560. package/src/routes/docs/evalOperator.js +15 -1
  561. package/src/routes/docs/events.js +50 -0
  562. package/src/routes/docs/events.test.mjs +117 -0
  563. package/src/routes/docs/find.js +9 -1
  564. package/src/routes/docs/icons.js +30 -0
  565. package/src/routes/docs/inspectState.js +16 -1
  566. package/src/routes/docs/journey.js +194 -0
  567. package/src/routes/docs/journey.test.mjs +401 -0
  568. package/src/routes/docs/journeyRefusals.test.mjs +71 -0
  569. package/src/routes/docs/loadState.js +4 -2
  570. package/src/routes/docs/mcp.js +34 -3
  571. package/src/routes/docs/mcp.test.mjs +130 -0
  572. package/src/routes/docs/mutants.js +78 -0
  573. package/src/routes/docs/mutants.test.mjs +180 -0
  574. package/src/routes/docs/parsePathParamsParam.js +41 -0
  575. package/src/routes/docs/parsePathParamsParam.test.mjs +42 -0
  576. package/src/routes/docs/parseUserParam.js +27 -21
  577. package/src/routes/docs/parseUserParam.test.mjs +17 -5
  578. package/src/routes/docs/restart.js +35 -0
  579. package/src/routes/docs/restart.test.mjs +64 -0
  580. package/src/routes/docs/runEndpoint.js +54 -0
  581. package/src/routes/docs/runEndpoint.test.mjs +165 -0
  582. package/src/routes/docs/runRequest.js +25 -8
  583. package/src/routes/docs/runRequest.test.mjs +92 -0
  584. package/src/routes/docs/screenshot.js +50 -2
  585. package/src/routes/docs/screenshot.test.mjs +117 -0
  586. package/src/routes/docs/search.js +15 -2
  587. package/src/routes/docs/snapshotState.js +17 -3
  588. package/src/routes/endpoints.js +27 -26
  589. package/src/routes/endpointsTextAnswer.test.mjs +77 -0
  590. package/src/routes/feedback.js +10 -13
  591. package/src/routes/jitPage.js +99 -35
  592. package/src/routes/jitPage.test.mjs +248 -0
  593. package/src/routes/mcp.js +82 -5
  594. package/src/routes/mcp.test.mjs +215 -0
  595. package/src/routes/mcpMemberToken.test.mjs +125 -0
  596. package/src/routes/mcpProtectedResourceMetadata.js +34 -0
  597. package/src/routes/mcpProtectedResourceMetadata.test.mjs +65 -0
  598. package/src/routes/mountDevPageRoutes.js +25 -0
  599. package/src/routes/mountDevPageRoutes.test.mjs +30 -0
  600. package/src/routes/mountOauthDiscovery.js +44 -0
  601. package/src/routes/mountOauthDiscovery.test.mjs +79 -0
  602. package/src/routes/normalizeMcpProtocolVersionHeader.js +46 -0
  603. package/src/routes/normalizeMcpProtocolVersionHeader.test.mjs +62 -0
  604. package/src/routes/reload.js +16 -0
  605. package/src/routes/request.js +6 -4
  606. package/src/routes/request.test.mjs +9 -5
  607. package/src/routes/requestBody.test.mjs +159 -0
  608. package/src/routes/usage.js +6 -2
  609. package/src/routes/user.js +27 -0
  610. package/src/routes/websocket.js +29 -8
  611. package/src/routes/websocket.test.mjs +65 -0
  612. package/src/routes/wellKnownFallback.js +30 -0
  613. package/src/routes/wellKnownFallback.test.mjs +32 -0
  614. package/src/websocket/devWebSocket.js +41 -26
  615. package/src/websocket/devWebSocket.test.mjs +157 -0
  616. package/vite.config.js +53 -23
  617. package/lib/server/auth/getDevSession.js +0 -70
  618. package/lib/server/auth/getDevSession.test.mjs +0 -138
  619. package/manager/processes/warnAuthUrlPortMismatch.mjs +0 -47
  620. package/manager/processes/warnAuthUrlPortMismatch.test.mjs +0 -93
  621. package/manager/watchers/moduleBuildWatcher.mjs +0 -79
  622. package/manager/watchers/serverArtifactWatcher.mjs +0 -120
  623. package/src/routes/apiPage.js +0 -37
@@ -0,0 +1,165 @@
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
+ const mockRunEndpoint = jest.fn();
20
+
21
+ jest.unstable_mockModule('../../../lib/docs/runEndpoint.js', () => ({
22
+ default: mockRunEndpoint,
23
+ }));
24
+
25
+ const { ConfigError } = await import('@lowdefy/errors');
26
+ const { default: docsRunEndpointHandler } = await import('./runEndpoint.js');
27
+
28
+ function createContext(body) {
29
+ const request = new Request('http://localhost:3224/lowdefy-docs/run-endpoint', {
30
+ method: 'POST',
31
+ headers: { 'content-type': 'application/json' },
32
+ body: JSON.stringify(body),
33
+ });
34
+ const json = jest.fn((data, status) => ({ data, status: status ?? 200 }));
35
+ return { req: { raw: request }, json };
36
+ }
37
+
38
+ beforeEach(() => {
39
+ jest.clearAllMocks();
40
+ mockRunEndpoint.mockResolvedValue({ refused: false, status: 'success', response: { ok: true } });
41
+ });
42
+
43
+ test('docsRunEndpointHandler passes endpointId, payload and a user object through to runEndpoint', async () => {
44
+ const c = createContext({
45
+ endpointId: 'create_order',
46
+ payload: { sku: 'A1' },
47
+ user: { roles: ['admin'] },
48
+ });
49
+
50
+ const result = await docsRunEndpointHandler(c);
51
+
52
+ expect(mockRunEndpoint).toHaveBeenCalledWith({
53
+ endpointId: 'create_order',
54
+ payload: { sku: 'A1' },
55
+ user: { roles: ['admin'] },
56
+ system: undefined,
57
+ honoContext: c,
58
+ });
59
+ expect(result.status).toBe(200);
60
+ expect(result.data).toEqual({ refused: false, status: 'success', response: { ok: true } });
61
+ });
62
+
63
+ test('docsRunEndpointHandler leaves the original request body readable', async () => {
64
+ const c = createContext({ endpointId: 'create_order' });
65
+
66
+ await docsRunEndpointHandler(c);
67
+
68
+ expect(c.req.raw.bodyUsed).toBe(false);
69
+ await expect(c.req.raw.json()).resolves.toEqual({ endpointId: 'create_order' });
70
+ });
71
+
72
+ test('docsRunEndpointHandler returns 400 when user is malformed', async () => {
73
+ const c = createContext({ endpointId: 'create_order', user: '{nope' });
74
+
75
+ const result = await docsRunEndpointHandler(c);
76
+
77
+ expect(result.status).toBe(400);
78
+ expect(result.data.error).toMatch(/The "user" param must be JSON/);
79
+ expect(mockRunEndpoint).not.toHaveBeenCalled();
80
+ });
81
+
82
+ test('docsRunEndpointHandler passes a data set user name and data through', async () => {
83
+ const c = createContext({ endpointId: 'create_order', user: 'member', data: 'crm' });
84
+
85
+ await docsRunEndpointHandler(c);
86
+
87
+ expect(mockRunEndpoint).toHaveBeenCalledWith(
88
+ expect.objectContaining({ user: 'member', data: 'crm' })
89
+ );
90
+ });
91
+
92
+ test('docsRunEndpointHandler passes an undefined user when the body omits it', async () => {
93
+ const c = createContext({ endpointId: 'create_order' });
94
+
95
+ await docsRunEndpointHandler(c);
96
+
97
+ expect(mockRunEndpoint).toHaveBeenCalledWith({
98
+ endpointId: 'create_order',
99
+ payload: undefined,
100
+ user: undefined,
101
+ system: undefined,
102
+ honoContext: c,
103
+ });
104
+ });
105
+
106
+ test('docsRunEndpointHandler passes system through to runEndpoint', async () => {
107
+ const c = createContext({ endpointId: 'purge_stale', system: true });
108
+
109
+ await docsRunEndpointHandler(c);
110
+
111
+ expect(mockRunEndpoint).toHaveBeenCalledWith({
112
+ endpointId: 'purge_stale',
113
+ payload: undefined,
114
+ user: undefined,
115
+ system: true,
116
+ honoContext: c,
117
+ });
118
+ });
119
+
120
+ test('docsRunEndpointHandler returns 400 when system is combined with user', async () => {
121
+ mockRunEndpoint.mockRejectedValue(
122
+ new ConfigError('run_endpoint "system" cannot be combined with "user".')
123
+ );
124
+ const c = createContext({ endpointId: 'purge_stale', system: true, user: { roles: ['admin'] } });
125
+
126
+ const result = await docsRunEndpointHandler(c);
127
+
128
+ expect(result.status).toBe(400);
129
+ expect(result.data.error).toMatch(/cannot be combined with "user"/);
130
+ });
131
+
132
+ test('docsRunEndpointHandler returns 400 when runEndpoint throws a ConfigError', async () => {
133
+ mockRunEndpoint.mockRejectedValue(
134
+ new ConfigError('run_endpoint requires an "endpointId" string. Received undefined.')
135
+ );
136
+ const c = createContext({ payload: {} });
137
+
138
+ const result = await docsRunEndpointHandler(c);
139
+
140
+ expect(result.status).toBe(400);
141
+ expect(result.data.error).toMatch(/requires an "endpointId" string/);
142
+ });
143
+
144
+ test('docsRunEndpointHandler returns refusals and rejects as 200 data', async () => {
145
+ mockRunEndpoint.mockResolvedValue({
146
+ refused: false,
147
+ success: false,
148
+ status: 'reject',
149
+ error: { name: 'Error', message: 'Out of stock.' },
150
+ response: null,
151
+ });
152
+ const c = createContext({ endpointId: 'create_order' });
153
+
154
+ const result = await docsRunEndpointHandler(c);
155
+
156
+ expect(result.status).toBe(200);
157
+ expect(result.data.status).toBe('reject');
158
+ });
159
+
160
+ test('docsRunEndpointHandler rethrows faults that are not ConfigErrors', async () => {
161
+ mockRunEndpoint.mockRejectedValue(new Error('boom'));
162
+ const c = createContext({ endpointId: 'create_order' });
163
+
164
+ await expect(docsRunEndpointHandler(c)).rejects.toThrow('boom');
165
+ });
@@ -14,22 +14,39 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
+ import { ConfigError } from '@lowdefy/errors';
18
+
19
+ import parseUserParam from './parseUserParam.js';
17
20
  import runRequest from '../../../lib/docs/runRequest.js';
18
21
 
19
22
  // Refusals and request errors are returned as 200 data (see runRequest.js) —
20
- // only malformed input (missing pageId/requestId) is a 400.
23
+ // only malformed input (missing pageId/requestId, thrown as a ConfigError) is a
24
+ // 400. Anything else is a fault and propagates to the error handler.
21
25
  async function docsRunRequestHandler(c) {
22
26
  // Parse the body from a clone: runRequest builds a Lowdefy context whose
23
- // getSession(c) → getAuthUser(c) reconstructs a Request from c.req.raw,
24
- // which throws ("body ... disturbed or locked") if the body was already
25
- // consumed. The normal /api/request path never hits this because its
26
- // session is read in middleware before the handler touches the body.
27
- const { pageId, requestId, payload } = await c.req.raw.clone().json();
27
+ // resolveAuthentication reads c.req.raw (headers) to resolve the caller,
28
+ // so leave the original request body intact and read our own copy here.
29
+ const { pageId, requestId, payload, user, data, saveResponse } = await c.req.raw.clone().json();
30
+ const { user: parsedUser, error: userError } = parseUserParam({ value: user });
31
+ if (userError) {
32
+ return c.json({ error: userError }, 400);
33
+ }
28
34
  try {
29
- const result = await runRequest({ pageId, requestId, payload, honoContext: c });
35
+ const result = await runRequest({
36
+ pageId,
37
+ requestId,
38
+ payload,
39
+ user: parsedUser,
40
+ data,
41
+ saveResponse,
42
+ honoContext: c,
43
+ });
30
44
  return c.json(result);
31
45
  } catch (error) {
32
- return c.json({ error: error.message }, 400);
46
+ if (error instanceof ConfigError) {
47
+ return c.json({ error: error.message }, 400);
48
+ }
49
+ throw error;
33
50
  }
34
51
  }
35
52
 
@@ -0,0 +1,92 @@
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
+ const mockRunRequest = jest.fn();
20
+
21
+ jest.unstable_mockModule('../../../lib/docs/runRequest.js', () => ({
22
+ default: mockRunRequest,
23
+ }));
24
+
25
+ const { default: docsRunRequestHandler } = await import('./runRequest.js');
26
+
27
+ function createContext(body) {
28
+ const request = new Request('http://localhost:3204/lowdefy-docs/run-request', {
29
+ method: 'POST',
30
+ headers: { 'content-type': 'application/json' },
31
+ body: JSON.stringify(body),
32
+ });
33
+ const json = jest.fn((data, status) => ({ data, status: status ?? 200 }));
34
+ return { req: { raw: request }, json };
35
+ }
36
+
37
+ beforeEach(() => {
38
+ jest.clearAllMocks();
39
+ mockRunRequest.mockResolvedValue({ refused: false, response: [] });
40
+ });
41
+
42
+ test('docsRunRequestHandler passes a user object through to runRequest', async () => {
43
+ const c = createContext({
44
+ pageId: 'home',
45
+ requestId: 'get_rows',
46
+ user: { roles: ['admin'] },
47
+ });
48
+
49
+ await docsRunRequestHandler(c);
50
+
51
+ expect(mockRunRequest).toHaveBeenCalledWith({
52
+ pageId: 'home',
53
+ requestId: 'get_rows',
54
+ payload: undefined,
55
+ user: { roles: ['admin'] },
56
+ honoContext: c,
57
+ });
58
+ });
59
+
60
+ test('docsRunRequestHandler returns 400 when user is malformed', async () => {
61
+ const c = createContext({ pageId: 'home', requestId: 'get_rows', user: 42 });
62
+
63
+ const result = await docsRunRequestHandler(c);
64
+
65
+ expect(result.status).toBe(400);
66
+ expect(result.data.error).toMatch(/must be a user object/);
67
+ expect(mockRunRequest).not.toHaveBeenCalled();
68
+ });
69
+
70
+ test('docsRunRequestHandler passes a data set user name and data through', async () => {
71
+ const c = createContext({ pageId: 'home', requestId: 'get_rows', user: 'member', data: 'crm' });
72
+
73
+ await docsRunRequestHandler(c);
74
+
75
+ expect(mockRunRequest).toHaveBeenCalledWith(
76
+ expect.objectContaining({ user: 'member', data: 'crm' })
77
+ );
78
+ });
79
+
80
+ test('docsRunRequestHandler passes an undefined user when the body omits it', async () => {
81
+ const c = createContext({ pageId: 'home', requestId: 'get_rows' });
82
+
83
+ await docsRunRequestHandler(c);
84
+
85
+ expect(mockRunRequest).toHaveBeenCalledWith({
86
+ pageId: 'home',
87
+ requestId: 'get_rows',
88
+ payload: undefined,
89
+ user: undefined,
90
+ honoContext: c,
91
+ });
92
+ });
@@ -16,8 +16,10 @@
16
16
 
17
17
  import { type } from '@lowdefy/helpers';
18
18
 
19
+ import parsePathParamsParam from './parsePathParamsParam.js';
19
20
  import parseUserParam from './parseUserParam.js';
20
21
  import screenshotPage from '../../../lib/docs/screenshotPage.js';
22
+ import validateViewport from '../../../lib/docs/validateViewport.js';
21
23
 
22
24
  // Parses a query param as a number, or undefined if absent/not a valid
23
25
  // number — undefined (not NaN/0) so callers can tell "not provided" apart
@@ -56,14 +58,60 @@ async function docsScreenshotHandler(c) {
56
58
  const scrollX = queryNumber(c, 'scrollX') ?? 0;
57
59
  const scrollY = queryNumber(c, 'scrollY') ?? 0;
58
60
 
61
+ // `width` and `height` name the clip here, so the viewport size takes the
62
+ // `viewport` prefix; the MCP tool, which nests the clip, calls them width
63
+ // and height.
64
+ for (const key of ['viewportWidth', 'viewportHeight']) {
65
+ const raw = c.req.query(key);
66
+ if (!type.isNone(raw) && type.isUndefined(queryNumber(c, key))) {
67
+ return c.json(
68
+ {
69
+ error: `"${key}" must be a positive integer (CSS pixels). Received ${JSON.stringify(
70
+ raw
71
+ )}.`,
72
+ },
73
+ 400
74
+ );
75
+ }
76
+ }
77
+ const width = queryNumber(c, 'viewportWidth');
78
+ const height = queryNumber(c, 'viewportHeight');
79
+ const colorScheme = c.req.query('colorScheme');
80
+ const viewportError = validateViewport({ width, height, colorScheme });
81
+ if (viewportError) {
82
+ return c.json({ error: viewportError }, 400);
83
+ }
84
+
59
85
  const { user, error: userError } = parseUserParam({ value: c.req.query('user') });
60
86
  if (userError) {
61
87
  return c.json({ error: userError }, 400);
62
88
  }
63
89
 
64
- const result = await screenshotPage({ origin, pageId, fullPage, clip, scrollX, scrollY, user });
90
+ const { pathParams, error: pathParamsError } = parsePathParamsParam({
91
+ value: c.req.query('pathParams'),
92
+ });
93
+ if (pathParamsError) {
94
+ return c.json({ error: pathParamsError }, 400);
95
+ }
96
+
97
+ const result = await screenshotPage({
98
+ origin,
99
+ pageId,
100
+ pathParams,
101
+ fullPage,
102
+ clip,
103
+ scrollX,
104
+ scrollY,
105
+ user,
106
+ data: c.req.query('data'),
107
+ width,
108
+ height,
109
+ colorScheme,
110
+ });
65
111
  if (result.error) {
66
- return c.json({ error: result.error }, 502);
112
+ // pathParams the page cannot be opened with are the caller's to fix, not a
113
+ // failed render.
114
+ return c.json({ error: result.error }, result.invalidInput ? 400 : 502);
67
115
  }
68
116
  const buffer = Buffer.from(result.data, 'base64');
69
117
  return c.body(buffer, 200, { 'Content-Type': result.mimeType });
@@ -0,0 +1,117 @@
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
+ const mockScreenshotPage = jest.fn();
20
+
21
+ jest.unstable_mockModule('../../../lib/docs/screenshotPage.js', () => ({
22
+ default: mockScreenshotPage,
23
+ }));
24
+
25
+ const { default: docsScreenshotHandler } = await import('./screenshot.js');
26
+
27
+ function createContext(url) {
28
+ const parsed = new URL(url);
29
+ const json = jest.fn((data, status) => ({ data, status: status ?? 200 }));
30
+ const body = jest.fn((data, status, headers) => ({ data, status, headers }));
31
+ return {
32
+ req: {
33
+ url,
34
+ param: () => parsed.pathname.split('/').pop(),
35
+ query: (key) => parsed.searchParams.get(key) ?? undefined,
36
+ },
37
+ json,
38
+ body,
39
+ };
40
+ }
41
+
42
+ beforeEach(() => {
43
+ jest.clearAllMocks();
44
+ mockScreenshotPage.mockResolvedValue({ data: 'cG5n', mimeType: 'image/png' });
45
+ });
46
+
47
+ test('docsScreenshotHandler passes the viewport size and colour scheme to screenshotPage', async () => {
48
+ const c = createContext(
49
+ 'http://localhost:3227/lowdefy-docs/screenshot/home?viewportWidth=390&viewportHeight=844&colorScheme=dark'
50
+ );
51
+
52
+ const result = await docsScreenshotHandler(c);
53
+
54
+ expect(mockScreenshotPage).toHaveBeenCalledWith(
55
+ expect.objectContaining({
56
+ origin: 'http://localhost:3227',
57
+ pageId: 'home',
58
+ width: 390,
59
+ height: 844,
60
+ colorScheme: 'dark',
61
+ clip: undefined,
62
+ })
63
+ );
64
+ expect(result.status).toBe(200);
65
+ expect(result.headers).toEqual({ 'Content-Type': 'image/png' });
66
+ });
67
+
68
+ test('docsScreenshotHandler keeps width and height as the clip, apart from the viewport', async () => {
69
+ const c = createContext(
70
+ 'http://localhost:3227/lowdefy-docs/screenshot/home?x=0&y=10&width=200&height=100&viewportWidth=800'
71
+ );
72
+
73
+ await docsScreenshotHandler(c);
74
+
75
+ expect(mockScreenshotPage).toHaveBeenCalledWith(
76
+ expect.objectContaining({
77
+ clip: { x: 0, y: 10, width: 200, height: 100 },
78
+ width: 800,
79
+ height: undefined,
80
+ colorScheme: undefined,
81
+ })
82
+ );
83
+ });
84
+
85
+ test('docsScreenshotHandler returns 400 for an unknown colour scheme', async () => {
86
+ const c = createContext('http://localhost:3227/lowdefy-docs/screenshot/home?colorScheme=dim');
87
+
88
+ const result = await docsScreenshotHandler(c);
89
+
90
+ expect(result.status).toBe(400);
91
+ expect(result.data.error).toEqual('Color scheme must be "light" or "dark". Received "dim".');
92
+ expect(mockScreenshotPage).not.toHaveBeenCalled();
93
+ });
94
+
95
+ test('docsScreenshotHandler returns 400 for a viewport width that is not a positive integer', async () => {
96
+ const c = createContext('http://localhost:3227/lowdefy-docs/screenshot/home?viewportWidth=0');
97
+
98
+ const result = await docsScreenshotHandler(c);
99
+
100
+ expect(result.status).toBe(400);
101
+ expect(result.data.error).toMatch(/Viewport width must be a positive integer/);
102
+ });
103
+
104
+ test.each(['viewportWidth', 'viewportHeight'])(
105
+ 'docsScreenshotHandler returns 400 naming a non-numeric %s',
106
+ async (key) => {
107
+ const c = createContext(`http://localhost:3227/lowdefy-docs/screenshot/home?${key}=wide`);
108
+
109
+ const result = await docsScreenshotHandler(c);
110
+
111
+ expect(result.status).toBe(400);
112
+ expect(result.data.error).toEqual(
113
+ `"${key}" must be a positive integer (CSS pixels). Received "wide".`
114
+ );
115
+ expect(mockScreenshotPage).not.toHaveBeenCalled();
116
+ }
117
+ );
@@ -14,14 +14,27 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
+ import docSources from '../../../lib/docs/docSources.js';
17
18
  import searchDocs from '../../../lib/docs/searchDocs.js';
18
19
 
19
20
  function docsSearchHandler(c) {
20
21
  const query = c.req.query('q');
22
+ const source = c.req.query('source');
21
23
  if (!query || query.trim() === '') {
22
- return c.json({ error: 'Missing search query. Use GET /lowdefy-docs/search?q=your+keywords.' }, 400);
24
+ return c.json(
25
+ { error: 'Missing search query. Use GET /lowdefy-docs/search?q=your+keywords.' },
26
+ 400
27
+ );
23
28
  }
24
- return c.json(searchDocs({ query }));
29
+ if (source !== undefined && !docSources.includes(source)) {
30
+ return c.json(
31
+ {
32
+ error: `Unknown docs source "${source}". Use one of: ${docSources.join(', ')}.`,
33
+ },
34
+ 400
35
+ );
36
+ }
37
+ return c.json(searchDocs({ query, source }));
25
38
  }
26
39
 
27
40
  export default docsSearchHandler;
@@ -14,21 +14,35 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
+ import { parseRequestBody } from '@lowdefy/api';
18
+
17
19
  import snapshotState from '../../../lib/docs/snapshotState.js';
18
20
 
19
21
  // Lets an agent (or a developer) snapshot the running app's page state and
20
22
  // recorded request/api responses into a checkpoint folder under .lowdefy,
21
23
  // for later replay via POST /lowdefy-docs/state/load.
22
24
  async function docsSnapshotStateHandler(c) {
23
- const { pageId, name, notes, source, overwrite } = await c.req.json();
25
+ const { pageId, pathParams, name, notes, source, overwrite } = parseRequestBody({
26
+ text: await c.req.text(),
27
+ });
24
28
  // Derived from the incoming request rather than a config value — this is
25
29
  // the origin an agent can actually reach the dev server on (host/port it
26
30
  // just connected to), regardless of how the server is bound.
27
31
  const origin = new URL(c.req.url).origin;
28
32
 
29
- const result = await snapshotState({ origin, pageId, name, notes, source, overwrite });
33
+ const result = await snapshotState({
34
+ origin,
35
+ pageId,
36
+ pathParams,
37
+ name,
38
+ notes,
39
+ source,
40
+ overwrite,
41
+ });
30
42
  if (result.error) {
31
- return c.json({ error: result.error }, 502);
43
+ // pathParams the page cannot be opened with are the caller's to fix, not a
44
+ // failed render.
45
+ return c.json({ error: result.error }, result.invalidInput ? 400 : 502);
32
46
  }
33
47
  return c.json(result);
34
48
  }
@@ -14,55 +14,56 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import { callEndpoint, getEndpointConfig, runWebhookEndpoint } from '@lowdefy/api';
17
+ import {
18
+ callEndpoint,
19
+ getEndpointConfig,
20
+ parseRequestBody,
21
+ runWebhookEndpoint,
22
+ } from '@lowdefy/api';
18
23
 
19
24
  import getPathSegments from '../lib/getPathSegments.js';
20
25
 
21
26
  async function endpointsHandler(c) {
22
27
  if (c.req.method !== 'POST') {
23
- throw new Error('Only POST requests are supported.');
28
+ // A wrong-method request is client-caused: answer 405 rather than raising a
29
+ // fault that would be logged at error level and answered with a 500.
30
+ return c.json({ error: 'Method not allowed.' }, 405);
24
31
  }
25
32
  const context = c.get('lowdefyContext');
26
33
  const endpointId = getPathSegments(c, '/api/endpoints/').join('/');
27
34
 
28
- // Endpoints opting in with `webhook: true` are third-party webhook receivers
29
- // (SNS, Event Grid, Stripe, ...): they take the request RAW — body in the
30
- // caller's own format (not the { payload } envelope), plus query + headers —
31
- // and their return value is sent back verbatim, because webhook handshakes
32
- // (e.g. Event Grid's validationResponse) require exact response shapes.
33
- // The config lookup is a cached file read; an unknown endpoint falls through
34
- // to the standard path so its error shape is unchanged. Everything about
35
- // non-webhook endpoints is untouched.
35
+ // Endpoints opting in with `webhook` (true or a { verify } object) are
36
+ // third-party webhook receivers (SNS, Event Grid, Stripe, ...): they take the
37
+ // request RAW — body in the caller's own format (not the { payload }
38
+ // envelope), plus query + headers — and their return value is sent back
39
+ // verbatim, because webhook handshakes (e.g. Event Grid's validationResponse)
40
+ // require exact response shapes. The config lookup is a cached file read; an
41
+ // unknown endpoint falls through to the standard path so its error shape is
42
+ // unchanged. Everything about non-webhook endpoints is untouched.
36
43
  let endpointConfig = null;
37
44
  try {
38
45
  endpointConfig = await getEndpointConfig(context, { endpointId });
39
46
  } catch {
40
47
  /* unknown endpoint — the standard path below reports it exactly as before */
41
48
  }
42
- if (endpointConfig?.webhook === true) {
43
- // SNS posts JSON as text/plain — parse regardless of content-type; an
44
- // unparseable body arrives as the raw string.
45
- const raw = await c.req.text();
46
- let body = raw;
47
- try {
48
- body = JSON.parse(raw);
49
- } catch {
50
- /* non-JSON body — raw string passthrough */
51
- }
49
+ if (endpointConfig?.webhook) {
52
50
  context.logger.info({ event: 'call_webhook_endpoint', endpointId });
53
- const result = await runWebhookEndpoint(context, {
51
+ const { status, body, contentType } = await runWebhookEndpoint(context, {
54
52
  endpointId,
55
- body,
53
+ rawBody: await c.req.text(),
56
54
  query: c.req.query(),
57
55
  headers: c.req.header(),
58
56
  });
59
- if (!result.success) {
60
- return c.json({ error: 'Webhook failed.' }, 500);
57
+ if (contentType === 'text/plain') {
58
+ // A text answer often echoes a value from the request (a handshake token), so the browser
59
+ // must not sniff it into HTML.
60
+ c.header('X-Content-Type-Options', 'nosniff');
61
+ return c.text(body, status);
61
62
  }
62
- return c.json(result.response ?? { ok: true });
63
+ return c.json(body, status);
63
64
  }
64
65
 
65
- const { blockId, payload, pageId } = await c.req.json();
66
+ const { blockId, payload, pageId } = parseRequestBody({ text: await c.req.text() });
66
67
  context.logger.info({ event: 'call_api_endpoint', blockId, endpointId, pageId });
67
68
  const response = await callEndpoint(context, { blockId, endpointId, pageId, payload });
68
69
  return c.json(response);