@lowdefy/server-dev 6.1.0 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (624) hide show
  1. package/client/App.jsx +52 -24
  2. package/client/Inspector.jsx +73 -30
  3. package/client/JourneyObserver.jsx +47 -0
  4. package/client/Page.jsx +54 -22
  5. package/client/Recorder.jsx +73 -0
  6. package/client/Routing.jsx +37 -13
  7. package/client/clientBuildImports.test.mjs +2 -0
  8. package/client/devStreamConnections.test.mjs +40 -0
  9. package/client/feedback/FeedbackMount.jsx +2 -2
  10. package/client/feedback/FeedbackOverlay.jsx +7 -4
  11. package/client/feedback/feedbackStyles.js +3 -0
  12. package/client/journeyObserver/startJourneyObserver.js +101 -0
  13. package/client/journeyObserver/startJourneyObserver.test.mjs +149 -0
  14. package/client/openInEditor/OpenInEditorListener.jsx +2 -0
  15. package/client/recorder/buildEngineRecord.js +37 -0
  16. package/client/recorder/buildInteractionRecord.js +64 -0
  17. package/client/recorder/buildTraceEvent.js +57 -0
  18. package/client/recorder/buildTraceEvent.test.mjs +81 -0
  19. package/client/recorder/chunkRecords.js +69 -0
  20. package/client/recorder/chunkRecords.test.mjs +59 -0
  21. package/client/recorder/collectEventActions.js +30 -0
  22. package/client/recorder/collectEventEndpoints.js +40 -0
  23. package/client/recorder/collectEventRequests.js +39 -0
  24. package/client/recorder/createPairingBuffer.js +187 -0
  25. package/client/recorder/createPairingBuffer.test.mjs +405 -0
  26. package/client/recorder/createPasswordRedactor.js +84 -0
  27. package/client/recorder/createRecordBatcher.js +54 -0
  28. package/client/recorder/createRecordBatcher.test.mjs +76 -0
  29. package/client/recorder/createRecorder.js +216 -0
  30. package/client/recorder/createRecorder.test.mjs +318 -0
  31. package/client/recorder/createRecordingSession.js +61 -0
  32. package/client/recorder/createRecordingSession.test.mjs +72 -0
  33. package/client/recorder/diffStateWrites.js +77 -0
  34. package/client/recorder/diffStateWrites.test.mjs +66 -0
  35. package/client/recorder/isEventCall.js +30 -0
  36. package/client/recorder/jsonType.js +39 -0
  37. package/client/recorder/keyChord.js +47 -0
  38. package/client/recorder/keyChord.test.mjs +50 -0
  39. package/client/recorder/sendRecords.js +40 -0
  40. package/client/recorder/toJsonSafe.js +29 -0
  41. package/lib/client/BuildingPage.jsx +3 -1
  42. package/lib/client/ErrorBar.jsx +17 -13
  43. package/lib/client/RedirectingPage.jsx +56 -0
  44. package/lib/client/auth/Auth.jsx +11 -6
  45. package/lib/client/auth/AuthConfigured.jsx +222 -36
  46. package/lib/client/auth/AuthNotConfigured.js +45 -4
  47. package/lib/client/auth/authNotConfiguredParity.test.mjs +61 -0
  48. package/lib/client/auth/authTemplateSync.test.mjs +40 -0
  49. package/lib/client/auth/betterAuthSessionContract.test.mjs +181 -0
  50. package/lib/client/auth/createFetchCheckingBuild.js +45 -0
  51. package/lib/client/bundleBuildId.js +25 -0
  52. package/lib/client/collectVisibleBlockIds.js +51 -0
  53. package/lib/client/getCspNonce.js +25 -0
  54. package/lib/client/getPagePath.js +36 -0
  55. package/lib/client/getPagePath.test.mjs +51 -0
  56. package/lib/client/utils/formatErrorsForCopy.js +30 -0
  57. package/lib/client/utils/formatErrorsForCopy.test.mjs +39 -0
  58. package/lib/client/utils/getErrorBarColor.js +25 -0
  59. package/lib/client/utils/getErrorBarColor.test.mjs +34 -0
  60. package/lib/client/utils/request.js +8 -1
  61. package/lib/client/utils/usePageConfig.js +89 -25
  62. package/lib/client/utils/usePageConfig.test.mjs +198 -13
  63. package/lib/configTree/buildConfigTree.mjs +58 -0
  64. package/lib/configTree/buildConfigTree.test.mjs +77 -0
  65. package/lib/configTree/collectConfigText.mjs +101 -0
  66. package/lib/configTree/collectConfigText.test.mjs +116 -0
  67. package/lib/configTree/prepareScratchServer.mjs +46 -0
  68. package/lib/configTree/runConfigTreeBuild.mjs +88 -0
  69. package/lib/configTree/runConfigTreeBuild.skipPluginChecks.test.mjs +74 -0
  70. package/lib/configTree/runConfigTreeBuild.test.mjs +202 -0
  71. package/lib/docs/JourneyStepError.js +30 -0
  72. package/lib/docs/acquireBrowserSlot.js +30 -0
  73. package/lib/docs/addNestedEndpoints.js +54 -0
  74. package/lib/docs/addNestedEndpoints.test.mjs +185 -0
  75. package/lib/docs/addWriteFlags.js +77 -0
  76. package/lib/docs/buildEditedPages.js +50 -0
  77. package/lib/docs/buildWaitHeader.js +21 -0
  78. package/lib/docs/captureAnnotatedScreenshot.js +23 -12
  79. package/lib/docs/checkAgentFiles.js +148 -0
  80. package/lib/docs/checkAgentFiles.test.mjs +225 -0
  81. package/lib/docs/checkStepReferences.js +239 -0
  82. package/lib/docs/checkStepReferences.test.mjs +173 -0
  83. package/lib/docs/checkpointStore.js +3 -0
  84. package/lib/docs/clientErrorStore.js +18 -1
  85. package/lib/docs/clientErrorStore.test.mjs +46 -0
  86. package/lib/docs/collectExercised.js +52 -0
  87. package/lib/docs/createBrowserLifecycle.js +102 -0
  88. package/lib/docs/createBrowserLifecycle.test.mjs +212 -0
  89. package/lib/docs/createDocsMcpServer.js +345 -388
  90. package/lib/docs/createDocsMcpServer.test.mjs +644 -4
  91. package/lib/docs/createDocsMcpServerRunJourney.test.mjs +148 -0
  92. package/lib/docs/createJourneyActors.chromium.test.mjs +227 -0
  93. package/lib/docs/createJourneyActors.js +213 -0
  94. package/lib/docs/createJourneyActors.test.mjs +79 -0
  95. package/lib/docs/createJourneyObservations.js +80 -0
  96. package/lib/docs/createLeftOriginError.js +26 -0
  97. package/lib/docs/createNetworkCounter.js +101 -0
  98. package/lib/docs/dataSets/checkSharedCollectionIds.js +78 -0
  99. package/lib/docs/dataSets/dataSessionRegistry.js +25 -0
  100. package/lib/docs/dataSets/dataSetJourneys.chromium.mongodb.test.mjs +727 -0
  101. package/lib/docs/dataSets/describeDataSetResult.js +25 -0
  102. package/lib/docs/dataSets/describeDatabaseTarget.js +46 -0
  103. package/lib/docs/dataSets/describeTargetKey.js +29 -0
  104. package/lib/docs/dataSets/deserializeFixture.js +34 -0
  105. package/lib/docs/dataSets/drainSessionWork.js +37 -0
  106. package/lib/docs/dataSets/drainSessionWork.test.mjs +64 -0
  107. package/lib/docs/dataSets/duplicateIndexName.js +24 -0
  108. package/lib/docs/dataSets/getDataStore.js +106 -0
  109. package/lib/docs/dataSets/getDataStore.mongodb.test.mjs +63 -0
  110. package/lib/docs/dataSets/getDataStore.test.mjs +195 -0
  111. package/lib/docs/dataSets/groupDataSetByCollection.js +52 -0
  112. package/lib/docs/dataSets/insertFixture.js +57 -0
  113. package/lib/docs/dataSets/insertGeneratedDocuments.js +41 -0
  114. package/lib/docs/dataSets/isDataStoreGone.js +29 -0
  115. package/lib/docs/dataSets/loadDataSetCollection.js +42 -0
  116. package/lib/docs/dataSets/mergeCollectionIndexes.js +52 -0
  117. package/lib/docs/dataSets/mergeCollectionIndexes.test.mjs +59 -0
  118. package/lib/docs/dataSets/openDataSession.js +133 -0
  119. package/lib/docs/dataSets/openDataSession.mongodb.test.mjs +493 -0
  120. package/lib/docs/dataSets/pickDataStorePort.js +58 -0
  121. package/lib/docs/dataSets/pickDataStorePort.test.mjs +63 -0
  122. package/lib/docs/dataSets/readConnectionArtifacts.js +46 -0
  123. package/lib/docs/dataSets/readDataSession.js +41 -0
  124. package/lib/docs/dataSets/readDataSession.test.mjs +57 -0
  125. package/lib/docs/dataSets/readDataSet.js +83 -0
  126. package/lib/docs/dataSets/readDataSet.test.mjs +251 -0
  127. package/lib/docs/dataSets/registerDataStoreShutdown.js +47 -0
  128. package/lib/docs/dataSets/removeDataStoreFiles.js +32 -0
  129. package/lib/docs/dataSets/resolveDataSetCollection.js +46 -0
  130. package/lib/docs/dataSets/resolveJourneyDataSet.js +106 -0
  131. package/lib/docs/dataSets/resolveJourneyDataSet.test.mjs +130 -0
  132. package/lib/docs/dataSets/sessionDatabaseName.js +32 -0
  133. package/lib/docs/dataSets/sweepOrphanDatabases.js +36 -0
  134. package/lib/docs/dataSets/test/app/lowdefy.yaml +225 -0
  135. package/lib/docs/dataSets/test/app/modules/archive/module.lowdefy.yaml +30 -0
  136. package/lib/docs/dataSets/test/app/tests/data/shop.yaml +12 -0
  137. package/lib/docs/dataSets/test/buildTestApp.mjs +43 -0
  138. package/lib/docs/devEventBus.js +136 -0
  139. package/lib/docs/devEventBus.test.mjs +269 -0
  140. package/lib/docs/devToolDefinitions.js +529 -0
  141. package/lib/docs/docSources.js +21 -0
  142. package/lib/docs/docs.test.mjs +171 -15
  143. package/lib/docs/docsIndex.test.mjs +377 -0
  144. package/lib/docs/evalOperator.js +5 -3
  145. package/lib/docs/evalOperatorHeadless.js +84 -12
  146. package/lib/docs/evalOperatorHeadless.test.mjs +7 -1
  147. package/lib/docs/evalOperatorInTab.js +2 -1
  148. package/lib/docs/findConfig.js +95 -49
  149. package/lib/docs/fitResponse.js +81 -0
  150. package/lib/docs/fitResponse.test.mjs +128 -0
  151. package/lib/docs/formatFeedback.js +26 -5
  152. package/lib/docs/formatFeedback.test.mjs +48 -0
  153. package/lib/docs/getAppMap.js +7 -1
  154. package/lib/docs/getBrowser.js +150 -80
  155. package/lib/docs/getBrowser.test.mjs +407 -16
  156. package/lib/docs/getBuildId.js +61 -0
  157. package/lib/docs/getBuildId.test.mjs +84 -0
  158. package/lib/docs/getBuildStatus.js +81 -7
  159. package/lib/docs/getBuildStatus.test.mjs +229 -0
  160. package/lib/docs/getBuildStatusAfterEdits.js +35 -0
  161. package/lib/docs/getBuildStatusAfterEdits.test.mjs +76 -0
  162. package/lib/docs/getCoreDoc.js +9 -1
  163. package/lib/docs/getDoc.js +100 -0
  164. package/lib/docs/getDocsIndex.js +152 -0
  165. package/lib/docs/getHazards.js +115 -0
  166. package/lib/docs/getHazards.test.mjs +175 -0
  167. package/lib/docs/getMailOutboxDirectory.js +26 -0
  168. package/lib/docs/getOverview.js +79 -13
  169. package/lib/docs/getOverviewBasePath.test.mjs +37 -0
  170. package/lib/docs/getPageBuildStatus.js +46 -0
  171. package/lib/docs/getPluginDoc.js +21 -23
  172. package/lib/docs/getSchema.js +33 -1
  173. package/lib/docs/getStaleStatus.js +36 -0
  174. package/lib/docs/getStaleStatus.test.mjs +74 -0
  175. package/lib/docs/guardJourneyOrigin.js +39 -0
  176. package/lib/docs/inspectState.js +5 -3
  177. package/lib/docs/inspectStateFromTab.js +2 -2
  178. package/lib/docs/inspectStateHeadless.js +89 -27
  179. package/lib/docs/inspectStateHeadless.test.mjs +20 -1
  180. package/lib/docs/installHeadlessShell.failure.test.mjs +45 -0
  181. package/lib/docs/installHeadlessShell.js +123 -0
  182. package/lib/docs/installHeadlessShell.test.mjs +70 -0
  183. package/lib/docs/installHeadlessShell.timeout.test.mjs +72 -0
  184. package/lib/docs/isHeadlessShellIncomplete.js +34 -0
  185. package/lib/docs/isHeadlessShellIncomplete.test.mjs +63 -0
  186. package/lib/docs/isPageReady.js +77 -0
  187. package/lib/docs/isPageReady.test.mjs +235 -0
  188. package/lib/docs/isRunErrorEntry.js +25 -0
  189. package/lib/docs/journeyMutantCookie.chromium.test.mjs +150 -0
  190. package/lib/docs/journeyRunRecording.js +35 -0
  191. package/lib/docs/journeyRunRecording.test.mjs +39 -0
  192. package/lib/docs/keepEventStreamAlive.js +68 -0
  193. package/lib/docs/keepEventStreamAlive.test.mjs +77 -0
  194. package/lib/docs/launchBrowser.js +83 -0
  195. package/lib/docs/launchBrowser.test.mjs +175 -0
  196. package/lib/docs/listModuleDocEntries.js +69 -0
  197. package/lib/docs/listPackageDocFiles.js +67 -0
  198. package/lib/docs/listPluginDocEntries.js +111 -0
  199. package/lib/docs/listPlugins.js +9 -58
  200. package/lib/docs/listTypes.js +14 -6
  201. package/lib/docs/loadFrameworkHazards.js +40 -0
  202. package/lib/docs/loadFrameworkHazards.test.mjs +43 -0
  203. package/lib/docs/loadState.js +103 -35
  204. package/lib/docs/makeModuleManifestDoc.js +174 -0
  205. package/lib/docs/matchExercisedPages.js +35 -0
  206. package/lib/docs/matchJourneyRoute.js +69 -0
  207. package/lib/docs/matchJourneyRoute.test.mjs +178 -0
  208. package/lib/docs/mergeNetworkSnapshots.js +49 -0
  209. package/lib/docs/nextJourneySequence.js +30 -0
  210. package/lib/docs/noBrowserError.js +22 -0
  211. package/lib/docs/observe/TRANSIENT_SELECTOR.js +21 -0
  212. package/lib/docs/observe/claimExpectedErrors.js +67 -0
  213. package/lib/docs/observe/createJourneyAppErrors.js +236 -0
  214. package/lib/docs/observe/createJourneyAppErrors.test.mjs +385 -0
  215. package/lib/docs/observe/evaluateInvariants.js +183 -0
  216. package/lib/docs/observe/evaluateInvariants.test.mjs +299 -0
  217. package/lib/docs/observe/findingKey.js +29 -0
  218. package/lib/docs/observe/hasNoEffect.js +31 -0
  219. package/lib/docs/observe/hasNoEffect.test.mjs +37 -0
  220. package/lib/docs/observe/installStepObserver.js +72 -0
  221. package/lib/docs/observe/isAppApiUrl.js +33 -0
  222. package/lib/docs/observe/parseAppApiUrl.js +37 -0
  223. package/lib/docs/observe/readStepObserver.js +42 -0
  224. package/lib/docs/observe/relativeSource.js +29 -0
  225. package/lib/docs/observe/resolveConfigKeySource.js +33 -0
  226. package/lib/docs/observe/takeJourneyEvents.js +36 -0
  227. package/lib/docs/observe/toAppErrorFailure.js +61 -0
  228. package/lib/docs/observe/usesSearchStage.js +45 -0
  229. package/lib/docs/observe/waitForClientErrorReports.js +31 -0
  230. package/lib/docs/observe/watchJourneyContext.js +75 -0
  231. package/lib/docs/observe/withAppErrors.js +31 -0
  232. package/lib/docs/observe/withAppErrors.test.mjs +101 -0
  233. package/lib/docs/openJourney.js +94 -0
  234. package/lib/docs/openJourneyEmail.js +41 -0
  235. package/lib/docs/openJourneyEmail.test.mjs +187 -0
  236. package/lib/docs/pageBlocks.js +47 -0
  237. package/lib/docs/pageBuildStatus.test.mjs +103 -0
  238. package/lib/{client/setPageId.js → docs/parseModuleSource.js} +11 -12
  239. package/lib/docs/rails/authEngineActions.js +53 -0
  240. package/lib/docs/rails/authEngineActions.test.mjs +69 -0
  241. package/lib/docs/rails/authEngineMethods.js +52 -0
  242. package/lib/docs/rails/authEngineMethods.test.mjs +36 -0
  243. package/lib/docs/rails/checkAuthActionsRan.js +66 -0
  244. package/lib/docs/rails/checkAuthActionsRan.test.mjs +149 -0
  245. package/lib/docs/rails/checkClickRails.js +56 -0
  246. package/lib/docs/rails/createDataSetRails.js +82 -0
  247. package/lib/docs/rails/createDataSetRails.test.mjs +172 -0
  248. package/lib/docs/rails/findExternalBlocks.js +147 -0
  249. package/lib/docs/rails/findExternalBlocks.test.mjs +254 -0
  250. package/lib/docs/rails/installAuthActionRecorder.js +56 -0
  251. package/lib/docs/rails/readAuthActionRecorder.js +32 -0
  252. package/lib/docs/rails/readBuiltArtifact.js +30 -0
  253. package/lib/docs/readConnectionPlugins.js +26 -0
  254. package/lib/docs/readDevAuthMode.js +32 -0
  255. package/lib/docs/readDocEntry.js +40 -0
  256. package/lib/docs/readDocTitle.js +26 -0
  257. package/lib/docs/readJourneyEmailMatch.js +57 -0
  258. package/lib/docs/readJourneyEmailMatch.test.mjs +110 -0
  259. package/lib/docs/readJourneySession.js +56 -0
  260. package/lib/docs/readMailOutbox.js +37 -0
  261. package/lib/docs/readPagePath.js +27 -0
  262. package/lib/docs/readPluginPackages.js +99 -0
  263. package/lib/docs/readProxyBuildWait.js +38 -0
  264. package/lib/docs/readRoutineSteps.js +45 -0
  265. package/lib/docs/registerShownTextEngine.js +118 -0
  266. package/lib/docs/requestRestart.js +30 -0
  267. package/lib/docs/requestRestart.test.mjs +49 -0
  268. package/lib/docs/resolvePageInstance.js +46 -0
  269. package/lib/docs/resolvePageInstance.test.mjs +81 -0
  270. package/lib/docs/resolveSource.js +11 -7
  271. package/lib/docs/resolveSource.test.mjs +1 -1
  272. package/lib/docs/resolveToolCaller.js +62 -0
  273. package/lib/docs/reviewPage.js +51 -0
  274. package/lib/docs/reviewPageBuilds.js +56 -0
  275. package/lib/docs/reviewPageBuilds.test.mjs +207 -0
  276. package/lib/docs/runCheck.js +75 -0
  277. package/lib/docs/runCheck.test.mjs +91 -0
  278. package/lib/docs/runCheckChild.js +49 -0
  279. package/lib/docs/runEndpoint.js +185 -0
  280. package/lib/docs/runEndpoint.test.mjs +509 -0
  281. package/lib/docs/runErrorBuffers.js +99 -0
  282. package/lib/docs/runErrorBuffers.test.mjs +95 -0
  283. package/lib/docs/runJourney.js +347 -0
  284. package/lib/docs/runJourney.test.mjs +2383 -0
  285. package/lib/docs/runJourneyAsUsers.js +44 -0
  286. package/lib/docs/runJourneyDataSet.test.mjs +274 -0
  287. package/lib/docs/runJourneySteps.js +1320 -0
  288. package/lib/docs/runRequest.js +100 -59
  289. package/lib/docs/runRequest.test.mjs +190 -0
  290. package/lib/docs/saveAnnotatedScreenshot.js +1 -1
  291. package/lib/docs/scaffoldPage.js +3 -1
  292. package/lib/docs/screenshotPage.js +163 -11
  293. package/lib/docs/screenshotPage.test.mjs +263 -1
  294. package/lib/docs/screenshotPageClip.test.mjs +4 -0
  295. package/lib/docs/searchDocs.js +82 -30
  296. package/lib/docs/searchDocs.test.mjs +100 -0
  297. package/lib/docs/searchIcons.js +90 -0
  298. package/lib/docs/searchIcons.test.mjs +127 -0
  299. package/lib/docs/selectExercised.js +39 -0
  300. package/lib/docs/selectFinalState.js +66 -0
  301. package/lib/docs/selectFinalState.test.mjs +51 -0
  302. package/lib/docs/serverErrorStore.js +54 -0
  303. package/lib/docs/serverErrorStore.test.mjs +50 -0
  304. package/lib/docs/setupTestFixtures.mjs +133 -39
  305. package/lib/docs/shownTextSelector.js +28 -0
  306. package/lib/docs/snapshotState.js +13 -5
  307. package/lib/docs/splitSearchTerms.js +62 -0
  308. package/lib/docs/splitSearchTerms.test.mjs +50 -0
  309. package/lib/docs/stateCheckpoints.test.mjs +77 -0
  310. package/lib/docs/tabAvailable.js +6 -5
  311. package/lib/docs/tabChannel.js +127 -21
  312. package/lib/docs/tabChannel.test.mjs +187 -29
  313. package/lib/docs/unsettledPageNote.js +24 -0
  314. package/lib/docs/validateJourneyMail.js +36 -0
  315. package/lib/docs/validateJourneyTimeout.js +38 -0
  316. package/lib/docs/validateJourneyTimeout.test.mjs +29 -0
  317. package/lib/docs/validateRecordingParam.js +42 -0
  318. package/lib/docs/validateStateSelection.js +33 -0
  319. package/lib/docs/validateStateSelection.test.mjs +33 -0
  320. package/lib/docs/validateViewport.js +51 -0
  321. package/lib/docs/validateViewport.test.mjs +54 -0
  322. package/lib/docs/waitForBuild.js +50 -0
  323. package/lib/docs/waitForBuild.test.mjs +75 -0
  324. package/lib/docs/waitForJourneyEmail.js +77 -0
  325. package/lib/docs/withBrowserSlot.js +45 -0
  326. package/lib/docs/withBrowserSlot.test.mjs +75 -0
  327. package/lib/docs/withDataSession.js +53 -0
  328. package/lib/server/applyDataSetRedirect.js +92 -0
  329. package/lib/server/applyDataSetRedirect.test.mjs +446 -0
  330. package/lib/server/applyDataSetRedirectTenantReach.test.mjs +207 -0
  331. package/lib/server/applyDataSetRedirectWalled.test.mjs +134 -0
  332. package/lib/server/auth/createSystemContext.js +73 -0
  333. package/lib/server/auth/{getAuthConfig.js → getAuth.js} +15 -9
  334. package/lib/server/auth/getHeadlessUser.js +9 -8
  335. package/lib/server/auth/getHeadlessUser.test.mjs +41 -0
  336. package/lib/server/auth/getMockUser.js +18 -14
  337. package/lib/server/auth/{session.js → getStrategies.js} +17 -16
  338. package/lib/server/auth/headlessUser.js +3 -2
  339. package/lib/server/auth/journeyActor.js +35 -0
  340. package/lib/server/auth/resolveHeadlessUser.test.mjs +0 -4
  341. package/lib/server/checkPageRecord.js +44 -0
  342. package/lib/server/contextMapBudget.js +24 -0
  343. package/lib/server/contextMapBudget.test.mjs +98 -0
  344. package/lib/server/continueJitKeys.js +28 -0
  345. package/lib/server/continueJitKeys.test.mjs +43 -0
  346. package/lib/server/createDataSessionEndedError.js +33 -0
  347. package/lib/server/createDataSetRefusal.js +26 -0
  348. package/lib/server/createLowdefyContext.js +168 -41
  349. package/lib/server/createLowdefyContext.test.mjs +331 -8
  350. package/lib/server/getClientAddress.js +51 -0
  351. package/lib/server/getClientAddress.test.mjs +56 -0
  352. package/lib/server/getPageJitEnrichment.test.mjs +29 -11
  353. package/lib/server/hashConfigContent.js +26 -0
  354. package/lib/server/importAppCode.test.mjs +57 -0
  355. package/lib/server/isRoutineStep.js +27 -0
  356. package/lib/server/jitPageBuilder.js +309 -126
  357. package/lib/server/jitPageBuilder.skipPluginChecks.test.mjs +78 -0
  358. package/lib/server/jitPageBuilder.test.mjs +218 -0
  359. package/lib/server/journeyCookies/JOURNEY_COOKIES.js +37 -0
  360. package/lib/server/journeyCookies/forwardJourneyCookies.js +46 -0
  361. package/lib/server/journeyCookies/readJourneyCookie.js +52 -0
  362. package/lib/server/journeyCookies/writeJourneyCookie.js +46 -0
  363. package/lib/server/journeyCookies.js +22 -0
  364. package/lib/server/journeyCookies.test.mjs +145 -0
  365. package/lib/server/loadDynamicJsMap.js +47 -0
  366. package/lib/server/log/createHandleError.js +38 -2
  367. package/lib/server/log/createHandleError.test.mjs +125 -2
  368. package/lib/server/log/createLogger.js +3 -0
  369. package/lib/server/log/createLogger.test.mjs +15 -0
  370. package/lib/server/log/logRequest.js +2 -3
  371. package/lib/server/mutantDataSetWrappers.test.mjs +115 -0
  372. package/lib/server/mutants/applyMutant.js +39 -0
  373. package/lib/server/mutants/artifactPatterns.js +25 -0
  374. package/lib/server/mutants/buildAnchor.js +45 -0
  375. package/lib/server/mutants/collectSharedIds.js +41 -0
  376. package/lib/server/mutants/configPathSegments.js +38 -0
  377. package/lib/server/mutants/createMutantReadConfigFile.js +42 -0
  378. package/lib/server/mutants/describeEvent.js +22 -0
  379. package/lib/server/mutants/enumerateArtifact.js +56 -0
  380. package/lib/server/mutants/findNodeByKey.js +45 -0
  381. package/lib/server/mutants/findNodeByPointer.js +49 -0
  382. package/lib/server/mutants/getArtifactKind.js +32 -0
  383. package/lib/server/mutants/groupMutantCopies.js +56 -0
  384. package/lib/server/mutants/hashArtifact.js +25 -0
  385. package/lib/server/mutants/hashMutantTarget.js +34 -0
  386. package/lib/server/mutants/listMutants.js +122 -0
  387. package/lib/server/mutants/listMutants.test.mjs +502 -0
  388. package/lib/server/mutants/mutantId.js +30 -0
  389. package/lib/server/mutants/mutantRuns.js +57 -0
  390. package/lib/server/mutants/mutantRuns.test.mjs +136 -0
  391. package/lib/server/mutants/operators/dropAction.js +47 -0
  392. package/lib/server/mutants/operators/dropBlock.js +44 -0
  393. package/lib/server/mutants/operators/dropPayload.js +52 -0
  394. package/lib/server/mutants/operators/dropStep.js +44 -0
  395. package/lib/server/mutants/operators/flipVisible.js +53 -0
  396. package/lib/server/mutants/operators/index.js +37 -0
  397. package/lib/server/mutants/operators/retargetLink.js +72 -0
  398. package/lib/server/mutants/operators/skipValidate.js +77 -0
  399. package/lib/server/mutants/operators/swapIf.js +112 -0
  400. package/lib/server/mutants/operators.test.mjs +396 -0
  401. package/lib/server/mutants/positionFreePath.js +38 -0
  402. package/lib/server/mutants/removeFromParent.js +28 -0
  403. package/lib/server/mutants/test/fixtures.mjs +173 -0
  404. package/lib/server/mutants/test/keyArtifact.mjs +63 -0
  405. package/lib/server/mutants/validateMutantParam.js +69 -0
  406. package/lib/server/mutants/validateMutantsBody.js +88 -0
  407. package/lib/server/mutants/walkArtifact.js +179 -0
  408. package/lib/server/pageBuildRecords.js +131 -0
  409. package/lib/server/pageBuildRecords.test.mjs +258 -0
  410. package/lib/server/pageCache.mjs +22 -7
  411. package/lib/server/pageCache.test.mjs +22 -7
  412. package/lib/server/pageRebuilds.test.mjs +543 -0
  413. package/lib/server/pruneJitMaps.js +36 -0
  414. package/lib/server/pruneJitMaps.test.mjs +55 -0
  415. package/lib/server/readInstanceRecord.js +33 -0
  416. package/lib/server/readInstanceRecord.test.mjs +99 -0
  417. package/lib/server/readMergedMaps.js +86 -0
  418. package/lib/server/readMergedMaps.test.mjs +79 -0
  419. package/lib/server/recording/appendRecords.js +33 -0
  420. package/lib/server/recording/errorRecordingStamp.js +34 -0
  421. package/lib/server/recording/errorRecordingStamp.test.mjs +31 -0
  422. package/lib/server/recording/isRecordingEnabled.js +31 -0
  423. package/lib/server/recording/readRecordingCookie.js +71 -0
  424. package/lib/server/recording/readRecordingCookie.test.mjs +73 -0
  425. package/lib/server/recording/recordingCookiePayload.js +33 -0
  426. package/lib/server/recording/servedBuilds.js +46 -0
  427. package/lib/server/recording/servedBuilds.test.mjs +42 -0
  428. package/lib/server/sinkExternalConnections.js +95 -0
  429. package/lib/server/sinkExternalConnections.test.mjs +135 -0
  430. package/lib/server/skipStaleMapWrites.js +62 -0
  431. package/lib/server/skipStaleMapWrites.test.mjs +113 -0
  432. package/lib/server/startIdleGc.js +67 -0
  433. package/lib/server/startIdleGc.test.mjs +138 -0
  434. package/lib/server/testFixtures/importAppCodeTwice.mjs +70 -0
  435. package/lib/server/trackSessionWork.js +30 -0
  436. package/lib/vite/createDevServerExclude.js +40 -0
  437. package/lib/vite/createDevServerExclude.test.mjs +79 -0
  438. package/manager/getContext.mjs +25 -3
  439. package/manager/processes/initialBuild.mjs +17 -1
  440. package/manager/processes/initialBuild.test.mjs +104 -0
  441. package/manager/processes/lowdefyBuild.mjs +31 -7
  442. package/manager/processes/lowdefyBuild.skipPluginChecks.test.mjs +72 -0
  443. package/manager/processes/lowdefyBuild.test.mjs +136 -1
  444. package/manager/processes/optimizeDependencies.mjs +59 -0
  445. package/manager/processes/restartServer.mjs +34 -6
  446. package/manager/processes/restartServer.test.mjs +162 -0
  447. package/manager/processes/shutdownServer.mjs +11 -0
  448. package/manager/processes/shutdownServer.test.mjs +45 -0
  449. package/manager/processes/startFirstServer.mjs +46 -0
  450. package/manager/processes/startFirstServer.test.mjs +326 -0
  451. package/manager/processes/startMailSink.mjs +135 -0
  452. package/manager/processes/startMailSink.test.mjs +146 -0
  453. package/manager/processes/startProxy.mjs +129 -12
  454. package/manager/processes/startProxy.test.mjs +366 -0
  455. package/manager/processes/startServer.mjs +47 -6
  456. package/manager/processes/startServer.test.mjs +155 -0
  457. package/manager/processes/startWatchers.mjs +6 -4
  458. package/manager/processes/syncServer.mjs +69 -0
  459. package/manager/processes/syncServer.test.mjs +141 -0
  460. package/manager/processes/waitForDevServer.mjs +48 -0
  461. package/manager/processes/waitForDevServer.test.mjs +112 -0
  462. package/manager/run.mjs +128 -43
  463. package/manager/utils/BatchChanges.mjs +21 -5
  464. package/manager/utils/BatchChanges.test.mjs +77 -0
  465. package/manager/utils/acquireDevInstance.mjs +99 -0
  466. package/manager/utils/acquireDevInstance.test.mjs +160 -0
  467. package/manager/utils/createBuildActivity.mjs +67 -0
  468. package/manager/utils/createBuildActivity.test.mjs +58 -0
  469. package/manager/utils/createChangeSignal.mjs +39 -0
  470. package/manager/utils/createChangeSignal.test.mjs +62 -0
  471. package/manager/utils/createCustomPluginMessagesMap.mjs +23 -33
  472. package/manager/utils/createCustomPluginTypesMap.mjs +24 -30
  473. package/manager/utils/createCustomPluginTypesMap.test.mjs +94 -0
  474. package/manager/utils/createDotPathIgnore.mjs +44 -0
  475. package/manager/utils/createDotPathIgnore.test.mjs +65 -0
  476. package/manager/utils/createIdleGc.mjs +51 -0
  477. package/manager/utils/createIdleGc.test.mjs +77 -0
  478. package/manager/utils/createRequestActivity.mjs +100 -0
  479. package/manager/utils/createRequestActivity.test.mjs +121 -0
  480. package/manager/utils/createServerArtifactTracker.mjs +83 -0
  481. package/manager/utils/createServerArtifactTracker.test.mjs +78 -0
  482. package/manager/utils/createServerEnv.mjs +39 -0
  483. package/manager/utils/filesHaveSameBytes.mjs +41 -0
  484. package/manager/utils/findBuildFilesOutsideWatch.mjs +59 -0
  485. package/manager/utils/findBuildFilesOutsideWatch.test.mjs +111 -0
  486. package/manager/utils/importFresh.mjs +46 -0
  487. package/manager/utils/importFresh.test.mjs +72 -0
  488. package/manager/utils/importFreshWorker.mjs +20 -0
  489. package/manager/utils/killTaggedBrowser.mjs +35 -0
  490. package/manager/utils/pagePathChanged.mjs +56 -0
  491. package/manager/utils/pruneRecordings.mjs +85 -0
  492. package/manager/utils/pruneRecordings.test.mjs +117 -0
  493. package/manager/utils/publishBuildDirectory.mjs +121 -0
  494. package/manager/utils/publishBuildDirectory.order.test.mjs +74 -0
  495. package/manager/utils/publishBuildDirectory.test.mjs +262 -0
  496. package/manager/utils/readBasePath.mjs +27 -0
  497. package/manager/utils/readBasePath.test.mjs +43 -0
  498. package/manager/utils/readBuildStatusWait.mjs +67 -0
  499. package/manager/utils/readManagerVersion.mjs +30 -0
  500. package/manager/utils/readManagerVersion.test.mjs +35 -0
  501. package/manager/utils/readPluginDefinitions.mjs +36 -0
  502. package/manager/utils/resolveDevAuthUrl.mjs +58 -0
  503. package/manager/utils/resolveDevAuthUrl.test.mjs +107 -0
  504. package/manager/utils/resolvePorts.mjs +49 -0
  505. package/manager/utils/setupWatcher.mjs +19 -11
  506. package/manager/utils/startRecordingPruner.mjs +45 -0
  507. package/manager/utils/startRecordingPruner.test.mjs +66 -0
  508. package/manager/utils/waitForChildExit.mjs +53 -0
  509. package/manager/utils/waitForChildExit.test.mjs +70 -0
  510. package/manager/utils/waitForServer.mjs +44 -0
  511. package/manager/utils/writeBuildStatus.mjs +1 -1
  512. package/manager/watchers/createPluginSourceIgnore.mjs +77 -0
  513. package/manager/watchers/envWatcher.mjs +9 -2
  514. package/manager/watchers/envWatcher.test.mjs +87 -0
  515. package/manager/watchers/listServerSidePackages.mjs +47 -0
  516. package/manager/watchers/listServerSidePackages.test.mjs +38 -0
  517. package/manager/watchers/lowdefyBuildWatcher.mjs +106 -22
  518. package/manager/watchers/lowdefyBuildWatcher.test.mjs +309 -0
  519. package/manager/watchers/pluginSourceWatcher.mjs +79 -0
  520. package/manager/watchers/pluginSourceWatcher.test.mjs +222 -0
  521. package/manager/watchers/restartRequestWatcher.mjs +71 -0
  522. package/manager/watchers/restartRequestWatcher.test.mjs +111 -0
  523. package/manager/watchers/selectLocalPluginPackages.mjs +48 -0
  524. package/manager/watchers/selectLocalPluginPackages.test.mjs +73 -0
  525. package/manager/watchers/serverPackageWatcher.mjs +38 -0
  526. package/package.json +60 -47
  527. package/package.original.json +60 -47
  528. package/src/app.js +131 -20
  529. package/src/html/renderDevPage.js +63 -11
  530. package/src/html/renderDevPage.test.mjs +138 -0
  531. package/src/lib/getRequestPath.js +29 -0
  532. package/src/middleware/createSameOriginGuard.js +37 -0
  533. package/src/middleware/createSameOriginGuard.test.mjs +102 -0
  534. package/src/middleware/errorHandler.js +51 -1
  535. package/src/middleware/errorHandler.test.mjs +173 -3
  536. package/src/middleware/isRebindingSafeHost.js +44 -0
  537. package/src/middleware/isRebindingSafeHost.test.mjs +32 -0
  538. package/src/middleware/localDevToolsOnly.js +38 -0
  539. package/src/middleware/localDevToolsOnly.test.mjs +61 -0
  540. package/src/middleware/noStore.js +27 -0
  541. package/src/middleware/noStore.test.mjs +48 -0
  542. package/src/middleware/requestTimeout.js +38 -0
  543. package/src/middleware/staleFlag.js +76 -0
  544. package/src/middleware/staleFlag.test.mjs +127 -0
  545. package/src/middleware/stampCaller.js +38 -0
  546. package/src/middleware/stampCaller.test.mjs +64 -0
  547. package/src/routes/agent.js +15 -3
  548. package/src/routes/auth.js +29 -20
  549. package/src/routes/auth.test.mjs +30 -56
  550. package/src/routes/clientError.js +13 -13
  551. package/src/routes/cron.js +3 -1
  552. package/src/routes/cronForward.js +3 -1
  553. package/src/routes/detached.js +22 -9
  554. package/src/routes/devInspect.js +17 -8
  555. package/src/routes/devInspect.test.mjs +44 -7
  556. package/src/routes/devRecording.js +113 -0
  557. package/src/routes/devRecording.test.mjs +253 -0
  558. package/src/routes/docs/buildStatus.js +10 -2
  559. package/src/routes/docs/buildStatus.test.mjs +46 -0
  560. package/src/routes/docs/content.js +5 -3
  561. package/src/routes/docs/evalOperator.js +15 -1
  562. package/src/routes/docs/events.js +50 -0
  563. package/src/routes/docs/events.test.mjs +117 -0
  564. package/src/routes/docs/find.js +9 -1
  565. package/src/routes/docs/icons.js +30 -0
  566. package/src/routes/docs/inspectState.js +16 -1
  567. package/src/routes/docs/journey.js +194 -0
  568. package/src/routes/docs/journey.test.mjs +401 -0
  569. package/src/routes/docs/journeyRefusals.test.mjs +71 -0
  570. package/src/routes/docs/loadState.js +4 -2
  571. package/src/routes/docs/mcp.js +34 -3
  572. package/src/routes/docs/mcp.test.mjs +130 -0
  573. package/src/routes/docs/mutants.js +78 -0
  574. package/src/routes/docs/mutants.test.mjs +180 -0
  575. package/src/routes/docs/parsePathParamsParam.js +41 -0
  576. package/src/routes/docs/parsePathParamsParam.test.mjs +42 -0
  577. package/src/routes/docs/parseUserParam.js +27 -21
  578. package/src/routes/docs/parseUserParam.test.mjs +17 -5
  579. package/src/routes/docs/restart.js +35 -0
  580. package/src/routes/docs/restart.test.mjs +64 -0
  581. package/src/routes/docs/runEndpoint.js +54 -0
  582. package/src/routes/docs/runEndpoint.test.mjs +165 -0
  583. package/src/routes/docs/runRequest.js +25 -8
  584. package/src/routes/docs/runRequest.test.mjs +92 -0
  585. package/src/routes/docs/screenshot.js +50 -2
  586. package/src/routes/docs/screenshot.test.mjs +117 -0
  587. package/src/routes/docs/search.js +15 -2
  588. package/src/routes/docs/snapshotState.js +17 -3
  589. package/src/routes/endpoints.js +27 -26
  590. package/src/routes/endpointsTextAnswer.test.mjs +77 -0
  591. package/src/routes/feedback.js +10 -13
  592. package/src/routes/jitPage.js +99 -35
  593. package/src/routes/jitPage.test.mjs +248 -0
  594. package/src/routes/mcp.js +82 -5
  595. package/src/routes/mcp.test.mjs +215 -0
  596. package/src/routes/mcpMemberToken.test.mjs +125 -0
  597. package/src/routes/mcpProtectedResourceMetadata.js +34 -0
  598. package/src/routes/mcpProtectedResourceMetadata.test.mjs +65 -0
  599. package/src/routes/mountDevPageRoutes.js +25 -0
  600. package/src/routes/mountDevPageRoutes.test.mjs +30 -0
  601. package/src/routes/mountOauthDiscovery.js +44 -0
  602. package/src/routes/mountOauthDiscovery.test.mjs +79 -0
  603. package/src/routes/normalizeMcpProtocolVersionHeader.js +46 -0
  604. package/src/routes/normalizeMcpProtocolVersionHeader.test.mjs +62 -0
  605. package/src/routes/reload.js +16 -0
  606. package/src/routes/request.js +6 -4
  607. package/src/routes/request.test.mjs +9 -5
  608. package/src/routes/requestBody.test.mjs +159 -0
  609. package/src/routes/usage.js +6 -2
  610. package/src/routes/user.js +27 -0
  611. package/src/routes/websocket.js +29 -8
  612. package/src/routes/websocket.test.mjs +65 -0
  613. package/src/routes/wellKnownFallback.js +30 -0
  614. package/src/routes/wellKnownFallback.test.mjs +32 -0
  615. package/src/websocket/devWebSocket.js +41 -26
  616. package/src/websocket/devWebSocket.test.mjs +157 -0
  617. package/vite.config.js +53 -23
  618. package/lib/server/auth/getDevSession.js +0 -70
  619. package/lib/server/auth/getDevSession.test.mjs +0 -138
  620. package/manager/processes/warnAuthUrlPortMismatch.mjs +0 -47
  621. package/manager/processes/warnAuthUrlPortMismatch.test.mjs +0 -93
  622. package/manager/watchers/moduleBuildWatcher.mjs +0 -79
  623. package/manager/watchers/serverArtifactWatcher.mjs +0 -120
  624. package/src/routes/apiPage.js +0 -37
@@ -0,0 +1,47 @@
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 fs from 'node:fs';
18
+ import path from 'node:path';
19
+
20
+ import staticJsMap from '../../build/plugins/operators/serverJsMap.js';
21
+
22
+ // Dynamic JS map loading for JIT-built pages — the build rewrites
23
+ // serverJsMap.js when a JIT page discovers new _js operators.
24
+ let cachedJsMapMtime = null;
25
+ let cachedJsMap = staticJsMap;
26
+
27
+ function loadDynamicJsMap(buildDirectory) {
28
+ const jsMapPath = path.join(buildDirectory, 'plugins', 'operators', 'serverJsMap.js');
29
+ try {
30
+ const stat = fs.statSync(jsMapPath);
31
+ if (cachedJsMapMtime && stat.mtimeMs === cachedJsMapMtime) {
32
+ return cachedJsMap;
33
+ }
34
+ cachedJsMapMtime = stat.mtimeMs;
35
+ // For server-side, we can read and eval the JS file
36
+ const content = fs.readFileSync(jsMapPath, 'utf8');
37
+ const fn = new Function('exports', content.replace('export default', 'exports.default ='));
38
+ const exports = {};
39
+ fn(exports);
40
+ cachedJsMap = { ...staticJsMap, ...(exports.default ?? {}) };
41
+ return cachedJsMap;
42
+ } catch {
43
+ return cachedJsMap;
44
+ }
45
+ }
46
+
47
+ export default loadDynamicJsMap;
@@ -14,7 +14,23 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
+ import { inspect } from 'node:util';
18
+
17
19
  import { LowdefyInternalError, loadAndResolveErrorLocation } from '@lowdefy/errors';
20
+ import { scrubCredentials } from '@lowdefy/node-utils';
21
+
22
+ import errorRecordingStamp from '../recording/errorRecordingStamp.js';
23
+ import serverErrorStore from '../../docs/serverErrorStore.js';
24
+
25
+ // A UserError is an expected outcome of user interaction (a rejected payload,
26
+ // a :reject) and a LowdefyInternalError never carries a config location, so
27
+ // neither belongs in the agent's feedback channel.
28
+ function shouldStore(error) {
29
+ if (error instanceof LowdefyInternalError) {
30
+ return false;
31
+ }
32
+ return error.name !== 'UserError';
33
+ }
18
34
 
19
35
  function createHandleError({ context }) {
20
36
  return async function handleError(error) {
@@ -36,6 +52,9 @@ function createHandleError({ context }) {
36
52
  error,
37
53
  readConfigFile: context.readConfigFile,
38
54
  configDirectory: context.configDirectory,
55
+ // The request context's reader adds the JIT page builds' maps
56
+ // (readMergedMaps); without one, keyMap.json and refMap.json.
57
+ readMaps: context.readMaps,
39
58
  });
40
59
 
41
60
  // Attach resolved location to error for display layer
@@ -44,11 +63,28 @@ function createHandleError({ context }) {
44
63
  error.config = location.config;
45
64
  }
46
65
 
66
+ if (shouldStore(error)) {
67
+ serverErrorStore.push({
68
+ timestamp: new Date().toISOString(),
69
+ name: error.name,
70
+ // Agents read the store through build-status, after this request's scope has ended.
71
+ message: scrubCredentials(error.message),
72
+ source: error.source ?? null,
73
+ config: error.config ?? null,
74
+ hint: error.hint ?? null,
75
+ endpointId: context.endpointId ?? null,
76
+ requestId: context.requestId ?? null,
77
+ pageId: context.pageId ?? null,
78
+ recording: errorRecordingStamp(context.recording),
79
+ });
80
+ }
81
+
47
82
  context.logger.error(error);
48
83
  } catch (e) {
49
- console.error(error);
84
+ // Written past the logger, so past its streamWrite scrub of marked credentials.
85
+ console.error(scrubCredentials(inspect(error)));
50
86
  console.error('An error occurred while logging the error.');
51
- console.error(e);
87
+ console.error(scrubCredentials(inspect(e)));
52
88
  }
53
89
  };
54
90
  }
@@ -15,9 +15,15 @@
15
15
  */
16
16
 
17
17
  import { jest } from '@jest/globals';
18
- import { ConfigError, LowdefyInternalError } from '@lowdefy/errors';
18
+ import { ConfigError, LowdefyInternalError, UserError } from '@lowdefy/errors';
19
+ import { markCredential, runInCredentialScope } from '@lowdefy/node-utils';
19
20
 
20
- import createHandleError from './createHandleError.js';
21
+ jest.unstable_mockModule('../../docs/serverErrorStore.js', () => ({
22
+ default: { push: jest.fn(), list: jest.fn(() => []) },
23
+ }));
24
+
25
+ const { default: serverErrorStore } = await import('../../docs/serverErrorStore.js');
26
+ const { default: createHandleError } = await import('./createHandleError.js');
21
27
 
22
28
  // This sink is the sole writer of `error.handled`. Two callers read the flag and
23
29
  // neither can set it for itself: runRoutine's catch guards on `!error.handled` to
@@ -46,6 +52,7 @@ function testContext({ logger, readConfigFile, configDirectory } = {}) {
46
52
 
47
53
  beforeEach(() => {
48
54
  console.error = jest.fn();
55
+ serverErrorStore.push.mockReset();
49
56
  });
50
57
 
51
58
  test('handleError marks the error handled and resolves its location', async () => {
@@ -124,3 +131,119 @@ test('handleError marks a plain Error handled', async () => {
124
131
 
125
132
  expect(error.handled).toBe(true);
126
133
  });
134
+
135
+ test('handleError pushes an error with a configKey into the server error store with its source', async () => {
136
+ const context = testContext();
137
+ context.endpointId = 'get-customer';
138
+ context.pageId = '_mcp';
139
+ const handleError = createHandleError({ context });
140
+ const error = new ConfigError('Bad config.', { configKey: 'key_1' });
141
+
142
+ await handleError(error);
143
+
144
+ expect(serverErrorStore.push).toHaveBeenCalledTimes(1);
145
+ const entry = serverErrorStore.push.mock.calls[0][0];
146
+ expect(entry).toEqual({
147
+ timestamp: expect.any(String),
148
+ name: 'ConfigError',
149
+ message: 'Bad config.',
150
+ source: 'pages/home.yaml:5',
151
+ config: 'root.pages[0:home]',
152
+ hint: null,
153
+ endpointId: 'get-customer',
154
+ requestId: null,
155
+ pageId: '_mcp',
156
+ recording: null,
157
+ });
158
+ // Pushed before the log so a throwing logger cannot lose the entry.
159
+ expect(serverErrorStore.push.mock.invocationCallOrder[0]).toBeLessThan(
160
+ context.logger.error.mock.invocationCallOrder[0]
161
+ );
162
+ });
163
+
164
+ test('handleError does not push a UserError into the server error store', async () => {
165
+ const context = testContext();
166
+ const handleError = createHandleError({ context });
167
+ const error = new UserError('Rejected.');
168
+
169
+ await handleError(error);
170
+
171
+ expect(error.handled).toBe(true);
172
+ expect(serverErrorStore.push).not.toHaveBeenCalled();
173
+ expect(context.logger.error).toHaveBeenCalledWith(error);
174
+ });
175
+
176
+ test('handleError does not push a LowdefyInternalError into the server error store', async () => {
177
+ const context = testContext();
178
+ const handleError = createHandleError({ context });
179
+ const error = new LowdefyInternalError('Unexpected condition.');
180
+
181
+ await handleError(error);
182
+
183
+ expect(serverErrorStore.push).not.toHaveBeenCalled();
184
+ expect(context.logger.error).toHaveBeenCalledWith(error);
185
+ });
186
+
187
+ test('handleError still logs when the server error store throws', async () => {
188
+ serverErrorStore.push.mockImplementation(() => {
189
+ throw new Error('Store full.');
190
+ });
191
+ const context = testContext();
192
+ const handleError = createHandleError({ context });
193
+ const error = new ConfigError('Bad config.', { configKey: 'key_1' });
194
+
195
+ await handleError(error);
196
+
197
+ expect(error.handled).toBe(true);
198
+ expect(console.error).toHaveBeenCalledWith(expect.stringContaining('Bad config.'));
199
+ expect(console.error).toHaveBeenCalledWith('An error occurred while logging the error.');
200
+ });
201
+
202
+ test('handleError writes its fallback lines with the credentials the request marked redacted', async () => {
203
+ serverErrorStore.push.mockImplementation(() => {
204
+ throw new Error('Store refused runtime-made-app-key-0001.');
205
+ });
206
+ const handleError = createHandleError({ context: testContext() });
207
+ await runInCredentialScope(async () => {
208
+ markCredential('runtime-made-app-key-0001');
209
+ await handleError(new ConfigError('Upstream refused runtime-made-app-key-0001.'));
210
+ });
211
+ const written = console.error.mock.calls.map(([line]) => line);
212
+ expect(written).toEqual(
213
+ expect.arrayContaining([
214
+ expect.stringContaining('Upstream refused [REDACTED].'),
215
+ expect.stringContaining('Store refused [REDACTED].'),
216
+ ])
217
+ );
218
+ written.forEach((line) => expect(line).not.toContain('runtime-made-app-key-0001'));
219
+ });
220
+
221
+ test('handleError stamps a stored error with the recording of the context that caused it', async () => {
222
+ const context = testContext();
223
+ context.recording = {
224
+ source: 'journey',
225
+ run: { id: '20261003T151200Z-p0d4rm', by: 'agent', journey: 'save-ticket', actor: 'main' },
226
+ };
227
+ await createHandleError({ context })(new ConfigError('Bad config.', { configKey: 'key_1' }));
228
+ expect(serverErrorStore.push.mock.calls[0][0].recording).toEqual({
229
+ source: 'journey',
230
+ run: '20261003T151200Z-p0d4rm',
231
+ journey: 'save-ticket',
232
+ });
233
+ });
234
+
235
+ test('handleError stamps a developer request error with a null recording', async () => {
236
+ const context = testContext();
237
+ context.recording = null;
238
+ await createHandleError({ context })(new ConfigError('Bad config.', { configKey: 'key_1' }));
239
+ expect(serverErrorStore.push.mock.calls[0][0].recording).toBeNull();
240
+ });
241
+
242
+ test('handleError stores a message with the credentials the request marked redacted', async () => {
243
+ const handleError = createHandleError({ context: testContext() });
244
+ await runInCredentialScope(async () => {
245
+ markCredential('runtime-made-app-key-0001');
246
+ await handleError(new ConfigError('Upstream refused runtime-made-app-key-0001.'));
247
+ });
248
+ expect(serverErrorStore.push.mock.calls[0][0].message).toBe('Upstream refused [REDACTED].');
249
+ });
@@ -15,11 +15,14 @@
15
15
  */
16
16
 
17
17
  import { createNodeLogger } from '@lowdefy/logger/node';
18
+ import { scrubCredentials } from '@lowdefy/node-utils';
18
19
 
19
20
  const logger = createNodeLogger({
20
21
  name: 'lowdefy_server_dev',
21
22
  level: process.env.LOWDEFY_LOG_LEVEL ?? 'info',
22
23
  base: { pid: undefined, hostname: undefined },
24
+ // Secrets stay in these logs, but a value a request marked with _credential does not.
25
+ hooks: { streamWrite: scrubCredentials },
23
26
  });
24
27
 
25
28
  function createLogger(metadata = {}) {
@@ -16,6 +16,7 @@
16
16
 
17
17
  import { jest } from '@jest/globals';
18
18
  import { createNodeLogger } from '@lowdefy/logger/node';
19
+ import { markCredential, runInCredentialScope } from '@lowdefy/node-utils';
19
20
  import { serializer } from '@lowdefy/helpers';
20
21
 
21
22
  const secret = 'planted-dev-secret-value';
@@ -60,3 +61,17 @@ test('dev logger writes an axios error with full default serialization and no sc
60
61
  expect(err.config.headers.Authorization).toBe('Bearer planted-config-header');
61
62
  expect(msg).toBe(`Request failed for key ${secret}`);
62
63
  });
64
+
65
+ test('dev logger redacts a value the request marked as a credential and keeps secrets', () => {
66
+ lines.length = 0;
67
+ runInCredentialScope(() => {
68
+ markCredential('runtime-made-app-key-0001');
69
+ createLogger({ rid: 'req-1' }).info({
70
+ event: 'debug_control_set_state',
71
+ evaluated: { key: 'runtime-made-app-key-0001', secret },
72
+ });
73
+ });
74
+ expect(lines).toHaveLength(1);
75
+ const { evaluated } = JSON.parse(lines[0]);
76
+ expect(evaluated).toEqual({ key: '[REDACTED]', secret });
77
+ });
@@ -16,17 +16,16 @@
16
16
 
17
17
  // TODO: Better name needed here maybe?
18
18
  function logRequest({ context }) {
19
- const { user = {} } = context;
19
+ const user = context.user ?? {};
20
20
  context.logger.debug({
21
21
  user: {
22
22
  id: user.id,
23
23
  roles: user.roles,
24
- sub: user.sub,
25
- session_id: user.session_id,
26
24
  },
27
25
  url: context.req.url,
28
26
  method: context.req.method,
29
27
  resolvedUrl: context.nextContext?.resolvedUrl,
28
+ dataSet: context.dataSet,
30
29
  });
31
30
  }
32
31
 
@@ -0,0 +1,115 @@
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 { serializer } from '@lowdefy/helpers';
18
+
19
+ import applyDataSetRedirect from './applyDataSetRedirect.js';
20
+ import createMutantReadConfigFile from './mutants/createMutantReadConfigFile.js';
21
+ import { endpointFixture } from './mutants/test/fixtures.mjs';
22
+ import { openMutantRun } from './mutants/mutantRuns.js';
23
+
24
+ // A journey on a data set under a mutant has both read wrappers on its
25
+ // context: the data set redirect on connections/<id>.json and the mutant on
26
+ // its one artifact. They touch different artifacts, so the order they are
27
+ // stacked in must not matter.
28
+
29
+ const connections = { MongoDBCollection: { meta: { tenant: true, dataSet: 'redirect' } } };
30
+
31
+ const session = {
32
+ id: 'session-1',
33
+ name: 'shop',
34
+ state: 'open',
35
+ databaseUri: 'mongodb://127.0.0.1:27999/?replicaSet=rs',
36
+ prefix: 'ld_0123456789ab',
37
+ databaseName: 'ld_0123456789ab_0',
38
+ databases: new Map([[JSON.stringify(['secret:MONGODB_URI', null]), 'ld_0123456789ab_0']]),
39
+ databaseCount: 1,
40
+ };
41
+
42
+ function setup() {
43
+ const { root } = endpointFixture();
44
+ const files = {
45
+ 'api/notify.json': serializer.serializeToString(root),
46
+ 'connections/tickets.json': serializer.serializeToString({
47
+ id: 'connection:tickets',
48
+ connectionId: 'tickets',
49
+ type: 'MongoDBCollection',
50
+ properties: { databaseUri: { _secret: 'MONGODB_URI' }, collection: 'tickets', write: true },
51
+ }),
52
+ };
53
+ async function readConfigFile(filePath) {
54
+ return serializer.deserializeFromString(files[filePath] ?? 'null');
55
+ }
56
+ const opened = openMutantRun({
57
+ mutant: {
58
+ buildId: 'build-1',
59
+ artifact: 'api/notify.json',
60
+ key: root.routine[0]['~k'],
61
+ arg: null,
62
+ operator: 'drop-step',
63
+ },
64
+ });
65
+ return { opened, readConfigFile };
66
+ }
67
+
68
+ function stackDataThenMutant({ readConfigFile, run }) {
69
+ const context = { readConfigFile };
70
+ applyDataSetRedirect({ context, session, connections });
71
+ context.readConfigFile = createMutantReadConfigFile({
72
+ readConfigFile: context.readConfigFile,
73
+ run,
74
+ });
75
+ return context.readConfigFile;
76
+ }
77
+
78
+ function stackMutantThenData({ readConfigFile, run }) {
79
+ const context = { readConfigFile: createMutantReadConfigFile({ readConfigFile, run }) };
80
+ applyDataSetRedirect({ context, session, connections });
81
+ return context.readConfigFile;
82
+ }
83
+
84
+ async function readBoth(stack) {
85
+ const { opened, readConfigFile } = setup();
86
+ const read = stack({ readConfigFile, run: opened.run });
87
+ const result = {
88
+ endpoint: await read('api/notify.json'),
89
+ connection: await read('connections/tickets.json'),
90
+ applied: opened.run.applied,
91
+ misses: opened.run.misses,
92
+ };
93
+ opened.close();
94
+ return result;
95
+ }
96
+
97
+ test('the data set redirect and the mutant wrapper give the same reads stacked in either order', async () => {
98
+ const dataFirst = await readBoth(stackDataThenMutant);
99
+ const mutantFirst = await readBoth(stackMutantThenData);
100
+ expect(mutantFirst).toEqual(dataFirst);
101
+
102
+ expect(dataFirst.applied).toBe(1);
103
+ expect(dataFirst.misses).toEqual([]);
104
+ expect(dataFirst.endpoint.routine.map((step) => step.stepId ?? Object.keys(step)[0])).toEqual([
105
+ ':if',
106
+ ':if',
107
+ ':return',
108
+ ]);
109
+ expect(dataFirst.connection.properties).toEqual({
110
+ databaseUri: session.databaseUri,
111
+ databaseName: session.databaseName,
112
+ collection: 'tickets',
113
+ write: true,
114
+ });
115
+ });
@@ -0,0 +1,39 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { type } from '@lowdefy/helpers';
18
+
19
+ import findNodeByKey from './findNodeByKey.js';
20
+ import findNodeByPointer from './findNodeByPointer.js';
21
+ import operators from './operators/index.js';
22
+
23
+ // Applies one mutant to a deserialised artifact copy, in place. Reports
24
+ // whether it applied, and why not when it did not, changing nothing then. The
25
+ // node is found by its key; when the page was rebuilt since the mutant was
26
+ // listed its keys are new, and the node is found by its pointer instead.
27
+ function applyMutant({ root, mutant: { operator, key, arg, pointer, nodeHash } }) {
28
+ const definition = operators[operator];
29
+ if (type.isUndefined(definition)) {
30
+ return { applied: false, reason: `unknown operator "${operator}"` };
31
+ }
32
+ const found = findNodeByKey({ root, key }) ?? findNodeByPointer({ root, pointer, nodeHash });
33
+ if (found === null) {
34
+ return { applied: false, reason: 'key not found' };
35
+ }
36
+ return definition.apply({ ...found, arg });
37
+ }
38
+
39
+ export default applyMutant;
@@ -0,0 +1,25 @@
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 build artifacts config mutants reach, by path relative to the build
18
+ // directory. A request artifact sits under its page's folder, so a page
19
+ // artifact is any other JSON file under pages/.
20
+ const REQUEST_ARTIFACT = /^pages\/.+\/requests\/[^/]+\.json$/;
21
+ const PAGE_ARTIFACT = /^pages\/(?!.+\/requests\/[^/]+\.json$).+\.json$/;
22
+ const ENDPOINT_ARTIFACT = /^api\/.+\.json$/;
23
+ const EVENTS_ARTIFACT = /^events\.json$/;
24
+
25
+ export { REQUEST_ARTIFACT, PAGE_ARTIFACT, ENDPOINT_ARTIFACT, EVENTS_ARTIFACT };
@@ -0,0 +1,45 @@
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
+ // Where a mutant sits, in the terms a journey's measured path uses, so the
18
+ // harden run can tell which journeys reach it.
19
+ function buildAnchor({ scope, type }) {
20
+ switch (type) {
21
+ case 'action':
22
+ return {
23
+ type,
24
+ pageId: scope.pageId,
25
+ blockId: scope.blockId,
26
+ eventName: scope.eventName,
27
+ actionId: scope.actionId,
28
+ };
29
+ case 'block':
30
+ return {
31
+ type,
32
+ pageId: scope.pageId,
33
+ blockId: scope.blockId,
34
+ parentBlockId: scope.parentBlockId,
35
+ };
36
+ case 'request':
37
+ return { type, pageId: scope.pageId, requestId: scope.requestId };
38
+ case 'endpoint':
39
+ return { type, endpointId: scope.endpointId };
40
+ default:
41
+ throw new Error(`Unknown mutant anchor type "${type}".`);
42
+ }
43
+ }
44
+
45
+ export default buildAnchor;
@@ -0,0 +1,41 @@
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 configPathSegments from './configPathSegments.js';
18
+
19
+ // The array items, across a build's keyMap, whose id and type label is shared
20
+ // by a sibling at another index. Their position-free path keeps the index,
21
+ // since the label alone would name two nodes.
22
+ function collectSharedIds({ keyMap }) {
23
+ const indices = new Map();
24
+ Object.values(keyMap).forEach((entry) => {
25
+ const segments = configPathSegments(entry.key ?? '');
26
+ const last = segments[segments.length - 1];
27
+ if (last === undefined || last.label === '' || last.end !== entry.key.length) {
28
+ return;
29
+ }
30
+ const sibling = `${last.prefix}[${last.label}]`;
31
+ if (!indices.has(sibling)) {
32
+ indices.set(sibling, new Set());
33
+ }
34
+ indices.get(sibling).add(last.index);
35
+ });
36
+ return new Set(
37
+ [...indices.entries()].filter(([, found]) => found.size > 1).map(([sibling]) => sibling)
38
+ );
39
+ }
40
+
41
+ export default collectSharedIds;
@@ -0,0 +1,38 @@
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 array-item segments of a keyMap config path, such as
18
+ // `root.pages[0:tickets:PageHeaderMenu].blocks[3:assign_submit:Button]`: each
19
+ // with the path before it, its index and what follows the index (the item's
20
+ // id and type, empty for an item that has neither).
21
+ function configPathSegments(configPath) {
22
+ const pattern = /\[(\d+)(?::([^\]]*))?\]/g;
23
+ const segments = [];
24
+ let match = pattern.exec(configPath);
25
+ while (match !== null) {
26
+ segments.push({
27
+ start: match.index,
28
+ end: match.index + match[0].length,
29
+ prefix: configPath.slice(0, match.index),
30
+ index: match[1],
31
+ label: match[2] ?? '',
32
+ });
33
+ match = pattern.exec(configPath);
34
+ }
35
+ return segments;
36
+ }
37
+
38
+ export default configPathSegments;
@@ -0,0 +1,42 @@
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 { serializer } from '@lowdefy/helpers';
18
+
19
+ import applyMutant from './applyMutant.js';
20
+
21
+ // Wraps a request's readConfigFile so the one artifact a mutant run targets is
22
+ // served mutated: the original read (which the cache may share with every
23
+ // other request) is copied, the mutant applied to the copy, and each
24
+ // application counted on the run. Every other path passes straight through.
25
+ function createMutantReadConfigFile({ readConfigFile, run }) {
26
+ return async function mutantReadConfigFile(filePath) {
27
+ const original = await readConfigFile(filePath);
28
+ if (filePath !== run.mutant.artifact) {
29
+ return original;
30
+ }
31
+ const copy = serializer.copy(original);
32
+ const outcome = applyMutant({ root: copy, mutant: run.mutant });
33
+ if (outcome.applied) {
34
+ run.applied += 1;
35
+ } else {
36
+ run.misses.push({ reason: outcome.reason, path: filePath });
37
+ }
38
+ return copy;
39
+ };
40
+ }
41
+
42
+ export default createMutantReadConfigFile;