@lowdefy/server-dev 6.0.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 (625) 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 +63 -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 +224 -39
  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/getAuth.js +46 -0
  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/{getAuthConfig.js → getStrategies.js} +12 -10
  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 +174 -41
  348. package/lib/server/createLowdefyContext.test.mjs +437 -0
  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 +77 -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/scrubSecrets.js +23 -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 +24 -2
  439. package/manager/processes/initialBuild.mjs +14 -0
  440. package/manager/processes/initialBuild.test.mjs +101 -0
  441. package/manager/processes/lowdefyBuild.mjs +30 -7
  442. package/manager/processes/lowdefyBuild.skipPluginChecks.test.mjs +62 -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 +61 -49
  527. package/package.original.json +61 -49
  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 +196 -14
  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 +4 -2
  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/lib/server/auth/session.js +0 -39
  621. package/manager/processes/warnAuthUrlPortMismatch.mjs +0 -47
  622. package/manager/processes/warnAuthUrlPortMismatch.test.mjs +0 -93
  623. package/manager/watchers/moduleBuildWatcher.mjs +0 -79
  624. package/manager/watchers/serverArtifactWatcher.mjs +0 -120
  625. package/src/routes/apiPage.js +0 -37
@@ -0,0 +1,340 @@
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
+ import { callRequest } from '@lowdefy/api';
19
+ import { ConfigError } from '@lowdefy/errors';
20
+
21
+ import applyDataSetRedirect from './applyDataSetRedirect.js';
22
+
23
+ const connections = {
24
+ MongoDBCollection: { meta: { tenant: true, dataSet: 'redirect' } },
25
+ FolderRuntime: { meta: { dataSet: 'redirect' } },
26
+ AxiosHttp: { meta: { dataSet: 'external' } },
27
+ Knex: {},
28
+ Redis: { meta: {} },
29
+ PluginStore: { meta: { dataSet: 'mongo' } },
30
+ };
31
+
32
+ const artifacts = {
33
+ 'connections/tickets.json': {
34
+ id: 'connection:tickets',
35
+ connectionId: 'tickets',
36
+ '~k': 'k_tickets',
37
+ type: 'MongoDBCollection',
38
+ properties: { databaseUri: { _secret: 'MONGODB_URI' }, collection: 'tickets', write: true },
39
+ },
40
+ 'connections/no_uri.json': {
41
+ id: 'connection:no_uri',
42
+ connectionId: 'no_uri',
43
+ type: 'MongoDBCollection',
44
+ properties: { collection: 'tickets' },
45
+ },
46
+ 'connections/nested.json': {
47
+ id: 'connection:nested',
48
+ connectionId: 'nested',
49
+ type: 'MongoDBCollection',
50
+ properties: {
51
+ databaseUri: { _secret: 'MONGODB_URI' },
52
+ collection: { _if: { test: true, then: 'a', else: 'b' } },
53
+ options: { appName: { '_string.concat': ['x', 'y'] } },
54
+ },
55
+ },
56
+ 'connections/whole.json': {
57
+ id: 'connection:whole',
58
+ connectionId: 'whole',
59
+ '~k': 'k_whole',
60
+ type: 'MongoDBCollection',
61
+ properties: {
62
+ _if: { test: true, then: { databaseUri: 'mongodb://real/' }, else: {} },
63
+ },
64
+ },
65
+ 'connections/whole_ref.json': {
66
+ id: 'connection:whole_ref',
67
+ connectionId: 'whole_ref',
68
+ type: 'MongoDBCollection',
69
+ properties: { _ref: 'connection.yaml' },
70
+ },
71
+ 'connections/array.json': {
72
+ id: 'connection:array',
73
+ connectionId: 'array',
74
+ type: 'MongoDBCollection',
75
+ properties: [],
76
+ },
77
+ 'connections/folders.json': {
78
+ id: 'connection:folders',
79
+ connectionId: 'folders',
80
+ type: 'FolderRuntime',
81
+ properties: { databaseUri: { _secret: 'MONGODB_URI' }, databaseName: 'app' },
82
+ },
83
+ 'connections/contacts/contacts.json': {
84
+ id: 'connection:contacts/contacts',
85
+ connectionId: 'contacts/contacts',
86
+ type: 'MongoDBCollection',
87
+ properties: { databaseUri: { _secret: 'MONGODB_URI' }, collection: 'contacts' },
88
+ },
89
+ 'connections/api.json': {
90
+ id: 'connection:api',
91
+ connectionId: 'api',
92
+ type: 'AxiosHttp',
93
+ properties: { baseURL: 'https://example.com' },
94
+ },
95
+ 'connections/sql.json': {
96
+ id: 'connection:sql',
97
+ connectionId: 'sql',
98
+ '~k': 'k_sql',
99
+ type: 'Knex',
100
+ properties: { client: 'pg', connection: { _secret: 'PG_URI' } },
101
+ },
102
+ 'connections/cache.json': {
103
+ id: 'connection:cache',
104
+ connectionId: 'cache',
105
+ type: 'Redis',
106
+ properties: { connection: { _secret: 'REDIS_URI' } },
107
+ },
108
+ 'connections/store.json': {
109
+ id: 'connection:store',
110
+ connectionId: 'store',
111
+ type: 'PluginStore',
112
+ properties: { databaseUri: 'mongodb://real/' },
113
+ },
114
+ 'connections/unknown.json': {
115
+ id: 'connection:unknown',
116
+ connectionId: 'unknown',
117
+ type: 'NotInstalled',
118
+ properties: { databaseUri: 'mongodb://real/' },
119
+ },
120
+ 'pages/home.json': { id: 'page:home', properties: { databaseUri: 'not a connection' } },
121
+ };
122
+
123
+ function createContext() {
124
+ return { readConfigFile: async (filePath) => artifacts[filePath] ?? null };
125
+ }
126
+
127
+ const session = {
128
+ id: 'abc',
129
+ name: 'staging-sample',
130
+ state: 'open',
131
+ databaseUri: 'mongodb://127.0.0.1:5555/?replicaSet=testset',
132
+ databaseName: 'ld_000000000001',
133
+ };
134
+
135
+ function redirectedContext(options = {}) {
136
+ const context = createContext();
137
+ applyDataSetRedirect({ context, session: options.session ?? session, connections });
138
+ return context;
139
+ }
140
+
141
+ test('applyDataSetRedirect points a MongoDBCollection connection at the session database', async () => {
142
+ const context = redirectedContext();
143
+ expect(await context.readConfigFile('connections/tickets.json')).toEqual({
144
+ id: 'connection:tickets',
145
+ connectionId: 'tickets',
146
+ '~k': 'k_tickets',
147
+ type: 'MongoDBCollection',
148
+ properties: {
149
+ databaseUri: session.databaseUri,
150
+ databaseName: session.databaseName,
151
+ collection: 'tickets',
152
+ write: true,
153
+ },
154
+ });
155
+ });
156
+
157
+ test('applyDataSetRedirect points a module connection, written under its module folder, at the session database', async () => {
158
+ const context = redirectedContext();
159
+ const artifact = await context.readConfigFile('connections/contacts/contacts.json');
160
+ expect(artifact.properties).toEqual({
161
+ databaseUri: session.databaseUri,
162
+ databaseName: session.databaseName,
163
+ collection: 'contacts',
164
+ });
165
+ });
166
+
167
+ test('applyDataSetRedirect points a plugin type that declares redirect at the session database', async () => {
168
+ const context = redirectedContext();
169
+ const artifact = await context.readConfigFile('connections/folders.json');
170
+ expect(artifact.properties).toEqual({
171
+ databaseUri: session.databaseUri,
172
+ databaseName: session.databaseName,
173
+ });
174
+ });
175
+
176
+ test('applyDataSetRedirect merges the session database into a redirect connection that names no databaseUri', async () => {
177
+ const context = redirectedContext();
178
+ const artifact = await context.readConfigFile('connections/no_uri.json');
179
+ expect(artifact.properties).toEqual({
180
+ databaseUri: session.databaseUri,
181
+ databaseName: session.databaseName,
182
+ collection: 'tickets',
183
+ });
184
+ });
185
+
186
+ test('applyDataSetRedirect gives a connection with a _secret databaseUri and nested operators the session literal URI', async () => {
187
+ const context = redirectedContext();
188
+ const artifact = await context.readConfigFile('connections/nested.json');
189
+ expect(artifact.properties.databaseUri).toEqual(session.databaseUri);
190
+ expect(artifact.properties.databaseName).toEqual(session.databaseName);
191
+ expect(artifact.properties.collection).toEqual({ _if: { test: true, then: 'a', else: 'b' } });
192
+ });
193
+
194
+ test('applyDataSetRedirect refuses a redirect connection whose properties are an operator, naming the connection', async () => {
195
+ const context = redirectedContext();
196
+ const error = await context.readConfigFile('connections/whole.json').catch((caught) => caught);
197
+ expect(error).toBeInstanceOf(ConfigError);
198
+ expect(error.message).toEqual(
199
+ 'Connection "whole" (type MongoDBCollection) cannot run under data set "staging-sample": its properties are an operator, so the redirect cannot be checked.'
200
+ );
201
+ expect(error.configKey).toEqual('k_whole');
202
+ for (const filePath of ['connections/whole_ref.json', 'connections/array.json']) {
203
+ await expect(context.readConfigFile(filePath)).rejects.toThrow(
204
+ 'its properties are an operator, so the redirect cannot be checked.'
205
+ );
206
+ }
207
+ });
208
+
209
+ test('applyDataSetRedirect refuses a connection whose type declares no dataSet, even with a literal databaseUri', async () => {
210
+ const context = redirectedContext();
211
+ const error = await context.readConfigFile('connections/sql.json').catch((caught) => caught);
212
+ expect(error).toBeInstanceOf(ConfigError);
213
+ expect(error.message).toEqual(
214
+ 'Connection "sql" (type Knex) cannot run under data set "staging-sample": its type does not say how to redirect it.'
215
+ );
216
+ expect(error.configKey).toEqual('k_sql');
217
+ await expect(context.readConfigFile('connections/cache.json')).rejects.toThrow(
218
+ 'Connection "cache" (type Redis) cannot run under data set "staging-sample": its type does not say how to redirect it.'
219
+ );
220
+ await expect(context.readConfigFile('connections/store.json')).rejects.toThrow(
221
+ 'Connection "store" (type PluginStore) cannot run under data set "staging-sample": its type does not say how to redirect it.'
222
+ );
223
+ await expect(context.readConfigFile('connections/unknown.json')).rejects.toThrow(
224
+ 'Connection "unknown" (type NotInstalled) cannot run under data set "staging-sample": its type does not say how to redirect it.'
225
+ );
226
+ });
227
+
228
+ test('applyDataSetRedirect leaves an external connection and every other path untouched', async () => {
229
+ const context = redirectedContext();
230
+ expect(await context.readConfigFile('connections/api.json')).toBe(
231
+ artifacts['connections/api.json']
232
+ );
233
+ expect(await context.readConfigFile('pages/home.json')).toBe(artifacts['pages/home.json']);
234
+ expect(await context.readConfigFile('connections/missing.json')).toBeNull();
235
+ });
236
+
237
+ test('applyDataSetRedirect never mutates the artifact it read', async () => {
238
+ const context = redirectedContext();
239
+ await context.readConfigFile('connections/tickets.json');
240
+ await context.readConfigFile('connections/nested.json');
241
+ const plain = createContext();
242
+ expect((await plain.readConfigFile('connections/tickets.json')).properties.databaseUri).toEqual({
243
+ _secret: 'MONGODB_URI',
244
+ });
245
+ expect(artifacts['connections/tickets.json'].properties.databaseName).toBeUndefined();
246
+ expect(artifacts['connections/nested.json'].properties.databaseUri).toEqual({
247
+ _secret: 'MONGODB_URI',
248
+ });
249
+ });
250
+
251
+ test('applyDataSetRedirect throws on a connection read after the session has closed', async () => {
252
+ const context = redirectedContext({ session: { ...session, state: 'closed' } });
253
+ await expect(context.readConfigFile('connections/tickets.json')).rejects.toThrow(
254
+ 'Data session abc has ended.'
255
+ );
256
+ await expect(context.readConfigFile('connections/api.json')).rejects.toThrow(
257
+ 'Data session abc has ended.'
258
+ );
259
+ expect(await context.readConfigFile('pages/home.json')).toBe(artifacts['pages/home.json']);
260
+ });
261
+
262
+ describe('a request through callRequest under a data session', () => {
263
+ const resolver = jest.fn(async () => ({ ok: true }));
264
+ resolver.schema = {};
265
+ resolver.meta = { checkRead: false, checkWrite: false };
266
+
267
+ function createRequestContext({ connectionType }) {
268
+ const requestConnections = {
269
+ [connectionType]: {
270
+ ...connections[connectionType],
271
+ schema: {},
272
+ requests: { RunQuery: resolver },
273
+ },
274
+ };
275
+ const configFiles = {
276
+ 'connections/store.json': {
277
+ id: 'connection:store',
278
+ connectionId: 'store',
279
+ type: connectionType,
280
+ properties: { databaseUri: 'mongodb://real/', collection: 'rows' },
281
+ },
282
+ 'pages/home/requests/get_rows.json': {
283
+ id: 'request:home:get_rows',
284
+ type: 'RunQuery',
285
+ requestId: 'get_rows',
286
+ pageId: 'home',
287
+ connectionId: 'store',
288
+ auth: { public: true },
289
+ properties: {},
290
+ },
291
+ };
292
+ const logger = { debug: () => {}, error: () => {}, info: () => {}, warn: () => {} };
293
+ const context = {
294
+ appMeta: {},
295
+ authorizeOutcome: () => 'allow',
296
+ config: {},
297
+ connections: requestConnections,
298
+ handleError: async (error) => {
299
+ error.handled = true;
300
+ },
301
+ headers: {},
302
+ logger,
303
+ mode: 'dev',
304
+ operators: {},
305
+ organization: null,
306
+ readConfigFile: async (filePath) => configFiles[filePath] ?? null,
307
+ scrubSecrets: (value) => value,
308
+ secrets: {},
309
+ steps: {},
310
+ user: null,
311
+ };
312
+ applyDataSetRedirect({ context, session, connections: requestConnections });
313
+ return context;
314
+ }
315
+
316
+ beforeEach(() => {
317
+ resolver.mockClear();
318
+ });
319
+
320
+ test('fails and never reaches the resolver when the connection type declares no dataSet', async () => {
321
+ const context = createRequestContext({ connectionType: 'Knex' });
322
+ await expect(
323
+ callRequest(context, { pageId: 'home', requestId: 'get_rows', payload: {} })
324
+ ).rejects.toThrow(
325
+ 'Connection "store" (type Knex) cannot run under data set "staging-sample": its type does not say how to redirect it.'
326
+ );
327
+ expect(resolver).not.toHaveBeenCalled();
328
+ });
329
+
330
+ test('reaches the resolver with the session database when the connection type declares redirect', async () => {
331
+ const context = createRequestContext({ connectionType: 'FolderRuntime' });
332
+ await callRequest(context, { pageId: 'home', requestId: 'get_rows', payload: {} });
333
+ expect(resolver).toHaveBeenCalledTimes(1);
334
+ expect(resolver.mock.calls[0][0].connection).toEqual({
335
+ databaseUri: session.databaseUri,
336
+ databaseName: session.databaseName,
337
+ collection: 'rows',
338
+ });
339
+ });
340
+ });
@@ -0,0 +1,204 @@
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 os from 'node:os';
19
+ import path from 'node:path';
20
+
21
+ import { callRequest } from '@lowdefy/api';
22
+ import createReadConfigFile from '@lowdefy/api/context/createReadConfigFile.js';
23
+
24
+ import applyDataSetRedirect from './applyDataSetRedirect.js';
25
+
26
+ // A walled-client plugin under auth.organizations.policy: tenant, served inside a data session and
27
+ // outside one, in both orders. The unwalled-reach verdict is memoised per process and read from the
28
+ // build, so whichever request evaluates it first, the walled-client plugin is served everywhere and
29
+ // a plugin that really holds the walled database's URI is refused everywhere.
30
+
31
+ const walledUri = 'mongodb://app-host:27017/app';
32
+
33
+ const session = {
34
+ id: 'abc',
35
+ name: 'staging-sample',
36
+ state: 'open',
37
+ databaseUri: 'mongodb://127.0.0.1:5555/?replicaSet=testset',
38
+ databaseName: 'ld_000000000001',
39
+ };
40
+
41
+ async function WalledRead({ connection, walled }) {
42
+ const bound = await walled(connection.mongoConnectionId);
43
+ return {
44
+ databaseUri: bound.connection.databaseUri,
45
+ databaseName: bound.connection.databaseName ?? null,
46
+ tenant: bound.tenant,
47
+ };
48
+ }
49
+ WalledRead.schema = {};
50
+ WalledRead.meta = { checkRead: true, checkWrite: false };
51
+
52
+ const connections = {
53
+ MongoDBCollection: { meta: { tenant: true, dataSet: 'redirect' }, schema: {}, requests: {} },
54
+ WalledRuntime: {
55
+ meta: { tenant: false, dataSet: 'walled' },
56
+ schema: {},
57
+ requests: { WalledRead },
58
+ },
59
+ };
60
+
61
+ const artifacts = {
62
+ 'tenantConnections.json': [{ connectionId: 'fixture_db', type: 'MongoDBCollection' }],
63
+ 'unwalledConnections.json': [
64
+ { connectionId: 'walled_runtime', type: 'WalledRuntime' },
65
+ { connectionId: 'raw_runtime', type: 'WalledRuntime' },
66
+ ],
67
+ 'connections/fixture_db.json': {
68
+ id: 'connection:fixture_db',
69
+ connectionId: 'fixture_db',
70
+ type: 'MongoDBCollection',
71
+ tenantCapability: true,
72
+ properties: { databaseUri: walledUri, collection: 'items', read: true },
73
+ },
74
+ 'connections/walled_runtime.json': {
75
+ id: 'connection:walled_runtime',
76
+ connectionId: 'walled_runtime',
77
+ type: 'WalledRuntime',
78
+ tenantCapability: false,
79
+ properties: { mongoConnectionId: 'fixture_db', read: true },
80
+ },
81
+ // A plugin that holds the walled database's URI itself while naming a walled connection: the
82
+ // reach check refuses it, in and out of sessions.
83
+ 'connections/raw_runtime.json': {
84
+ id: 'connection:raw_runtime',
85
+ connectionId: 'raw_runtime',
86
+ type: 'WalledRuntime',
87
+ '~k': 'k_raw_runtime',
88
+ tenantCapability: false,
89
+ properties: { mongoConnectionId: 'fixture_db', uri: walledUri, read: true },
90
+ },
91
+ 'pages/home/requests/accept_match.json': {
92
+ id: 'request:home:accept_match',
93
+ type: 'WalledRead',
94
+ requestId: 'accept_match',
95
+ pageId: 'home',
96
+ connectionId: 'walled_runtime',
97
+ auth: { public: true },
98
+ properties: {},
99
+ },
100
+ 'pages/home/requests/raw_read.json': {
101
+ id: 'request:home:raw_read',
102
+ type: 'WalledRead',
103
+ requestId: 'raw_read',
104
+ pageId: 'home',
105
+ connectionId: 'raw_runtime',
106
+ auth: { public: true },
107
+ properties: {},
108
+ },
109
+ };
110
+
111
+ const rawRefusal =
112
+ 'Connection "raw_runtime" (WalledRuntime) holds a URI to the database of walled connection "fixture_db".';
113
+
114
+ let buildDirectory;
115
+
116
+ beforeAll(() => {
117
+ buildDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-walled-reach-'));
118
+ Object.entries(artifacts).forEach(([name, value]) => {
119
+ const filePath = path.join(buildDirectory, name);
120
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
121
+ fs.writeFileSync(filePath, JSON.stringify(value));
122
+ });
123
+ });
124
+
125
+ afterAll(() => {
126
+ fs.rmSync(buildDirectory, { recursive: true, force: true });
127
+ });
128
+
129
+ // The context createLowdefyContext composes: createApiContext's base reader over the build, and
130
+ // for a data-session request the gate over it.
131
+ function createContext({ config, dataSession }) {
132
+ const logger = { debug: () => {}, error: () => {}, info: () => {}, warn: () => {} };
133
+ const context = {
134
+ appMeta: {},
135
+ authorizeOutcome: () => 'allow',
136
+ buildDirectory,
137
+ config,
138
+ connections,
139
+ fileCache: new Map(),
140
+ handleError: async (error) => {
141
+ error.handled = true;
142
+ },
143
+ headers: {},
144
+ logger,
145
+ mode: 'dev',
146
+ operators: {},
147
+ organization: { policy: 'tenant' },
148
+ scrubSecrets: (value) => value,
149
+ secrets: {},
150
+ steps: {},
151
+ user: { id: 'u_member', organization_id: 'org_a' },
152
+ };
153
+ context.readConfigFile = createReadConfigFile(context);
154
+ if (dataSession) {
155
+ applyDataSetRedirect({ context, session, connections });
156
+ }
157
+ return context;
158
+ }
159
+
160
+ async function acceptMatch(context) {
161
+ const result = await callRequest(context, {
162
+ pageId: 'home',
163
+ requestId: 'accept_match',
164
+ payload: {},
165
+ });
166
+ return result.response;
167
+ }
168
+
169
+ const servedInSession = {
170
+ databaseUri: session.databaseUri,
171
+ databaseName: session.databaseName,
172
+ tenant: { field: 'organization_id', value: 'org_a' },
173
+ };
174
+ const servedOutside = {
175
+ databaseUri: walledUri,
176
+ databaseName: null,
177
+ tenant: { field: 'organization_id', value: 'org_a' },
178
+ };
179
+
180
+ test('a walled-client plugin is served in a data session first and outside one afterwards', async () => {
181
+ const config = {};
182
+ expect(await acceptMatch(createContext({ config, dataSession: true }))).toEqual(servedInSession);
183
+ expect(await acceptMatch(createContext({ config, dataSession: false }))).toEqual(servedOutside);
184
+ await expect(
185
+ callRequest(createContext({ config, dataSession: false }), {
186
+ pageId: 'home',
187
+ requestId: 'raw_read',
188
+ payload: {},
189
+ })
190
+ ).rejects.toThrow(rawRefusal);
191
+ });
192
+
193
+ test('a walled-client plugin is served outside a data session first and in one afterwards', async () => {
194
+ const config = {};
195
+ expect(await acceptMatch(createContext({ config, dataSession: false }))).toEqual(servedOutside);
196
+ expect(await acceptMatch(createContext({ config, dataSession: true }))).toEqual(servedInSession);
197
+ await expect(
198
+ callRequest(createContext({ config, dataSession: true }), {
199
+ pageId: 'home',
200
+ requestId: 'raw_read',
201
+ payload: {},
202
+ })
203
+ ).rejects.toThrow(rawRefusal);
204
+ });
@@ -0,0 +1,131 @@
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 createEvaluateOperators from '@lowdefy/api/context/createEvaluateOperators.js';
18
+ import createWalledResolver from '@lowdefy/api/routes/request/createWalledResolver.js';
19
+ import { ConfigError } from '@lowdefy/errors';
20
+ import { operatorsServer } from '@lowdefy/operators-js';
21
+
22
+ import applyDataSetRedirect from './applyDataSetRedirect.js';
23
+
24
+ const connections = {
25
+ MongoDBCollection: { meta: { tenant: true, dataSet: 'redirect' }, requests: {} },
26
+ WalledRuntime: { meta: { tenant: false, dataSet: 'walled' }, requests: {} },
27
+ };
28
+
29
+ const artifacts = {
30
+ 'connections/runtime.json': {
31
+ id: 'connection:runtime',
32
+ connectionId: 'runtime',
33
+ type: 'WalledRuntime',
34
+ properties: { mongoConnectionIds: ['matches'], apiKey: { _secret: 'RUNTIME_KEY' } },
35
+ },
36
+ 'connections/runtime_operator.json': {
37
+ id: 'connection:runtime_operator',
38
+ connectionId: 'runtime_operator',
39
+ type: 'WalledRuntime',
40
+ properties: { _if: { test: true, then: { mongoConnectionIds: ['matches'] }, else: {} } },
41
+ },
42
+ 'connections/matches.json': {
43
+ id: 'connection:matches',
44
+ connectionId: 'matches',
45
+ '~k': 'k_matches',
46
+ type: 'MongoDBCollection',
47
+ properties: { databaseUri: { _secret: 'MONGODB_URI' }, collection: 'matches', write: true },
48
+ },
49
+ 'connections/matches_operator.json': {
50
+ id: 'connection:matches_operator',
51
+ connectionId: 'matches_operator',
52
+ '~k': 'k_matches_operator',
53
+ type: 'MongoDBCollection',
54
+ properties: {
55
+ _if: { test: true, then: { databaseUri: 'mongodb://real/', collection: 'matches' } },
56
+ },
57
+ },
58
+ };
59
+
60
+ const session = {
61
+ id: 'abc',
62
+ name: 'staging-sample',
63
+ state: 'open',
64
+ databaseUri: 'mongodb://127.0.0.1:5555/?replicaSet=testset',
65
+ databaseName: 'ld_000000000001',
66
+ };
67
+
68
+ function createContext({ sessionState = 'open' } = {}) {
69
+ const context = {
70
+ appMeta: {},
71
+ config: {},
72
+ connections,
73
+ logger: { debug: () => {}, error: () => {}, info: () => {}, warn: () => {} },
74
+ operators: { ...operatorsServer },
75
+ organization: null,
76
+ payload: {},
77
+ readConfigFile: async (filePath) => artifacts[filePath] ?? null,
78
+ secrets: { MONGODB_URI: 'mongodb://real-host/app', RUNTIME_KEY: 'key' },
79
+ user: null,
80
+ };
81
+ context.evaluateOperators = createEvaluateOperators(context);
82
+ applyDataSetRedirect({
83
+ context,
84
+ session: { ...session, state: sessionState },
85
+ connections,
86
+ });
87
+ return context;
88
+ }
89
+
90
+ const requestConfig = { '~k': 'r.0', requestId: 'accept_match', connectionId: 'runtime' };
91
+
92
+ test('applyDataSetRedirect passes a connection whose type declares walled through with its properties untouched', async () => {
93
+ const context = createContext();
94
+ expect(await context.readConfigFile('connections/runtime.json')).toEqual(
95
+ artifacts['connections/runtime.json']
96
+ );
97
+ expect(await context.readConfigFile('connections/runtime_operator.json')).toEqual(
98
+ artifacts['connections/runtime_operator.json']
99
+ );
100
+ });
101
+
102
+ test('applyDataSetRedirect throws on a walled connection read after the session has closed', async () => {
103
+ const context = createContext({ sessionState: 'closed' });
104
+ await expect(context.readConfigFile('connections/runtime.json')).rejects.toThrow(
105
+ 'Data session abc has ended.'
106
+ );
107
+ });
108
+
109
+ test('the walled resolver under a data session gives the named MongoDBCollection the session database', async () => {
110
+ const context = createContext();
111
+ const walled = createWalledResolver(context, { requestConfig });
112
+ const bound = await walled('matches');
113
+ expect(bound.connection).toEqual({
114
+ databaseUri: session.databaseUri,
115
+ databaseName: session.databaseName,
116
+ collection: 'matches',
117
+ write: true,
118
+ });
119
+ expect(bound.connectionId).toEqual('matches');
120
+ });
121
+
122
+ test('the walled resolver under a data session refuses a named MongoDBCollection whose properties are an operator', async () => {
123
+ const context = createContext();
124
+ const walled = createWalledResolver(context, { requestConfig });
125
+ const error = await walled('matches_operator').catch((caught) => caught);
126
+ expect(error).toBeInstanceOf(ConfigError);
127
+ expect(error.message).toEqual(
128
+ 'Connection "matches_operator" (type MongoDBCollection) cannot run under data set "staging-sample": its properties are an operator, so the redirect cannot be checked.'
129
+ );
130
+ expect(error.configKey).toEqual('k_matches_operator');
131
+ });