@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,220 @@
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 readDataSet from './readDataSet.js';
22
+
23
+ let configDirectory;
24
+ let buildDirectory;
25
+
26
+ beforeEach(() => {
27
+ configDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-read-data-set-'));
28
+ buildDirectory = path.join(configDirectory, '.lowdefy', 'dev', 'build');
29
+ fs.mkdirSync(path.join(configDirectory, 'tests', 'data'), { recursive: true });
30
+ fs.mkdirSync(path.join(buildDirectory, 'connections'), { recursive: true });
31
+ });
32
+
33
+ afterEach(() => {
34
+ fs.rmSync(configDirectory, { recursive: true, force: true });
35
+ });
36
+
37
+ function writeDataSet(name, content) {
38
+ fs.writeFileSync(path.join(configDirectory, 'tests', 'data', `${name}.yaml`), content);
39
+ }
40
+
41
+ function writeConnection(connectionId, artifact) {
42
+ const filePath = path.join(buildDirectory, 'connections', `${connectionId}.json`);
43
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
44
+ fs.writeFileSync(
45
+ filePath,
46
+ JSON.stringify({ connectionId, '~k': `k_${connectionId}`, ...artifact })
47
+ );
48
+ }
49
+
50
+ function mongo(properties) {
51
+ return {
52
+ type: 'MongoDBCollection',
53
+ properties: { databaseUri: { _secret: 'MONGODB_URI', '~k': 'k' }, ...properties },
54
+ };
55
+ }
56
+
57
+ test('readDataSet maps each keyed connection to its collection', async () => {
58
+ writeConnection('tickets', mongo({ collection: 'tickets' }));
59
+ writeConnection('tickets-archive', mongo({ collection: 'tickets' }));
60
+ writeConnection('organizations', mongo({ collection: 'orgs', databaseName: 'app' }));
61
+ writeConnection('api', { type: 'AxiosHttp', properties: { baseURL: 'https://example.com' } });
62
+ writeDataSet(
63
+ 'fixtures-only',
64
+ `fixtures:
65
+ tickets: [{ _id: t1 }]
66
+ tickets-archive: [{ _id: t2 }]
67
+ indexes:
68
+ organizations: [{ key: { name: 1 } }]
69
+ users:
70
+ owner: { id: u_1 }
71
+ `
72
+ );
73
+ const dataSet = await readDataSet({ configDirectory, buildDirectory, name: 'fixtures-only' });
74
+ expect(dataSet.collections).toEqual({
75
+ tickets: 'tickets',
76
+ 'tickets-archive': 'tickets',
77
+ organizations: 'orgs',
78
+ });
79
+ expect(dataSet.warnings).toEqual([]);
80
+ expect(dataSet.users).toEqual({ owner: { id: 'u_1' } });
81
+ });
82
+
83
+ test('readDataSet reads module connections, which the build writes under their module folder', async () => {
84
+ writeConnection('contacts/contacts', mongo({ collection: 'contacts' }));
85
+ writeConnection('crm-contacts', mongo({ collection: 'contacts', databaseName: 'crm' }));
86
+ writeDataSet('alpha', 'fixtures:\n contacts/contacts: [{ _id: c1 }]\n');
87
+ await expect(readDataSet({ configDirectory, buildDirectory, name: 'alpha' })).rejects.toThrow(
88
+ 'Data set "alpha": Connections "contacts/contacts" and "crm-contacts" both name collection "contacts" in different databases'
89
+ );
90
+ });
91
+
92
+ test('readDataSet maps a module connection to its collection', async () => {
93
+ writeConnection('contacts/contacts', mongo({ collection: 'contacts' }));
94
+ writeDataSet('alpha', 'fixtures:\n contacts/contacts: [{ _id: c1 }]\n');
95
+ const dataSet = await readDataSet({ configDirectory, buildDirectory, name: 'alpha' });
96
+ expect(dataSet.collections).toEqual({ 'contacts/contacts': 'contacts' });
97
+ });
98
+
99
+ test('readDataSet refuses a fixture keyed by a non-MongoDB connection', async () => {
100
+ writeConnection('api', { type: 'AxiosHttp', properties: { baseURL: 'https://example.com' } });
101
+ writeDataSet('alpha', 'fixtures:\n api: [{ _id: a }]\n');
102
+ await expect(readDataSet({ configDirectory, buildDirectory, name: 'alpha' })).rejects.toThrow(
103
+ 'Data set "alpha" connection "api" is a AxiosHttp connection; data sets load MongoDBCollection connections only.'
104
+ );
105
+ });
106
+
107
+ test('readDataSet refuses a key that is not a connection', async () => {
108
+ writeDataSet('alpha', 'fixtures:\n missing: [{ _id: a }]\n');
109
+ await expect(readDataSet({ configDirectory, buildDirectory, name: 'alpha' })).rejects.toThrow(
110
+ 'Data set "alpha" connection "missing" is not a connection in this app.'
111
+ );
112
+ });
113
+
114
+ test('readDataSet refuses an index keyed by a connection whose collection is an operator', async () => {
115
+ writeConnection('tickets', mongo({ collection: { _user: 'organizationId' } }));
116
+ writeDataSet('alpha', 'indexes:\n tickets: [{ key: { a: 1 } }]\n');
117
+ await expect(readDataSet({ configDirectory, buildDirectory, name: 'alpha' })).rejects.toThrow(
118
+ 'Data set "alpha" connection "tickets" has a computed "collection"'
119
+ );
120
+ });
121
+
122
+ test('readDataSet refuses an indexed connection whose databaseName is an operator', async () => {
123
+ writeConnection('tickets', mongo({ collection: 'tickets', databaseName: { _env: 'DB' } }));
124
+ writeDataSet('alpha', 'indexes:\n tickets:\n - { key: { number: 1 } }\n');
125
+ await expect(readDataSet({ configDirectory, buildDirectory, name: 'alpha' })).rejects.toThrow(
126
+ 'Data set "alpha" connection "tickets" has a computed "databaseName"'
127
+ );
128
+ });
129
+
130
+ test('readDataSet refuses a keyed connection naming one collection under a different databaseName from another connection', async () => {
131
+ writeConnection('tickets', mongo({ collection: 'tickets', databaseName: 'app' }));
132
+ writeConnection('legacy-tickets', mongo({ collection: 'tickets', databaseName: 'legacy' }));
133
+ writeDataSet('alpha', 'fixtures:\n tickets: [{ _id: a }]\n');
134
+ await expect(readDataSet({ configDirectory, buildDirectory, name: 'alpha' })).rejects.toThrow(
135
+ 'Data set "alpha": Connections "legacy-tickets" and "tickets" both name collection "tickets" in different databases'
136
+ );
137
+ });
138
+
139
+ test('readDataSet refuses a keyed connection naming one collection under a different databaseUri secret from another connection', async () => {
140
+ writeConnection('tickets', mongo({ collection: 'tickets' }));
141
+ writeConnection('reporting', {
142
+ type: 'MongoDBCollection',
143
+ properties: { databaseUri: { _secret: 'REPORTING_URI' }, collection: 'tickets' },
144
+ });
145
+ writeDataSet('alpha', 'fixtures:\n tickets: [{ _id: a }]\n');
146
+ await expect(readDataSet({ configDirectory, buildDirectory, name: 'alpha' })).rejects.toThrow(
147
+ 'Connections "reporting" and "tickets" both name collection "tickets" in different databases'
148
+ );
149
+ });
150
+
151
+ test('readDataSet warns on the same collision between two unkeyed connections', async () => {
152
+ writeConnection('tickets', mongo({ collection: 'tickets' }));
153
+ writeConnection('a', mongo({ collection: 'events', databaseName: 'one' }));
154
+ writeConnection('b', mongo({ collection: 'events', databaseName: 'two' }));
155
+ writeConnection('computed', mongo({ collection: { _state: 'c' }, databaseName: 'three' }));
156
+ writeDataSet('alpha', 'fixtures:\n tickets: [{ _id: a }]\n');
157
+ const dataSet = await readDataSet({ configDirectory, buildDirectory, name: 'alpha' });
158
+ expect(dataSet.warnings).toEqual([
159
+ 'Connections "a" and "b" both name collection "events" in different databases; under a data set they read one database, so they share one collection.',
160
+ ]);
161
+ });
162
+
163
+ test('readDataSet resolves generated connections and refuses one the app does not have', async () => {
164
+ writeConnection('tickets', mongo({ collection: 'tickets' }));
165
+ writeDataSet(
166
+ 'alpha',
167
+ 'generate:\n seed: 1\n tickets:\n count: 2\n invoices:\n count: 1\n'
168
+ );
169
+ await expect(readDataSet({ configDirectory, buildDirectory, name: 'alpha' })).rejects.toThrow(
170
+ 'Data set "alpha" connection "invoices" is not a connection in this app.'
171
+ );
172
+ writeDataSet('beta', 'generate:\n seed: 1\n tickets:\n count: 2\n');
173
+ const dataSet = await readDataSet({ configDirectory, buildDirectory, name: 'beta' });
174
+ expect(dataSet.collections).toEqual({ tickets: 'tickets' });
175
+ expect(dataSet.generated.tickets.map(({ _id }) => _id)).toEqual(['tickets-1', 'tickets-2']);
176
+ });
177
+
178
+ test('readDataSet passes the size advice of a large data set on as a warning', async () => {
179
+ writeConnection('tickets', mongo({ collection: 'tickets' }));
180
+ writeDataSet('big', 'generate:\n seed: 1\n tickets:\n count: 1200\n');
181
+ const dataSet = await readDataSet({ configDirectory, buildDirectory, name: 'big' });
182
+ expect(dataSet.warnings).toEqual([
183
+ 'Data set "big" loads 1,200 documents for connection "tickets". More than 1,000 per collection is not advised: every journey on it loads slower, and journeys target fixture values, not volume.',
184
+ ]);
185
+ });
186
+
187
+ test('readDataSet refuses a fixture on another connection of the same collection sharing a generated _id', async () => {
188
+ writeConnection('tickets', mongo({ collection: 'tickets' }));
189
+ writeConnection('tickets-archive', mongo({ collection: 'tickets' }));
190
+ writeDataSet(
191
+ 'alpha',
192
+ 'fixtures:\n tickets-archive: [{ _id: tickets-2 }]\ngenerate:\n seed: 1\n tickets:\n count: 2\n'
193
+ );
194
+ await expect(readDataSet({ configDirectory, buildDirectory, name: 'alpha' })).rejects.toThrow(
195
+ 'Data set "alpha" generate.tickets[1] _id "tickets-2" is also the _id of fixture tickets-archive[0]; connections "tickets" and "tickets-archive" both load collection "tickets".'
196
+ );
197
+ });
198
+
199
+ test('readDataSet refuses two connections of the same collection generating one _id', async () => {
200
+ writeConnection('tickets', mongo({ collection: 'tickets' }));
201
+ writeConnection('tickets-archive', mongo({ collection: 'tickets' }));
202
+ writeDataSet(
203
+ 'alpha',
204
+ 'generate:\n seed: 1\n tickets:\n count: 1\n fields:\n _id: { sequence: { prefix: t-, start: 1 } }\n tickets-archive:\n count: 1\n fields:\n _id: { sequence: { prefix: t-, start: 1 } }\n'
205
+ );
206
+ await expect(readDataSet({ configDirectory, buildDirectory, name: 'alpha' })).rejects.toThrow(
207
+ 'Data set "alpha" generate.tickets-archive[0] _id "t-1" is also the _id of generate.tickets[0]; connections "tickets-archive" and "tickets" both load collection "tickets".'
208
+ );
209
+ });
210
+
211
+ test('readDataSet accepts the same _id on connections of different collections', async () => {
212
+ writeConnection('tickets', mongo({ collection: 'tickets' }));
213
+ writeConnection('invoices', mongo({ collection: 'invoices' }));
214
+ writeDataSet(
215
+ 'alpha',
216
+ 'fixtures:\n invoices: [{ _id: tickets-1 }]\ngenerate:\n seed: 1\n tickets:\n count: 1\n'
217
+ );
218
+ const dataSet = await readDataSet({ configDirectory, buildDirectory, name: 'alpha' });
219
+ expect(dataSet.generated.tickets.map(({ _id }) => _id)).toEqual(['tickets-1']);
220
+ });
@@ -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 removeDataStoreFiles from './removeDataStoreFiles.js';
18
+
19
+ const SHUTDOWN_SIGNALS = ['SIGTERM', 'SIGINT', 'SIGHUP'];
20
+
21
+ // A store is stopped, with its temporary database directory removed, when the dev server process
22
+ // shuts down. The manager stops the process with SIGTERM, a terminal Ctrl+C sends SIGINT and closing
23
+ // the terminal sends SIGHUP, whose default action ends the process without an exit event; on each
24
+ // the store stops first and the signal is raised again, so the process ends as it would have.
25
+ // Vite's own SIGTERM handler can exit before that stop finishes, so the exit hook kills mongod and
26
+ // removes its directory synchronously. mongodb-memory-server's killer process still stops mongod
27
+ // after a crash, which runs neither.
28
+ function registerDataStoreShutdown({ replSet, stop }) {
29
+ async function onSignal(signal) {
30
+ try {
31
+ await stop();
32
+ } finally {
33
+ process.kill(process.pid, signal);
34
+ }
35
+ }
36
+ function onExit() {
37
+ removeDataStoreFiles({ replSet });
38
+ }
39
+ SHUTDOWN_SIGNALS.forEach((signal) => process.once(signal, onSignal));
40
+ process.once('exit', onExit);
41
+ return function unregisterDataStoreShutdown() {
42
+ SHUTDOWN_SIGNALS.forEach((signal) => process.off(signal, onSignal));
43
+ process.off('exit', onExit);
44
+ };
45
+ }
46
+
47
+ export default registerDataStoreShutdown;
@@ -0,0 +1,32 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import fs from 'node:fs';
18
+
19
+ // Kills each mongod of the store and removes its temporary database directory, synchronously, so
20
+ // it also runs from a process exit hook. A store already stopped with cleanup has no servers left.
21
+ function removeDataStoreFiles({ replSet }) {
22
+ replSet.servers.forEach((server) => {
23
+ const info = server.instanceInfo;
24
+ if (info === undefined) return;
25
+ info.instance.mongodProcess?.kill('SIGKILL');
26
+ if (info.tmpDir !== undefined) {
27
+ fs.rmSync(info.tmpDir, { recursive: true, force: true });
28
+ }
29
+ });
30
+ }
31
+
32
+ export default removeDataStoreFiles;
@@ -0,0 +1,46 @@
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
+ // A data set loads a connection only when the built artifact names its
20
+ // collection: an operator there is evaluated per request, so the collection cannot be known before
21
+ // one. Returns the literal collection and databaseName (or undefined).
22
+ function resolveDataSetCollection({ dataSetName, connectionId, artifact }) {
23
+ const where = `Data set "${dataSetName}" connection "${connectionId}"`;
24
+ if (type.isNone(artifact)) {
25
+ throw new Error(`${where} is not a connection in this app.`);
26
+ }
27
+ if (artifact.type !== 'MongoDBCollection') {
28
+ throw new Error(
29
+ `${where} is a ${artifact.type} connection; data sets load MongoDBCollection connections only.`
30
+ );
31
+ }
32
+ const { collection, databaseName } = artifact.properties ?? {};
33
+ if (!type.isString(collection)) {
34
+ throw new Error(
35
+ `${where} has a computed "collection"; a data set can only load a connection whose collection is a literal string.`
36
+ );
37
+ }
38
+ if (!type.isUndefined(databaseName) && !type.isString(databaseName)) {
39
+ throw new Error(
40
+ `${where} has a computed "databaseName"; a data set can only load a connection whose databaseName is a literal string or not set.`
41
+ );
42
+ }
43
+ return { collection, databaseName };
44
+ }
45
+
46
+ export default resolveDataSetCollection;
@@ -0,0 +1,106 @@
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 readDataSet from './readDataSet.js';
20
+
21
+ function isUserName(user) {
22
+ return type.isString(user) && user !== 'none';
23
+ }
24
+
25
+ function describeDeclaredUsers({ users }) {
26
+ const names = Object.keys(users);
27
+ if (names.length === 0) {
28
+ return 'It declares no users.';
29
+ }
30
+ return `Declared: ${names.join(', ')}.`;
31
+ }
32
+
33
+ // Everything a journey's data set decides before a browser opens: the data set itself (read and
34
+ // checked against the built connections), and who the journey acts as. A string `user` other than
35
+ // "none" names a data set user. Returns { dataSet, user } (both undefined for a journey with no data
36
+ // set) or { error }.
37
+ //
38
+ // `user: none` while a dev mock user is active is refused: no caller cookie is injected, so the mock
39
+ // user would stand in for the journey instead of it running signed out.
40
+ //
41
+ // Two refusals keep a data-set journey off the app's real auth database:
42
+ // - `user: none` while auth is configured: a signed-out actor signs in through the auth engine,
43
+ // which is bound to the real auth database while its requests read the data set;
44
+ // - any data-set journey while a dev mock user is active: the mock user wins over the injected
45
+ // caller, so the journey would silently act as someone outside the data set.
46
+ //
47
+ // `subject` names the run in the refusals: "journey" for a journey, "call" for the other dev tools
48
+ // that act as someone (resolveToolCaller), which take the same user and data.
49
+ async function resolveJourneyDataSet({
50
+ data,
51
+ user,
52
+ subject = 'journey',
53
+ configDirectory,
54
+ buildDirectory,
55
+ authConfigured,
56
+ mockUserActive,
57
+ }) {
58
+ if (type.isNone(data)) {
59
+ if (user === 'none' && mockUserActive) {
60
+ return {
61
+ error: `The ${subject} has user "none", which cannot run while a dev mock user is active (auth.dev.mockUser or LOWDEFY_DEV_USER): with no caller of its own, every request would act as the mock user, not signed out. Remove the mock user to run signed out, or give the ${subject} a user.`,
62
+ };
63
+ }
64
+ if (isUserName(user)) {
65
+ return {
66
+ error: `The ${subject}'s user ${JSON.stringify(
67
+ user
68
+ )} names a data set user, but the ${subject} has no "data". Add data: <data set name>, or give user as an object.`,
69
+ };
70
+ }
71
+ return { user };
72
+ }
73
+ if (mockUserActive) {
74
+ return {
75
+ error: `The ${subject} on data set ${JSON.stringify(
76
+ data
77
+ )} cannot run while a dev mock user is active (auth.dev.mockUser or LOWDEFY_DEV_USER): every request would act as the mock user, who is not in the data set. Remove the mock user to run on data sets.`,
78
+ };
79
+ }
80
+ if (user === 'none' && authConfigured) {
81
+ return {
82
+ error: `The ${subject} on data set ${JSON.stringify(
83
+ data
84
+ )} has user "none", which is refused while auth is configured: signing in through the app would write to the app's real auth database. Name a data set user instead; runs that sign in stay off data sets.`,
85
+ };
86
+ }
87
+ let dataSet;
88
+ try {
89
+ dataSet = await readDataSet({ configDirectory, buildDirectory, name: data });
90
+ } catch (error) {
91
+ return { error: error.message };
92
+ }
93
+ if (!isUserName(user)) {
94
+ return { dataSet, user };
95
+ }
96
+ if (!Object.prototype.hasOwnProperty.call(dataSet.users, user)) {
97
+ return {
98
+ error: `Data set "${data}" declares no user ${JSON.stringify(user)}. ${describeDeclaredUsers(
99
+ dataSet
100
+ )}`,
101
+ };
102
+ }
103
+ return { dataSet, user: dataSet.users[user] };
104
+ }
105
+
106
+ export default resolveJourneyDataSet;
@@ -0,0 +1,130 @@
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 resolveJourneyDataSet from './resolveJourneyDataSet.js';
22
+
23
+ let configDirectory;
24
+ let buildDirectory;
25
+
26
+ beforeEach(() => {
27
+ configDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-resolve-journey-data-set-'));
28
+ buildDirectory = path.join(configDirectory, '.lowdefy', 'dev', 'build');
29
+ fs.mkdirSync(path.join(configDirectory, 'tests', 'data'), { recursive: true });
30
+ fs.mkdirSync(path.join(buildDirectory, 'connections'), { recursive: true });
31
+ fs.writeFileSync(
32
+ path.join(buildDirectory, 'connections', 'tickets.json'),
33
+ JSON.stringify({
34
+ connectionId: 'tickets',
35
+ type: 'MongoDBCollection',
36
+ properties: { databaseUri: { _secret: 'MONGODB_URI' }, collection: 'tickets' },
37
+ })
38
+ );
39
+ fs.writeFileSync(
40
+ path.join(configDirectory, 'tests', 'data', 'empty-org.yaml'),
41
+ `fixtures:
42
+ tickets: [{ _id: t1, organizationId: org_a }]
43
+ users:
44
+ owner: { id: u_1, roles: [admin], organizationId: org_a }
45
+ member: { id: u_2, roles: [member], organizationId: org_a }
46
+ `
47
+ );
48
+ });
49
+
50
+ afterEach(() => {
51
+ fs.rmSync(configDirectory, { recursive: true, force: true });
52
+ });
53
+
54
+ function resolve(overrides) {
55
+ return resolveJourneyDataSet({
56
+ configDirectory,
57
+ buildDirectory,
58
+ authConfigured: true,
59
+ mockUserActive: false,
60
+ ...overrides,
61
+ });
62
+ }
63
+
64
+ test('resolveJourneyDataSet resolves a named user from the data set', async () => {
65
+ const { dataSet, user, error } = await resolve({ data: 'empty-org', user: 'member' });
66
+ expect(error).toBeUndefined();
67
+ expect(user).toEqual({ id: 'u_2', roles: ['member'], organizationId: 'org_a' });
68
+ expect(dataSet.name).toEqual('empty-org');
69
+ expect(dataSet.collections).toEqual({ tickets: 'tickets' });
70
+ });
71
+
72
+ test('resolveJourneyDataSet keeps an inline user on a data set journey', async () => {
73
+ const { user, dataSet } = await resolve({ data: 'empty-org', user: { id: 'x', roles: [] } });
74
+ expect(user).toEqual({ id: 'x', roles: [] });
75
+ expect(dataSet.users.owner.id).toEqual('u_1');
76
+ });
77
+
78
+ test('resolveJourneyDataSet refuses a user name the data set does not declare', async () => {
79
+ const { error } = await resolve({ data: 'empty-org', user: 'outsider' });
80
+ expect(error).toEqual(
81
+ 'Data set "empty-org" declares no user "outsider". Declared: owner, member.'
82
+ );
83
+ });
84
+
85
+ test('resolveJourneyDataSet refuses a user name on a journey with no data', async () => {
86
+ const { error } = await resolve({ user: 'member' });
87
+ expect(error).toMatch(
88
+ 'The journey\'s user "member" names a data set user, but the journey has no "data".'
89
+ );
90
+ });
91
+
92
+ test('resolveJourneyDataSet passes a journey with no data and no named user through untouched', async () => {
93
+ expect(await resolve({ user: 'none' })).toEqual({ user: 'none' });
94
+ expect(await resolve({ user: { roles: ['admin'] } })).toEqual({ user: { roles: ['admin'] } });
95
+ });
96
+
97
+ test('resolveJourneyDataSet refuses user none on a data set while auth is configured', async () => {
98
+ const { error } = await resolve({ data: 'empty-org', user: 'none' });
99
+ expect(error).toMatch('has user "none", which is refused while auth is configured');
100
+ });
101
+
102
+ test('resolveJourneyDataSet allows user none on a data set when auth is not configured', async () => {
103
+ const { error, user } = await resolve({ data: 'empty-org', user: 'none', authConfigured: false });
104
+ expect(error).toBeUndefined();
105
+ expect(user).toEqual('none');
106
+ });
107
+
108
+ test.each([['member'], [{ id: 'x', roles: ['admin'] }], [undefined]])(
109
+ 'resolveJourneyDataSet refuses a data set journey with user %j while a dev mock user is active',
110
+ async (user) => {
111
+ const { error } = await resolve({ data: 'empty-org', user, mockUserActive: true });
112
+ expect(error).toMatch('cannot run while a dev mock user is active');
113
+ }
114
+ );
115
+
116
+ test('resolveJourneyDataSet returns readDataSet errors as its own', async () => {
117
+ const { error } = await resolve({ data: 'missing', user: 'member' });
118
+ expect(error).toEqual('Data set "missing" not found in tests/data. Declared: empty-org.');
119
+ });
120
+
121
+ test('resolveJourneyDataSet refuses user none while a dev mock user is active', async () => {
122
+ const { error } = await resolve({ user: 'none', mockUserActive: true });
123
+ expect(error).toMatch(
124
+ 'The journey has user "none", which cannot run while a dev mock user is active'
125
+ );
126
+ });
127
+
128
+ test('resolveJourneyDataSet keeps user none when no dev mock user is active', async () => {
129
+ expect(await resolve({ user: 'none' })).toEqual({ user: 'none' });
130
+ });
@@ -0,0 +1,33 @@
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 dataSessionRegistry from './dataSessionRegistry.js';
18
+
19
+ // Drops every session database on the store that no registered session owns: one left behind by a
20
+ // write that was still in flight when its session dropped the database, or by a dev server that
21
+ // stopped mid-run.
22
+ async function sweepOrphanDatabases({ client }) {
23
+ const owned = new Set([...dataSessionRegistry.values()].map((session) => session.databaseName));
24
+ const { databases } = await client.db('admin').admin().listDatabases({ nameOnly: true });
25
+ await Promise.all(
26
+ databases
27
+ .map((database) => database.name)
28
+ .filter((name) => name.startsWith('ld_') && !owned.has(name))
29
+ .map((name) => client.db(name).dropDatabase())
30
+ );
31
+ }
32
+
33
+ export default sweepOrphanDatabases;