@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,62 @@
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 { Hono } from 'hono';
18
+
19
+ import normalizeMcpProtocolVersionHeader from './normalizeMcpProtocolVersionHeader.js';
20
+
21
+ function createApp() {
22
+ const app = new Hono();
23
+ app.post('/api/mcp', (c) => {
24
+ normalizeMcpProtocolVersionHeader({ request: c.req.raw });
25
+ return c.json({ protocolVersion: c.req.header('mcp-protocol-version') ?? null });
26
+ });
27
+ return app;
28
+ }
29
+
30
+ async function send(headerValue) {
31
+ const headers = new Headers();
32
+ if (headerValue !== undefined) {
33
+ headers.set('mcp-protocol-version', headerValue);
34
+ }
35
+ const res = await createApp().request('/api/mcp', { method: 'POST', headers });
36
+ return (await res.json()).protocolVersion;
37
+ }
38
+
39
+ test('collapses a repeated protocol version to the single version it names', async () => {
40
+ expect(await send('2025-11-25, 2025-11-25')).toEqual('2025-11-25');
41
+ });
42
+
43
+ test('collapses the value the Fetch Headers constructor builds from both header spellings', async () => {
44
+ const headers = new Headers({
45
+ 'mcp-protocol-version': '2025-11-25',
46
+ 'MCP-Protocol-Version': '2025-11-25',
47
+ });
48
+ const res = await createApp().request('/api/mcp', { method: 'POST', headers });
49
+ expect((await res.json()).protocolVersion).toEqual('2025-11-25');
50
+ });
51
+
52
+ test('leaves a single protocol version untouched', async () => {
53
+ expect(await send('2025-06-18')).toEqual('2025-06-18');
54
+ });
55
+
56
+ test('leaves a header naming two different versions for the transport to refuse', async () => {
57
+ expect(await send('2025-11-25, 2025-06-18')).toEqual('2025-11-25, 2025-06-18');
58
+ });
59
+
60
+ test('leaves an absent header absent', async () => {
61
+ expect(await send(undefined)).toEqual(null);
62
+ });
@@ -20,6 +20,21 @@ import chokidar from 'chokidar';
20
20
  import { streamSSE } from 'hono/streaming';
21
21
 
22
22
  import { registerTab, unregisterTab } from '../../lib/docs/tabChannel.js';
23
+ import readRecordingCookie from '../../lib/server/recording/readRecordingCookie.js';
24
+
25
+ // A verified recording cookie means the dev server's own headless browser
26
+ // opened the tab: a journey run ({ source }), or a headless
27
+ // tool ('off'). A developer's tab carries none.
28
+ function tabOrigin(cookieHeader) {
29
+ const recording = readRecordingCookie(cookieHeader);
30
+ if (recording === null) {
31
+ return { source: 'dev', automated: false };
32
+ }
33
+ if (recording === 'off') {
34
+ return { source: 'headless', automated: true };
35
+ }
36
+ return { source: recording.source, automated: true };
37
+ }
23
38
 
24
39
  // SSE endpoint — notifies the client when build/reload is written so it can
25
40
  // mutate the SWR cache and refetch config. Also doubles as the transport for
@@ -46,6 +61,7 @@ async function reloadHandler(c) {
46
61
  registerTab({
47
62
  id: tabId,
48
63
  send: (event, data) => stream.writeSSE({ event, data: JSON.stringify(data) }),
64
+ ...tabOrigin(c.req.header('cookie')),
49
65
  });
50
66
 
51
67
  let open = true;
@@ -14,7 +14,7 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import { callRequest, redactErrorResponse } from '@lowdefy/api';
17
+ import { callRequest, parseRequestBody, redactErrorResponse } from '@lowdefy/api';
18
18
  import { serializer } from '@lowdefy/helpers';
19
19
 
20
20
  import buildPageIfNeeded from '../../lib/server/jitPageBuilder.js';
@@ -23,7 +23,9 @@ import { getMock } from '../../lib/docs/devMockRegistry.js';
23
23
 
24
24
  async function requestHandler(c) {
25
25
  if (c.req.method !== 'POST') {
26
- throw new Error('Only POST requests are supported.');
26
+ // A wrong-method request is client-caused: answer 405 rather than raising a
27
+ // fault that would be logged at error level and answered with a 500.
28
+ return c.json({ error: 'Method not allowed.' }, 405);
27
29
  }
28
30
  const context = c.get('lowdefyContext');
29
31
  const segments = getPathSegments(c, '/api/request/');
@@ -32,7 +34,7 @@ async function requestHandler(c) {
32
34
  }
33
35
  const requestId = segments[segments.length - 1];
34
36
  const pageId = segments.slice(0, -1).join('/');
35
- const { actionId, blockId, payload } = await c.req.json();
37
+ const { actionId, blockId, payload } = parseRequestBody({ text: await c.req.text() });
36
38
 
37
39
  // Dev-only agent tooling: once a state checkpoint has been loaded
38
40
  // (lib/docs/loadState.js), its recorded request/response pairs live in
@@ -55,7 +57,7 @@ async function requestHandler(c) {
55
57
 
56
58
  // Page artifacts (including pages/{pageId}/requests/{requestId}.json) are
57
59
  // built JIT by GET /api/page/* and thrown away on every page invalidation
58
- // (lowdefyBuildWatcher / moduleBuildWatcher). A client that already holds
60
+ // (lowdefyBuildWatcher). A client that already holds
59
61
  // the page config keeps firing requests across that window — and a request
60
62
  // that arrived before the page was rebuilt failed with
61
63
  // `Request "x" does not exist.` Run the same idempotent build the page route
@@ -20,6 +20,7 @@ import { jest } from '@jest/globals';
20
20
  const mockCallRequest = jest.fn();
21
21
  jest.unstable_mockModule('@lowdefy/api', () => ({
22
22
  callRequest: mockCallRequest,
23
+ parseRequestBody: ({ text }) => JSON.parse(text),
23
24
  redactErrorResponse: jest.fn((context, error) => ({ error: error.message })),
24
25
  }));
25
26
 
@@ -115,10 +116,13 @@ test('requestHandler replays a dev mock without building or calling the request'
115
116
  expect(mockCallRequest).not.toHaveBeenCalled();
116
117
  });
117
118
 
118
- test('requestHandler rejects non-POST methods', async () => {
119
- const app = createApp();
120
- app.onError((err, c) => c.json({ error: err.message }, 500));
121
- const res = await app.request('/api/request/dashboard/get_rows');
122
- expect(res.status).toEqual(500);
119
+ // A GET to the request route is client-caused, so it must answer 405 rather
120
+ // than throwing into the error handler and being logged as a fault with a 500.
121
+ test('requestHandler returns 405 without building or calling the request for a non-POST method', async () => {
122
+ const res = await createApp().request('/api/request/dashboard/get_rows');
123
+
124
+ expect(res.status).toEqual(405);
125
+ expect(await res.json()).toEqual({ error: 'Method not allowed.' });
123
126
  expect(mockBuildPageIfNeeded).not.toHaveBeenCalled();
127
+ expect(mockCallRequest).not.toHaveBeenCalled();
124
128
  });
@@ -0,0 +1,159 @@
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 { fileURLToPath } from 'node:url';
18
+
19
+ import { Hono } from 'hono';
20
+ import { jest } from '@jest/globals';
21
+
22
+ import { isSameOriginRequest, parseRequestBody } from '@lowdefy/api';
23
+
24
+ // usage.js reads package.json from the working directory as it loads, and a suite that
25
+ // ran earlier in the same jest worker may have left the working directory in a temporary
26
+ // fixture directory.
27
+ process.chdir(fileURLToPath(new URL('../..', import.meta.url)));
28
+
29
+ const mockGetEndpointConfig = jest.fn().mockRejectedValue(new Error('not found'));
30
+ const mockRunWebhookEndpoint = jest.fn();
31
+ jest.unstable_mockModule('@lowdefy/api', () => ({
32
+ acceptDetachedEndpoint: jest.fn(),
33
+ callAgent: jest.fn(),
34
+ callEndpoint: jest.fn(),
35
+ callRequest: jest.fn(),
36
+ getEndpointConfig: mockGetEndpointConfig,
37
+ logClientError: jest.fn(),
38
+ isSameOriginRequest,
39
+ parseRequestBody,
40
+ redactErrorResponse: jest.fn(),
41
+ runWebhookEndpoint: mockRunWebhookEndpoint,
42
+ }));
43
+ jest.unstable_mockModule('../../lib/server/jitPageBuilder.js', () => ({ default: jest.fn() }));
44
+ jest.unstable_mockModule('../../lib/docs/devMockRegistry.js', () => ({
45
+ getMock: jest.fn(),
46
+ loadMocks: jest.fn(),
47
+ }));
48
+ jest.unstable_mockModule('../../lib/docs/loadState.js', () => ({ default: jest.fn() }));
49
+ jest.unstable_mockModule('../../lib/docs/snapshotState.js', () => ({ default: jest.fn() }));
50
+
51
+ const { default: createErrorHandler } = await import('../middleware/errorHandler.js');
52
+ const routes = {
53
+ '/api/agent/*': (await import('./agent.js')).default,
54
+ '/api/client-error': (await import('./clientError.js')).default,
55
+ '/api/detached/*': (await import('./detached.js')).default,
56
+ '/api/dev-inspect': (await import('./devInspect.js')).default,
57
+ '/api/dev-inspect/*': (await import('./devInspect.js')).default,
58
+ '/api/endpoints/*': (await import('./endpoints.js')).default,
59
+ '/api/request/*': (await import('./request.js')).default,
60
+ '/api/usage': (await import('./usage.js')).default,
61
+ '/lowdefy-docs/state-checkpoints/load': (await import('./docs/loadState.js')).default,
62
+ '/lowdefy-docs/state-checkpoints/snapshot': (await import('./docs/snapshotState.js')).default,
63
+ '/lowdefy-feedback': (await import('./feedback.js')).default,
64
+ };
65
+
66
+ const logger = { debug: jest.fn(), error: jest.fn(), info: jest.fn(), warn: jest.fn() };
67
+
68
+ function createApp() {
69
+ const app = new Hono();
70
+ app.use('*', async (c, next) => {
71
+ c.set('lowdefyContext', { logger, handleError: jest.fn() });
72
+ await next();
73
+ });
74
+ Object.entries(routes).forEach(([path, handler]) => app.all(path, handler));
75
+ app.onError(createErrorHandler({ logger }));
76
+ return app;
77
+ }
78
+
79
+ const originalCronSecret = process.env.CRON_SECRET;
80
+ beforeAll(() => {
81
+ process.env.CRON_SECRET = 'secret';
82
+ });
83
+ afterAll(() => {
84
+ if (originalCronSecret === undefined) {
85
+ delete process.env.CRON_SECRET;
86
+ } else {
87
+ process.env.CRON_SECRET = originalCronSecret;
88
+ }
89
+ });
90
+
91
+ const sameOrigin = { host: 'localhost', origin: 'http://localhost' };
92
+ const notJson = JSON.stringify({ name: 'UserError', message: 'Request body is not valid JSON.' });
93
+
94
+ // Paths outside /api/ get the error handler's plain-text answer.
95
+ test.each([
96
+ ['/api/endpoints/save', {}, notJson],
97
+ ['/api/request/orders/load', {}, notJson],
98
+ ['/api/agent/orders/assistant', {}, notJson],
99
+ ['/api/detached/save', { authorization: 'Bearer secret' }, notJson],
100
+ ['/api/usage', {}, notJson],
101
+ ['/api/client-error', sameOrigin, notJson],
102
+ ['/api/dev-inspect', sameOrigin, notJson],
103
+ ['/api/dev-inspect/page', sameOrigin, notJson],
104
+ ['/lowdefy-docs/state-checkpoints/load', {}, 'Bad Request'],
105
+ ['/lowdefy-docs/state-checkpoints/snapshot', {}, 'Bad Request'],
106
+ ['/lowdefy-feedback', sameOrigin, 'Bad Request'],
107
+ ])(
108
+ 'POST %s with a body that is not JSON answers 400 and logs a warning',
109
+ async (path, headers, text) => {
110
+ const res = await createApp().request(path, { method: 'POST', body: 'nope', headers });
111
+ expect(res.status).toBe(400);
112
+ expect(await res.text()).toBe(text);
113
+ expect(logger.warn).toHaveBeenCalledTimes(1);
114
+ expect(logger.error).not.toHaveBeenCalled();
115
+ }
116
+ );
117
+
118
+ test('POST /api/endpoints with a null body answers 400', async () => {
119
+ const res = await createApp().request('/api/endpoints/save', { method: 'POST', body: 'null' });
120
+ expect(res.status).toBe(400);
121
+ expect(await res.json()).toEqual({
122
+ name: 'UserError',
123
+ message: 'Request body must be a JSON object.',
124
+ });
125
+ });
126
+
127
+ test('POST /api/endpoints to a webhook endpoint passes the body text exactly as sent as rawBody', async () => {
128
+ mockGetEndpointConfig.mockResolvedValueOnce({ webhook: { verify: {} } });
129
+ mockRunWebhookEndpoint.mockResolvedValue({ status: 200, body: { ok: true } });
130
+ const body = '{ "zeta": 1, "alpha": "caf\\u00e9", "beta": "café" }';
131
+ const res = await createApp().request('/api/endpoints/signed_hook?t=1', {
132
+ method: 'POST',
133
+ body,
134
+ headers: { 'x-hub-signature-256': 'sha256=abc' },
135
+ });
136
+ expect(res.status).toBe(200);
137
+ expect(mockRunWebhookEndpoint).toHaveBeenCalledWith(expect.anything(), {
138
+ endpointId: 'signed_hook',
139
+ rawBody: body,
140
+ query: { t: '1' },
141
+ headers: expect.objectContaining({ 'x-hub-signature-256': 'sha256=abc' }),
142
+ });
143
+ });
144
+
145
+ test.each([
146
+ [200, { received: true }],
147
+ [401, { error: { code: 'unauthorized', message: 'Webhook verification failed.' } }],
148
+ [404, { error: 'not_found', id: 'ticket-1' }],
149
+ [500, { error: { code: 'internal_error', message: 'Webhook failed.' } }],
150
+ ])(
151
+ 'POST /api/endpoints to a webhook endpoint answers status %s with its body',
152
+ async (status, body) => {
153
+ mockGetEndpointConfig.mockResolvedValueOnce({ webhook: true });
154
+ mockRunWebhookEndpoint.mockResolvedValue({ status, body });
155
+ const res = await createApp().request('/api/endpoints/hook', { method: 'POST', body: '{}' });
156
+ expect(res.status).toBe(status);
157
+ expect(await res.json()).toEqual(body);
158
+ }
159
+ );
@@ -17,14 +17,18 @@
17
17
  import fs from 'node:fs';
18
18
  import path from 'node:path';
19
19
 
20
+ import { parseRequestBody } from '@lowdefy/api';
21
+
20
22
  const packageJson = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf8'));
21
23
 
22
24
  async function usageHandler(c) {
23
25
  if (c.req.method !== 'POST') {
24
- throw new Error('Only POST requests are supported.');
26
+ // A wrong-method request is client-caused: answer 405 rather than raising a
27
+ // fault that would be logged at error level and answered with a 500.
28
+ return c.json({ error: 'Method not allowed.' }, 405);
25
29
  }
26
30
  const context = c.get('lowdefyContext');
27
- const { user, machine } = await c.req.json();
31
+ const { user, machine } = parseRequestBody({ text: await c.req.text() });
28
32
  const host = c.req.header('host');
29
33
  context.logger.info({ event: 'log_usage', user, machine });
30
34
 
@@ -0,0 +1,27 @@
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 resolved caller for the client - the same resolveAuthentication output
18
+ // the first page load embeds. The base session carries neither roles (read
19
+ // live from the active member row) nor the merged attributes bag, so the
20
+ // client re-syncs from here (UpdateSession) after a session change such as
21
+ // SetActiveOrganization.
22
+ async function userHandler(c) {
23
+ const context = c.get('lowdefyContext');
24
+ return c.json({ user: context.user ?? null });
25
+ }
26
+
27
+ export default userHandler;
@@ -14,19 +14,40 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import { setPendingWebSocketContext } from '../websocket/devWebSocket.js';
17
+ import { isWebSocketOriginAllowed } from '@lowdefy/api';
18
+ import { type } from '@lowdefy/helpers';
19
+
20
+ import getStrategies from '../../lib/server/auth/getStrategies.js';
21
+ import isRebindingSafeHost from '../middleware/isRebindingSafeHost.js';
18
22
 
19
23
  // In dev, Vite owns the HTTP server, so websocket upgrades can't flow through
20
- // @hono/node-server. The Vite plugin (see vite.config.js) fetches this route
21
- // with the upgrade request's headers to run the full middleware chain (auth
22
- // session, apiContext), and this handler hands the built context back to the
23
- // upgrade handler through a pending-context exchange keyed by request id.
24
+ // @hono/node-server. The upgrade handler (src/websocket/devWebSocket.js) runs
25
+ // this route with the upgrade request's headers so the full middleware chain
26
+ // (auth session, apiContext) builds the request context, and passes a
27
+ // websocketUpgrade object in the Hono env; this handler puts the context and
28
+ // auth strategies on it. A plain HTTP request to this route carries no such object.
24
29
  function websocketHandler(c) {
25
- const rid = c.req.header('x-lowdefy-websocket-rid');
26
- if (!rid) {
30
+ const websocketUpgrade = c.env?.websocketUpgrade;
31
+ if (type.isNone(websocketUpgrade)) {
27
32
  return c.json({ message: 'WebSocket upgrade required.' }, 400);
28
33
  }
29
- setPendingWebSocketContext(rid, c.get('lowdefyContext'));
34
+ // Vite's host check guards HTTP requests against DNS rebinding, but upgrades
35
+ // reach this route through a plugin's upgrade listener, past that check - and
36
+ // a rebound page's Origin matches its Host. So the Host must be one no DNS
37
+ // answer can rebind, as Vite requires of every HTTP request.
38
+ if (!isRebindingSafeHost({ host: c.req.header('host') })) {
39
+ return c.json({ error: 'Forbidden' }, 403);
40
+ }
41
+ const context = c.get('lowdefyContext');
42
+ // Upgrades are accepted from the app's own pages and from clients that send
43
+ // no Origin; isWebSocketOriginAllowed holds the rule and logs a refusal.
44
+ if (!isWebSocketOriginAllowed({ context, getHeader: (name) => c.req.header(name) })) {
45
+ return c.json({ error: 'Forbidden' }, 403);
46
+ }
47
+ websocketUpgrade.context = context;
48
+ // The connection resolves its caller again while the socket is open, with
49
+ // the upgrade's headers and the strategies of the current build.
50
+ websocketUpgrade.strategies = getStrategies({ logger: context.logger });
30
51
  return c.json({ ok: true });
31
52
  }
32
53
 
@@ -0,0 +1,65 @@
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 { Hono } from 'hono';
18
+ import { jest } from '@jest/globals';
19
+
20
+ const mockStrategies = [];
21
+ jest.unstable_mockModule('../../lib/server/auth/getStrategies.js', () => ({
22
+ default: () => mockStrategies,
23
+ }));
24
+
25
+ const { default: websocketHandler } = await import('./websocket.js');
26
+
27
+ // The dev upgrade listener (src/websocket/devWebSocket.js) runs this route
28
+ // through the app and opens the websocket only on a 200 that carries the
29
+ // context - so a refusal here is a refused upgrade.
30
+ async function upgrade({ headers }) {
31
+ const app = new Hono().basePath('/app');
32
+ app.use('*', async (c, next) => {
33
+ c.set('lowdefyContext', { config: {}, logger: { warn: () => {} }, rid: 'request' });
34
+ await next();
35
+ });
36
+ app.get('/api/websocket', websocketHandler);
37
+ const websocketUpgrade = { context: null, strategies: null };
38
+ const res = await app.request('/app/api/websocket', { headers }, { websocketUpgrade });
39
+ return {
40
+ status: res.status,
41
+ context: websocketUpgrade.context,
42
+ strategies: websocketUpgrade.strategies,
43
+ };
44
+ }
45
+
46
+ test.each([
47
+ ['the dev app page opens it', { host: 'localhost:4100', origin: 'http://localhost:4100' }],
48
+ ['a client sends no origin', { host: 'localhost:4100' }],
49
+ ])('the dev websocket route opens the upgrade when %s', async (_, headers) => {
50
+ expect(await upgrade({ headers })).toEqual({
51
+ status: 200,
52
+ context: expect.any(Object),
53
+ strategies: mockStrategies,
54
+ });
55
+ });
56
+
57
+ test.each([
58
+ ['the origin is another site', { host: 'localhost:4100', origin: 'https://other.test' }],
59
+ [
60
+ 'a rebound domain names itself in both host and origin',
61
+ { host: 'rebound.test:4100', origin: 'http://rebound.test:4100' },
62
+ ],
63
+ ])('the dev websocket route refuses the upgrade when %s', async (_, headers) => {
64
+ expect(await upgrade({ headers })).toEqual({ status: 403, context: null, strategies: null });
65
+ });
@@ -0,0 +1,30 @@
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
+ // RFC 8615 well-known URIs are machine-facing: a path this deployment does not
18
+ // serve must answer 404, never the app's HTML shell. Serving 200 HTML breaks
19
+ // clients that probe discovery documents - an MCP client's OAuth probe, for
20
+ // example, fails with "Failed to parse JSON" instead of learning there is no
21
+ // auth to discover. Mounted after the real discovery documents, before the
22
+ // page catch-all.
23
+ function wellKnownFallbackHandler(c) {
24
+ return c.json(
25
+ { name: 'NotFoundError', message: `No well-known document at ${c.req.path}.` },
26
+ 404
27
+ );
28
+ }
29
+
30
+ export default wellKnownFallbackHandler;
@@ -0,0 +1,32 @@
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
+ import wellKnownFallbackHandler from './wellKnownFallback.js';
20
+
21
+ test('an unserved well-known path answers 404 JSON, never the app shell', () => {
22
+ const json = jest.fn((data, status) => ({ data, status }));
23
+ const result = wellKnownFallbackHandler({
24
+ req: { path: '/.well-known/oauth-protected-resource' },
25
+ json,
26
+ });
27
+ expect(result.status).toBe(404);
28
+ expect(result.data).toEqual({
29
+ name: 'NotFoundError',
30
+ message: 'No well-known document at /.well-known/oauth-protected-resource.',
31
+ });
32
+ });
@@ -14,54 +14,69 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import { randomUUID } from 'node:crypto';
17
+ import { STATUS_CODES } from 'node:http';
18
18
  import { createChannelRegistry, createWebSocketConnection } from '@lowdefy/api';
19
+ import { type } from '@lowdefy/helpers';
19
20
 
20
- import app from '../app.js';
21
+ import createLogger from '../../lib/server/log/createLogger.js';
22
+
23
+ const logger = createLogger();
21
24
 
22
25
  // One registry per dev server process, shared by all websocket connections.
23
26
  const registry = createChannelRegistry();
24
27
 
25
- // Contexts built by the /api/websocket route (full middleware chain) are
26
- // handed to the upgrade handler through this exchange, keyed by request id.
27
- const pendingContexts = new Map();
28
-
29
- function setPendingWebSocketContext(rid, context) {
30
- pendingContexts.set(rid, context);
31
- }
32
-
33
- function takePendingWebSocketContext(rid) {
34
- const context = pendingContexts.get(rid);
35
- pendingContexts.delete(rid);
36
- return context;
37
- }
38
-
39
- // Called by the Vite plugin on an HTTP upgrade for /api/websocket. Runs the
40
- // dev Hono app for the upgrade request (auth session + apiContext), then
41
- // completes the websocket handshake and wires the connection to the registry.
42
- async function handleWebSocketUpgrade({ request, socket, head, wss }) {
43
- const rid = randomUUID();
28
+ // Called by the Vite plugin (see vite.config.js) on an HTTP upgrade for
29
+ // /api/websocket, with the dev Hono app. Runs the app for the upgrade request
30
+ // (auth session + apiContext) and receives the request context through the
31
+ // websocketUpgrade object in the Hono env, then completes the websocket
32
+ // handshake and wires the connection to the registry. The context travels
33
+ // with the request, not through state shared between modules, so the upgrade
34
+ // works however Vite has re-evaluated the app's modules after a rebuild.
35
+ async function handleWebSocketUpgrade({ app, request, socket, head, wss }) {
44
36
  const headers = new Headers();
45
37
  Object.entries(request.headers).forEach(([key, value]) => {
46
38
  if (value) {
47
39
  headers.set(key, Array.isArray(value) ? value[0] : value);
48
40
  }
49
41
  });
50
- headers.set('x-lowdefy-websocket-rid', rid);
51
42
 
52
43
  const url = `http://${request.headers.host ?? 'localhost'}${request.url}`;
53
- const response = await app.fetch(new Request(url, { headers }));
54
- const context = takePendingWebSocketContext(rid);
44
+ const websocketUpgrade = { context: null, strategies: null };
45
+ const response = await app.fetch(new Request(url, { headers }), { websocketUpgrade });
46
+
47
+ if (response.status !== 200) {
48
+ const body = await response.text();
49
+ logger.warn(
50
+ { event: 'ws_upgrade_refused', status: response.status },
51
+ `WebSocket upgrade refused: ${request.url} answered ${response.status}. ${body}`
52
+ );
53
+ socket.end(
54
+ `HTTP/1.1 ${response.status} ${STATUS_CODES[response.status]}\r\nConnection: close\r\n\r\n`
55
+ );
56
+ return;
57
+ }
55
58
 
56
- if (!context || response.status !== 200) {
59
+ // A 200 without a context means the request reached another handler than
60
+ // the websocket route (a route or middleware answered first).
61
+ const { context, strategies } = websocketUpgrade;
62
+ if (type.isNone(context)) {
63
+ logger.warn(
64
+ { event: 'ws_upgrade_refused', status: response.status },
65
+ `WebSocket upgrade refused: ${request.url} answered ${response.status} without a request context; the websocket route did not run.`
66
+ );
57
67
  socket.end('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
58
68
  return;
59
69
  }
60
70
 
61
71
  wss.handleUpgrade(request, socket, head, (ws) => {
72
+ // The upgrade's headers stay with the connection: its caller is resolved
73
+ // from them again while the socket is open.
62
74
  const connection = createWebSocketConnection(context, {
75
+ closeSocket: ({ code, reason }) => ws.close(code, reason),
76
+ headers,
63
77
  registry,
64
78
  send: (message) => ws.send(message),
79
+ strategies,
65
80
  });
66
81
  ws.on('message', (data) => {
67
82
  connection.handleMessage(data.toString());
@@ -75,4 +90,4 @@ async function handleWebSocketUpgrade({ request, socket, head, wss }) {
75
90
  });
76
91
  }
77
92
 
78
- export { handleWebSocketUpgrade, setPendingWebSocketContext };
93
+ export { handleWebSocketUpgrade };