@lowdefy/server-dev 6.1.0 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (624) hide show
  1. package/client/App.jsx +52 -24
  2. package/client/Inspector.jsx +73 -30
  3. package/client/JourneyObserver.jsx +47 -0
  4. package/client/Page.jsx +54 -22
  5. package/client/Recorder.jsx +73 -0
  6. package/client/Routing.jsx +37 -13
  7. package/client/clientBuildImports.test.mjs +2 -0
  8. package/client/devStreamConnections.test.mjs +40 -0
  9. package/client/feedback/FeedbackMount.jsx +2 -2
  10. package/client/feedback/FeedbackOverlay.jsx +7 -4
  11. package/client/feedback/feedbackStyles.js +3 -0
  12. package/client/journeyObserver/startJourneyObserver.js +101 -0
  13. package/client/journeyObserver/startJourneyObserver.test.mjs +149 -0
  14. package/client/openInEditor/OpenInEditorListener.jsx +2 -0
  15. package/client/recorder/buildEngineRecord.js +37 -0
  16. package/client/recorder/buildInteractionRecord.js +64 -0
  17. package/client/recorder/buildTraceEvent.js +57 -0
  18. package/client/recorder/buildTraceEvent.test.mjs +81 -0
  19. package/client/recorder/chunkRecords.js +69 -0
  20. package/client/recorder/chunkRecords.test.mjs +59 -0
  21. package/client/recorder/collectEventActions.js +30 -0
  22. package/client/recorder/collectEventEndpoints.js +40 -0
  23. package/client/recorder/collectEventRequests.js +39 -0
  24. package/client/recorder/createPairingBuffer.js +187 -0
  25. package/client/recorder/createPairingBuffer.test.mjs +405 -0
  26. package/client/recorder/createPasswordRedactor.js +84 -0
  27. package/client/recorder/createRecordBatcher.js +54 -0
  28. package/client/recorder/createRecordBatcher.test.mjs +76 -0
  29. package/client/recorder/createRecorder.js +216 -0
  30. package/client/recorder/createRecorder.test.mjs +318 -0
  31. package/client/recorder/createRecordingSession.js +61 -0
  32. package/client/recorder/createRecordingSession.test.mjs +72 -0
  33. package/client/recorder/diffStateWrites.js +77 -0
  34. package/client/recorder/diffStateWrites.test.mjs +66 -0
  35. package/client/recorder/isEventCall.js +30 -0
  36. package/client/recorder/jsonType.js +39 -0
  37. package/client/recorder/keyChord.js +47 -0
  38. package/client/recorder/keyChord.test.mjs +50 -0
  39. package/client/recorder/sendRecords.js +40 -0
  40. package/client/recorder/toJsonSafe.js +29 -0
  41. package/lib/client/BuildingPage.jsx +3 -1
  42. package/lib/client/ErrorBar.jsx +17 -13
  43. package/lib/client/RedirectingPage.jsx +56 -0
  44. package/lib/client/auth/Auth.jsx +11 -6
  45. package/lib/client/auth/AuthConfigured.jsx +222 -36
  46. package/lib/client/auth/AuthNotConfigured.js +45 -4
  47. package/lib/client/auth/authNotConfiguredParity.test.mjs +61 -0
  48. package/lib/client/auth/authTemplateSync.test.mjs +40 -0
  49. package/lib/client/auth/betterAuthSessionContract.test.mjs +181 -0
  50. package/lib/client/auth/createFetchCheckingBuild.js +45 -0
  51. package/lib/client/bundleBuildId.js +25 -0
  52. package/lib/client/collectVisibleBlockIds.js +51 -0
  53. package/lib/client/getCspNonce.js +25 -0
  54. package/lib/client/getPagePath.js +36 -0
  55. package/lib/client/getPagePath.test.mjs +51 -0
  56. package/lib/client/utils/formatErrorsForCopy.js +30 -0
  57. package/lib/client/utils/formatErrorsForCopy.test.mjs +39 -0
  58. package/lib/client/utils/getErrorBarColor.js +25 -0
  59. package/lib/client/utils/getErrorBarColor.test.mjs +34 -0
  60. package/lib/client/utils/request.js +8 -1
  61. package/lib/client/utils/usePageConfig.js +89 -25
  62. package/lib/client/utils/usePageConfig.test.mjs +198 -13
  63. package/lib/configTree/buildConfigTree.mjs +58 -0
  64. package/lib/configTree/buildConfigTree.test.mjs +77 -0
  65. package/lib/configTree/collectConfigText.mjs +101 -0
  66. package/lib/configTree/collectConfigText.test.mjs +116 -0
  67. package/lib/configTree/prepareScratchServer.mjs +46 -0
  68. package/lib/configTree/runConfigTreeBuild.mjs +88 -0
  69. package/lib/configTree/runConfigTreeBuild.skipPluginChecks.test.mjs +74 -0
  70. package/lib/configTree/runConfigTreeBuild.test.mjs +202 -0
  71. package/lib/docs/JourneyStepError.js +30 -0
  72. package/lib/docs/acquireBrowserSlot.js +30 -0
  73. package/lib/docs/addNestedEndpoints.js +54 -0
  74. package/lib/docs/addNestedEndpoints.test.mjs +185 -0
  75. package/lib/docs/addWriteFlags.js +77 -0
  76. package/lib/docs/buildEditedPages.js +50 -0
  77. package/lib/docs/buildWaitHeader.js +21 -0
  78. package/lib/docs/captureAnnotatedScreenshot.js +23 -12
  79. package/lib/docs/checkAgentFiles.js +148 -0
  80. package/lib/docs/checkAgentFiles.test.mjs +225 -0
  81. package/lib/docs/checkStepReferences.js +239 -0
  82. package/lib/docs/checkStepReferences.test.mjs +173 -0
  83. package/lib/docs/checkpointStore.js +3 -0
  84. package/lib/docs/clientErrorStore.js +18 -1
  85. package/lib/docs/clientErrorStore.test.mjs +46 -0
  86. package/lib/docs/collectExercised.js +52 -0
  87. package/lib/docs/createBrowserLifecycle.js +102 -0
  88. package/lib/docs/createBrowserLifecycle.test.mjs +212 -0
  89. package/lib/docs/createDocsMcpServer.js +345 -388
  90. package/lib/docs/createDocsMcpServer.test.mjs +644 -4
  91. package/lib/docs/createDocsMcpServerRunJourney.test.mjs +148 -0
  92. package/lib/docs/createJourneyActors.chromium.test.mjs +227 -0
  93. package/lib/docs/createJourneyActors.js +213 -0
  94. package/lib/docs/createJourneyActors.test.mjs +79 -0
  95. package/lib/docs/createJourneyObservations.js +80 -0
  96. package/lib/docs/createLeftOriginError.js +26 -0
  97. package/lib/docs/createNetworkCounter.js +101 -0
  98. package/lib/docs/dataSets/checkSharedCollectionIds.js +78 -0
  99. package/lib/docs/dataSets/dataSessionRegistry.js +25 -0
  100. package/lib/docs/dataSets/dataSetJourneys.chromium.mongodb.test.mjs +727 -0
  101. package/lib/docs/dataSets/describeDataSetResult.js +25 -0
  102. package/lib/docs/dataSets/describeDatabaseTarget.js +46 -0
  103. package/lib/docs/dataSets/describeTargetKey.js +29 -0
  104. package/lib/docs/dataSets/deserializeFixture.js +34 -0
  105. package/lib/docs/dataSets/drainSessionWork.js +37 -0
  106. package/lib/docs/dataSets/drainSessionWork.test.mjs +64 -0
  107. package/lib/docs/dataSets/duplicateIndexName.js +24 -0
  108. package/lib/docs/dataSets/getDataStore.js +106 -0
  109. package/lib/docs/dataSets/getDataStore.mongodb.test.mjs +63 -0
  110. package/lib/docs/dataSets/getDataStore.test.mjs +195 -0
  111. package/lib/docs/dataSets/groupDataSetByCollection.js +52 -0
  112. package/lib/docs/dataSets/insertFixture.js +57 -0
  113. package/lib/docs/dataSets/insertGeneratedDocuments.js +41 -0
  114. package/lib/docs/dataSets/isDataStoreGone.js +29 -0
  115. package/lib/docs/dataSets/loadDataSetCollection.js +42 -0
  116. package/lib/docs/dataSets/mergeCollectionIndexes.js +52 -0
  117. package/lib/docs/dataSets/mergeCollectionIndexes.test.mjs +59 -0
  118. package/lib/docs/dataSets/openDataSession.js +133 -0
  119. package/lib/docs/dataSets/openDataSession.mongodb.test.mjs +493 -0
  120. package/lib/docs/dataSets/pickDataStorePort.js +58 -0
  121. package/lib/docs/dataSets/pickDataStorePort.test.mjs +63 -0
  122. package/lib/docs/dataSets/readConnectionArtifacts.js +46 -0
  123. package/lib/docs/dataSets/readDataSession.js +41 -0
  124. package/lib/docs/dataSets/readDataSession.test.mjs +57 -0
  125. package/lib/docs/dataSets/readDataSet.js +83 -0
  126. package/lib/docs/dataSets/readDataSet.test.mjs +251 -0
  127. package/lib/docs/dataSets/registerDataStoreShutdown.js +47 -0
  128. package/lib/docs/dataSets/removeDataStoreFiles.js +32 -0
  129. package/lib/docs/dataSets/resolveDataSetCollection.js +46 -0
  130. package/lib/docs/dataSets/resolveJourneyDataSet.js +106 -0
  131. package/lib/docs/dataSets/resolveJourneyDataSet.test.mjs +130 -0
  132. package/lib/docs/dataSets/sessionDatabaseName.js +32 -0
  133. package/lib/docs/dataSets/sweepOrphanDatabases.js +36 -0
  134. package/lib/docs/dataSets/test/app/lowdefy.yaml +225 -0
  135. package/lib/docs/dataSets/test/app/modules/archive/module.lowdefy.yaml +30 -0
  136. package/lib/docs/dataSets/test/app/tests/data/shop.yaml +12 -0
  137. package/lib/docs/dataSets/test/buildTestApp.mjs +43 -0
  138. package/lib/docs/devEventBus.js +136 -0
  139. package/lib/docs/devEventBus.test.mjs +269 -0
  140. package/lib/docs/devToolDefinitions.js +529 -0
  141. package/lib/docs/docSources.js +21 -0
  142. package/lib/docs/docs.test.mjs +171 -15
  143. package/lib/docs/docsIndex.test.mjs +377 -0
  144. package/lib/docs/evalOperator.js +5 -3
  145. package/lib/docs/evalOperatorHeadless.js +84 -12
  146. package/lib/docs/evalOperatorHeadless.test.mjs +7 -1
  147. package/lib/docs/evalOperatorInTab.js +2 -1
  148. package/lib/docs/findConfig.js +95 -49
  149. package/lib/docs/fitResponse.js +81 -0
  150. package/lib/docs/fitResponse.test.mjs +128 -0
  151. package/lib/docs/formatFeedback.js +26 -5
  152. package/lib/docs/formatFeedback.test.mjs +48 -0
  153. package/lib/docs/getAppMap.js +7 -1
  154. package/lib/docs/getBrowser.js +150 -80
  155. package/lib/docs/getBrowser.test.mjs +407 -16
  156. package/lib/docs/getBuildId.js +61 -0
  157. package/lib/docs/getBuildId.test.mjs +84 -0
  158. package/lib/docs/getBuildStatus.js +81 -7
  159. package/lib/docs/getBuildStatus.test.mjs +229 -0
  160. package/lib/docs/getBuildStatusAfterEdits.js +35 -0
  161. package/lib/docs/getBuildStatusAfterEdits.test.mjs +76 -0
  162. package/lib/docs/getCoreDoc.js +9 -1
  163. package/lib/docs/getDoc.js +100 -0
  164. package/lib/docs/getDocsIndex.js +152 -0
  165. package/lib/docs/getHazards.js +115 -0
  166. package/lib/docs/getHazards.test.mjs +175 -0
  167. package/lib/docs/getMailOutboxDirectory.js +26 -0
  168. package/lib/docs/getOverview.js +79 -13
  169. package/lib/docs/getOverviewBasePath.test.mjs +37 -0
  170. package/lib/docs/getPageBuildStatus.js +46 -0
  171. package/lib/docs/getPluginDoc.js +21 -23
  172. package/lib/docs/getSchema.js +33 -1
  173. package/lib/docs/getStaleStatus.js +36 -0
  174. package/lib/docs/getStaleStatus.test.mjs +74 -0
  175. package/lib/docs/guardJourneyOrigin.js +39 -0
  176. package/lib/docs/inspectState.js +5 -3
  177. package/lib/docs/inspectStateFromTab.js +2 -2
  178. package/lib/docs/inspectStateHeadless.js +89 -27
  179. package/lib/docs/inspectStateHeadless.test.mjs +20 -1
  180. package/lib/docs/installHeadlessShell.failure.test.mjs +45 -0
  181. package/lib/docs/installHeadlessShell.js +123 -0
  182. package/lib/docs/installHeadlessShell.test.mjs +70 -0
  183. package/lib/docs/installHeadlessShell.timeout.test.mjs +72 -0
  184. package/lib/docs/isHeadlessShellIncomplete.js +34 -0
  185. package/lib/docs/isHeadlessShellIncomplete.test.mjs +63 -0
  186. package/lib/docs/isPageReady.js +77 -0
  187. package/lib/docs/isPageReady.test.mjs +235 -0
  188. package/lib/docs/isRunErrorEntry.js +25 -0
  189. package/lib/docs/journeyMutantCookie.chromium.test.mjs +150 -0
  190. package/lib/docs/journeyRunRecording.js +35 -0
  191. package/lib/docs/journeyRunRecording.test.mjs +39 -0
  192. package/lib/docs/keepEventStreamAlive.js +68 -0
  193. package/lib/docs/keepEventStreamAlive.test.mjs +77 -0
  194. package/lib/docs/launchBrowser.js +83 -0
  195. package/lib/docs/launchBrowser.test.mjs +175 -0
  196. package/lib/docs/listModuleDocEntries.js +69 -0
  197. package/lib/docs/listPackageDocFiles.js +67 -0
  198. package/lib/docs/listPluginDocEntries.js +111 -0
  199. package/lib/docs/listPlugins.js +9 -58
  200. package/lib/docs/listTypes.js +14 -6
  201. package/lib/docs/loadFrameworkHazards.js +40 -0
  202. package/lib/docs/loadFrameworkHazards.test.mjs +43 -0
  203. package/lib/docs/loadState.js +103 -35
  204. package/lib/docs/makeModuleManifestDoc.js +174 -0
  205. package/lib/docs/matchExercisedPages.js +35 -0
  206. package/lib/docs/matchJourneyRoute.js +69 -0
  207. package/lib/docs/matchJourneyRoute.test.mjs +178 -0
  208. package/lib/docs/mergeNetworkSnapshots.js +49 -0
  209. package/lib/docs/nextJourneySequence.js +30 -0
  210. package/lib/docs/noBrowserError.js +22 -0
  211. package/lib/docs/observe/TRANSIENT_SELECTOR.js +21 -0
  212. package/lib/docs/observe/claimExpectedErrors.js +67 -0
  213. package/lib/docs/observe/createJourneyAppErrors.js +236 -0
  214. package/lib/docs/observe/createJourneyAppErrors.test.mjs +385 -0
  215. package/lib/docs/observe/evaluateInvariants.js +183 -0
  216. package/lib/docs/observe/evaluateInvariants.test.mjs +299 -0
  217. package/lib/docs/observe/findingKey.js +29 -0
  218. package/lib/docs/observe/hasNoEffect.js +31 -0
  219. package/lib/docs/observe/hasNoEffect.test.mjs +37 -0
  220. package/lib/docs/observe/installStepObserver.js +72 -0
  221. package/lib/docs/observe/isAppApiUrl.js +33 -0
  222. package/lib/docs/observe/parseAppApiUrl.js +37 -0
  223. package/lib/docs/observe/readStepObserver.js +42 -0
  224. package/lib/docs/observe/relativeSource.js +29 -0
  225. package/lib/docs/observe/resolveConfigKeySource.js +33 -0
  226. package/lib/docs/observe/takeJourneyEvents.js +36 -0
  227. package/lib/docs/observe/toAppErrorFailure.js +61 -0
  228. package/lib/docs/observe/usesSearchStage.js +45 -0
  229. package/lib/docs/observe/waitForClientErrorReports.js +31 -0
  230. package/lib/docs/observe/watchJourneyContext.js +75 -0
  231. package/lib/docs/observe/withAppErrors.js +31 -0
  232. package/lib/docs/observe/withAppErrors.test.mjs +101 -0
  233. package/lib/docs/openJourney.js +94 -0
  234. package/lib/docs/openJourneyEmail.js +41 -0
  235. package/lib/docs/openJourneyEmail.test.mjs +187 -0
  236. package/lib/docs/pageBlocks.js +47 -0
  237. package/lib/docs/pageBuildStatus.test.mjs +103 -0
  238. package/lib/{client/setPageId.js → docs/parseModuleSource.js} +11 -12
  239. package/lib/docs/rails/authEngineActions.js +53 -0
  240. package/lib/docs/rails/authEngineActions.test.mjs +69 -0
  241. package/lib/docs/rails/authEngineMethods.js +52 -0
  242. package/lib/docs/rails/authEngineMethods.test.mjs +36 -0
  243. package/lib/docs/rails/checkAuthActionsRan.js +66 -0
  244. package/lib/docs/rails/checkAuthActionsRan.test.mjs +149 -0
  245. package/lib/docs/rails/checkClickRails.js +56 -0
  246. package/lib/docs/rails/createDataSetRails.js +82 -0
  247. package/lib/docs/rails/createDataSetRails.test.mjs +172 -0
  248. package/lib/docs/rails/findExternalBlocks.js +147 -0
  249. package/lib/docs/rails/findExternalBlocks.test.mjs +254 -0
  250. package/lib/docs/rails/installAuthActionRecorder.js +56 -0
  251. package/lib/docs/rails/readAuthActionRecorder.js +32 -0
  252. package/lib/docs/rails/readBuiltArtifact.js +30 -0
  253. package/lib/docs/readConnectionPlugins.js +26 -0
  254. package/lib/docs/readDevAuthMode.js +32 -0
  255. package/lib/docs/readDocEntry.js +40 -0
  256. package/lib/docs/readDocTitle.js +26 -0
  257. package/lib/docs/readJourneyEmailMatch.js +57 -0
  258. package/lib/docs/readJourneyEmailMatch.test.mjs +110 -0
  259. package/lib/docs/readJourneySession.js +56 -0
  260. package/lib/docs/readMailOutbox.js +37 -0
  261. package/lib/docs/readPagePath.js +27 -0
  262. package/lib/docs/readPluginPackages.js +99 -0
  263. package/lib/docs/readProxyBuildWait.js +38 -0
  264. package/lib/docs/readRoutineSteps.js +45 -0
  265. package/lib/docs/registerShownTextEngine.js +118 -0
  266. package/lib/docs/requestRestart.js +30 -0
  267. package/lib/docs/requestRestart.test.mjs +49 -0
  268. package/lib/docs/resolvePageInstance.js +46 -0
  269. package/lib/docs/resolvePageInstance.test.mjs +81 -0
  270. package/lib/docs/resolveSource.js +11 -7
  271. package/lib/docs/resolveSource.test.mjs +1 -1
  272. package/lib/docs/resolveToolCaller.js +62 -0
  273. package/lib/docs/reviewPage.js +51 -0
  274. package/lib/docs/reviewPageBuilds.js +56 -0
  275. package/lib/docs/reviewPageBuilds.test.mjs +207 -0
  276. package/lib/docs/runCheck.js +75 -0
  277. package/lib/docs/runCheck.test.mjs +91 -0
  278. package/lib/docs/runCheckChild.js +49 -0
  279. package/lib/docs/runEndpoint.js +185 -0
  280. package/lib/docs/runEndpoint.test.mjs +509 -0
  281. package/lib/docs/runErrorBuffers.js +99 -0
  282. package/lib/docs/runErrorBuffers.test.mjs +95 -0
  283. package/lib/docs/runJourney.js +347 -0
  284. package/lib/docs/runJourney.test.mjs +2383 -0
  285. package/lib/docs/runJourneyAsUsers.js +44 -0
  286. package/lib/docs/runJourneyDataSet.test.mjs +274 -0
  287. package/lib/docs/runJourneySteps.js +1320 -0
  288. package/lib/docs/runRequest.js +100 -59
  289. package/lib/docs/runRequest.test.mjs +190 -0
  290. package/lib/docs/saveAnnotatedScreenshot.js +1 -1
  291. package/lib/docs/scaffoldPage.js +3 -1
  292. package/lib/docs/screenshotPage.js +163 -11
  293. package/lib/docs/screenshotPage.test.mjs +263 -1
  294. package/lib/docs/screenshotPageClip.test.mjs +4 -0
  295. package/lib/docs/searchDocs.js +82 -30
  296. package/lib/docs/searchDocs.test.mjs +100 -0
  297. package/lib/docs/searchIcons.js +90 -0
  298. package/lib/docs/searchIcons.test.mjs +127 -0
  299. package/lib/docs/selectExercised.js +39 -0
  300. package/lib/docs/selectFinalState.js +66 -0
  301. package/lib/docs/selectFinalState.test.mjs +51 -0
  302. package/lib/docs/serverErrorStore.js +54 -0
  303. package/lib/docs/serverErrorStore.test.mjs +50 -0
  304. package/lib/docs/setupTestFixtures.mjs +133 -39
  305. package/lib/docs/shownTextSelector.js +28 -0
  306. package/lib/docs/snapshotState.js +13 -5
  307. package/lib/docs/splitSearchTerms.js +62 -0
  308. package/lib/docs/splitSearchTerms.test.mjs +50 -0
  309. package/lib/docs/stateCheckpoints.test.mjs +77 -0
  310. package/lib/docs/tabAvailable.js +6 -5
  311. package/lib/docs/tabChannel.js +127 -21
  312. package/lib/docs/tabChannel.test.mjs +187 -29
  313. package/lib/docs/unsettledPageNote.js +24 -0
  314. package/lib/docs/validateJourneyMail.js +36 -0
  315. package/lib/docs/validateJourneyTimeout.js +38 -0
  316. package/lib/docs/validateJourneyTimeout.test.mjs +29 -0
  317. package/lib/docs/validateRecordingParam.js +42 -0
  318. package/lib/docs/validateStateSelection.js +33 -0
  319. package/lib/docs/validateStateSelection.test.mjs +33 -0
  320. package/lib/docs/validateViewport.js +51 -0
  321. package/lib/docs/validateViewport.test.mjs +54 -0
  322. package/lib/docs/waitForBuild.js +50 -0
  323. package/lib/docs/waitForBuild.test.mjs +75 -0
  324. package/lib/docs/waitForJourneyEmail.js +77 -0
  325. package/lib/docs/withBrowserSlot.js +45 -0
  326. package/lib/docs/withBrowserSlot.test.mjs +75 -0
  327. package/lib/docs/withDataSession.js +53 -0
  328. package/lib/server/applyDataSetRedirect.js +92 -0
  329. package/lib/server/applyDataSetRedirect.test.mjs +446 -0
  330. package/lib/server/applyDataSetRedirectTenantReach.test.mjs +207 -0
  331. package/lib/server/applyDataSetRedirectWalled.test.mjs +134 -0
  332. package/lib/server/auth/createSystemContext.js +73 -0
  333. package/lib/server/auth/{getAuthConfig.js → getAuth.js} +15 -9
  334. package/lib/server/auth/getHeadlessUser.js +9 -8
  335. package/lib/server/auth/getHeadlessUser.test.mjs +41 -0
  336. package/lib/server/auth/getMockUser.js +18 -14
  337. package/lib/server/auth/{session.js → getStrategies.js} +17 -16
  338. package/lib/server/auth/headlessUser.js +3 -2
  339. package/lib/server/auth/journeyActor.js +35 -0
  340. package/lib/server/auth/resolveHeadlessUser.test.mjs +0 -4
  341. package/lib/server/checkPageRecord.js +44 -0
  342. package/lib/server/contextMapBudget.js +24 -0
  343. package/lib/server/contextMapBudget.test.mjs +98 -0
  344. package/lib/server/continueJitKeys.js +28 -0
  345. package/lib/server/continueJitKeys.test.mjs +43 -0
  346. package/lib/server/createDataSessionEndedError.js +33 -0
  347. package/lib/server/createDataSetRefusal.js +26 -0
  348. package/lib/server/createLowdefyContext.js +168 -41
  349. package/lib/server/createLowdefyContext.test.mjs +331 -8
  350. package/lib/server/getClientAddress.js +51 -0
  351. package/lib/server/getClientAddress.test.mjs +56 -0
  352. package/lib/server/getPageJitEnrichment.test.mjs +29 -11
  353. package/lib/server/hashConfigContent.js +26 -0
  354. package/lib/server/importAppCode.test.mjs +57 -0
  355. package/lib/server/isRoutineStep.js +27 -0
  356. package/lib/server/jitPageBuilder.js +309 -126
  357. package/lib/server/jitPageBuilder.skipPluginChecks.test.mjs +78 -0
  358. package/lib/server/jitPageBuilder.test.mjs +218 -0
  359. package/lib/server/journeyCookies/JOURNEY_COOKIES.js +37 -0
  360. package/lib/server/journeyCookies/forwardJourneyCookies.js +46 -0
  361. package/lib/server/journeyCookies/readJourneyCookie.js +52 -0
  362. package/lib/server/journeyCookies/writeJourneyCookie.js +46 -0
  363. package/lib/server/journeyCookies.js +22 -0
  364. package/lib/server/journeyCookies.test.mjs +145 -0
  365. package/lib/server/loadDynamicJsMap.js +47 -0
  366. package/lib/server/log/createHandleError.js +38 -2
  367. package/lib/server/log/createHandleError.test.mjs +125 -2
  368. package/lib/server/log/createLogger.js +3 -0
  369. package/lib/server/log/createLogger.test.mjs +15 -0
  370. package/lib/server/log/logRequest.js +2 -3
  371. package/lib/server/mutantDataSetWrappers.test.mjs +115 -0
  372. package/lib/server/mutants/applyMutant.js +39 -0
  373. package/lib/server/mutants/artifactPatterns.js +25 -0
  374. package/lib/server/mutants/buildAnchor.js +45 -0
  375. package/lib/server/mutants/collectSharedIds.js +41 -0
  376. package/lib/server/mutants/configPathSegments.js +38 -0
  377. package/lib/server/mutants/createMutantReadConfigFile.js +42 -0
  378. package/lib/server/mutants/describeEvent.js +22 -0
  379. package/lib/server/mutants/enumerateArtifact.js +56 -0
  380. package/lib/server/mutants/findNodeByKey.js +45 -0
  381. package/lib/server/mutants/findNodeByPointer.js +49 -0
  382. package/lib/server/mutants/getArtifactKind.js +32 -0
  383. package/lib/server/mutants/groupMutantCopies.js +56 -0
  384. package/lib/server/mutants/hashArtifact.js +25 -0
  385. package/lib/server/mutants/hashMutantTarget.js +34 -0
  386. package/lib/server/mutants/listMutants.js +122 -0
  387. package/lib/server/mutants/listMutants.test.mjs +502 -0
  388. package/lib/server/mutants/mutantId.js +30 -0
  389. package/lib/server/mutants/mutantRuns.js +57 -0
  390. package/lib/server/mutants/mutantRuns.test.mjs +136 -0
  391. package/lib/server/mutants/operators/dropAction.js +47 -0
  392. package/lib/server/mutants/operators/dropBlock.js +44 -0
  393. package/lib/server/mutants/operators/dropPayload.js +52 -0
  394. package/lib/server/mutants/operators/dropStep.js +44 -0
  395. package/lib/server/mutants/operators/flipVisible.js +53 -0
  396. package/lib/server/mutants/operators/index.js +37 -0
  397. package/lib/server/mutants/operators/retargetLink.js +72 -0
  398. package/lib/server/mutants/operators/skipValidate.js +77 -0
  399. package/lib/server/mutants/operators/swapIf.js +112 -0
  400. package/lib/server/mutants/operators.test.mjs +396 -0
  401. package/lib/server/mutants/positionFreePath.js +38 -0
  402. package/lib/server/mutants/removeFromParent.js +28 -0
  403. package/lib/server/mutants/test/fixtures.mjs +173 -0
  404. package/lib/server/mutants/test/keyArtifact.mjs +63 -0
  405. package/lib/server/mutants/validateMutantParam.js +69 -0
  406. package/lib/server/mutants/validateMutantsBody.js +88 -0
  407. package/lib/server/mutants/walkArtifact.js +179 -0
  408. package/lib/server/pageBuildRecords.js +131 -0
  409. package/lib/server/pageBuildRecords.test.mjs +258 -0
  410. package/lib/server/pageCache.mjs +22 -7
  411. package/lib/server/pageCache.test.mjs +22 -7
  412. package/lib/server/pageRebuilds.test.mjs +543 -0
  413. package/lib/server/pruneJitMaps.js +36 -0
  414. package/lib/server/pruneJitMaps.test.mjs +55 -0
  415. package/lib/server/readInstanceRecord.js +33 -0
  416. package/lib/server/readInstanceRecord.test.mjs +99 -0
  417. package/lib/server/readMergedMaps.js +86 -0
  418. package/lib/server/readMergedMaps.test.mjs +79 -0
  419. package/lib/server/recording/appendRecords.js +33 -0
  420. package/lib/server/recording/errorRecordingStamp.js +34 -0
  421. package/lib/server/recording/errorRecordingStamp.test.mjs +31 -0
  422. package/lib/server/recording/isRecordingEnabled.js +31 -0
  423. package/lib/server/recording/readRecordingCookie.js +71 -0
  424. package/lib/server/recording/readRecordingCookie.test.mjs +73 -0
  425. package/lib/server/recording/recordingCookiePayload.js +33 -0
  426. package/lib/server/recording/servedBuilds.js +46 -0
  427. package/lib/server/recording/servedBuilds.test.mjs +42 -0
  428. package/lib/server/sinkExternalConnections.js +95 -0
  429. package/lib/server/sinkExternalConnections.test.mjs +135 -0
  430. package/lib/server/skipStaleMapWrites.js +62 -0
  431. package/lib/server/skipStaleMapWrites.test.mjs +113 -0
  432. package/lib/server/startIdleGc.js +67 -0
  433. package/lib/server/startIdleGc.test.mjs +138 -0
  434. package/lib/server/testFixtures/importAppCodeTwice.mjs +70 -0
  435. package/lib/server/trackSessionWork.js +30 -0
  436. package/lib/vite/createDevServerExclude.js +40 -0
  437. package/lib/vite/createDevServerExclude.test.mjs +79 -0
  438. package/manager/getContext.mjs +25 -3
  439. package/manager/processes/initialBuild.mjs +17 -1
  440. package/manager/processes/initialBuild.test.mjs +104 -0
  441. package/manager/processes/lowdefyBuild.mjs +31 -7
  442. package/manager/processes/lowdefyBuild.skipPluginChecks.test.mjs +72 -0
  443. package/manager/processes/lowdefyBuild.test.mjs +136 -1
  444. package/manager/processes/optimizeDependencies.mjs +59 -0
  445. package/manager/processes/restartServer.mjs +34 -6
  446. package/manager/processes/restartServer.test.mjs +162 -0
  447. package/manager/processes/shutdownServer.mjs +11 -0
  448. package/manager/processes/shutdownServer.test.mjs +45 -0
  449. package/manager/processes/startFirstServer.mjs +46 -0
  450. package/manager/processes/startFirstServer.test.mjs +326 -0
  451. package/manager/processes/startMailSink.mjs +135 -0
  452. package/manager/processes/startMailSink.test.mjs +146 -0
  453. package/manager/processes/startProxy.mjs +129 -12
  454. package/manager/processes/startProxy.test.mjs +366 -0
  455. package/manager/processes/startServer.mjs +47 -6
  456. package/manager/processes/startServer.test.mjs +155 -0
  457. package/manager/processes/startWatchers.mjs +6 -4
  458. package/manager/processes/syncServer.mjs +69 -0
  459. package/manager/processes/syncServer.test.mjs +141 -0
  460. package/manager/processes/waitForDevServer.mjs +48 -0
  461. package/manager/processes/waitForDevServer.test.mjs +112 -0
  462. package/manager/run.mjs +128 -43
  463. package/manager/utils/BatchChanges.mjs +21 -5
  464. package/manager/utils/BatchChanges.test.mjs +77 -0
  465. package/manager/utils/acquireDevInstance.mjs +99 -0
  466. package/manager/utils/acquireDevInstance.test.mjs +160 -0
  467. package/manager/utils/createBuildActivity.mjs +67 -0
  468. package/manager/utils/createBuildActivity.test.mjs +58 -0
  469. package/manager/utils/createChangeSignal.mjs +39 -0
  470. package/manager/utils/createChangeSignal.test.mjs +62 -0
  471. package/manager/utils/createCustomPluginMessagesMap.mjs +23 -33
  472. package/manager/utils/createCustomPluginTypesMap.mjs +24 -30
  473. package/manager/utils/createCustomPluginTypesMap.test.mjs +94 -0
  474. package/manager/utils/createDotPathIgnore.mjs +44 -0
  475. package/manager/utils/createDotPathIgnore.test.mjs +65 -0
  476. package/manager/utils/createIdleGc.mjs +51 -0
  477. package/manager/utils/createIdleGc.test.mjs +77 -0
  478. package/manager/utils/createRequestActivity.mjs +100 -0
  479. package/manager/utils/createRequestActivity.test.mjs +121 -0
  480. package/manager/utils/createServerArtifactTracker.mjs +83 -0
  481. package/manager/utils/createServerArtifactTracker.test.mjs +78 -0
  482. package/manager/utils/createServerEnv.mjs +39 -0
  483. package/manager/utils/filesHaveSameBytes.mjs +41 -0
  484. package/manager/utils/findBuildFilesOutsideWatch.mjs +59 -0
  485. package/manager/utils/findBuildFilesOutsideWatch.test.mjs +111 -0
  486. package/manager/utils/importFresh.mjs +46 -0
  487. package/manager/utils/importFresh.test.mjs +72 -0
  488. package/manager/utils/importFreshWorker.mjs +20 -0
  489. package/manager/utils/killTaggedBrowser.mjs +35 -0
  490. package/manager/utils/pagePathChanged.mjs +56 -0
  491. package/manager/utils/pruneRecordings.mjs +85 -0
  492. package/manager/utils/pruneRecordings.test.mjs +117 -0
  493. package/manager/utils/publishBuildDirectory.mjs +121 -0
  494. package/manager/utils/publishBuildDirectory.order.test.mjs +74 -0
  495. package/manager/utils/publishBuildDirectory.test.mjs +262 -0
  496. package/manager/utils/readBasePath.mjs +27 -0
  497. package/manager/utils/readBasePath.test.mjs +43 -0
  498. package/manager/utils/readBuildStatusWait.mjs +67 -0
  499. package/manager/utils/readManagerVersion.mjs +30 -0
  500. package/manager/utils/readManagerVersion.test.mjs +35 -0
  501. package/manager/utils/readPluginDefinitions.mjs +36 -0
  502. package/manager/utils/resolveDevAuthUrl.mjs +58 -0
  503. package/manager/utils/resolveDevAuthUrl.test.mjs +107 -0
  504. package/manager/utils/resolvePorts.mjs +49 -0
  505. package/manager/utils/setupWatcher.mjs +19 -11
  506. package/manager/utils/startRecordingPruner.mjs +45 -0
  507. package/manager/utils/startRecordingPruner.test.mjs +66 -0
  508. package/manager/utils/waitForChildExit.mjs +53 -0
  509. package/manager/utils/waitForChildExit.test.mjs +70 -0
  510. package/manager/utils/waitForServer.mjs +44 -0
  511. package/manager/utils/writeBuildStatus.mjs +1 -1
  512. package/manager/watchers/createPluginSourceIgnore.mjs +77 -0
  513. package/manager/watchers/envWatcher.mjs +9 -2
  514. package/manager/watchers/envWatcher.test.mjs +87 -0
  515. package/manager/watchers/listServerSidePackages.mjs +47 -0
  516. package/manager/watchers/listServerSidePackages.test.mjs +38 -0
  517. package/manager/watchers/lowdefyBuildWatcher.mjs +106 -22
  518. package/manager/watchers/lowdefyBuildWatcher.test.mjs +309 -0
  519. package/manager/watchers/pluginSourceWatcher.mjs +79 -0
  520. package/manager/watchers/pluginSourceWatcher.test.mjs +222 -0
  521. package/manager/watchers/restartRequestWatcher.mjs +71 -0
  522. package/manager/watchers/restartRequestWatcher.test.mjs +111 -0
  523. package/manager/watchers/selectLocalPluginPackages.mjs +48 -0
  524. package/manager/watchers/selectLocalPluginPackages.test.mjs +73 -0
  525. package/manager/watchers/serverPackageWatcher.mjs +38 -0
  526. package/package.json +60 -47
  527. package/package.original.json +60 -47
  528. package/src/app.js +131 -20
  529. package/src/html/renderDevPage.js +63 -11
  530. package/src/html/renderDevPage.test.mjs +138 -0
  531. package/src/lib/getRequestPath.js +29 -0
  532. package/src/middleware/createSameOriginGuard.js +37 -0
  533. package/src/middleware/createSameOriginGuard.test.mjs +102 -0
  534. package/src/middleware/errorHandler.js +51 -1
  535. package/src/middleware/errorHandler.test.mjs +173 -3
  536. package/src/middleware/isRebindingSafeHost.js +44 -0
  537. package/src/middleware/isRebindingSafeHost.test.mjs +32 -0
  538. package/src/middleware/localDevToolsOnly.js +38 -0
  539. package/src/middleware/localDevToolsOnly.test.mjs +61 -0
  540. package/src/middleware/noStore.js +27 -0
  541. package/src/middleware/noStore.test.mjs +48 -0
  542. package/src/middleware/requestTimeout.js +38 -0
  543. package/src/middleware/staleFlag.js +76 -0
  544. package/src/middleware/staleFlag.test.mjs +127 -0
  545. package/src/middleware/stampCaller.js +38 -0
  546. package/src/middleware/stampCaller.test.mjs +64 -0
  547. package/src/routes/agent.js +15 -3
  548. package/src/routes/auth.js +29 -20
  549. package/src/routes/auth.test.mjs +30 -56
  550. package/src/routes/clientError.js +13 -13
  551. package/src/routes/cron.js +3 -1
  552. package/src/routes/cronForward.js +3 -1
  553. package/src/routes/detached.js +22 -9
  554. package/src/routes/devInspect.js +17 -8
  555. package/src/routes/devInspect.test.mjs +44 -7
  556. package/src/routes/devRecording.js +113 -0
  557. package/src/routes/devRecording.test.mjs +253 -0
  558. package/src/routes/docs/buildStatus.js +10 -2
  559. package/src/routes/docs/buildStatus.test.mjs +46 -0
  560. package/src/routes/docs/content.js +5 -3
  561. package/src/routes/docs/evalOperator.js +15 -1
  562. package/src/routes/docs/events.js +50 -0
  563. package/src/routes/docs/events.test.mjs +117 -0
  564. package/src/routes/docs/find.js +9 -1
  565. package/src/routes/docs/icons.js +30 -0
  566. package/src/routes/docs/inspectState.js +16 -1
  567. package/src/routes/docs/journey.js +194 -0
  568. package/src/routes/docs/journey.test.mjs +401 -0
  569. package/src/routes/docs/journeyRefusals.test.mjs +71 -0
  570. package/src/routes/docs/loadState.js +4 -2
  571. package/src/routes/docs/mcp.js +34 -3
  572. package/src/routes/docs/mcp.test.mjs +130 -0
  573. package/src/routes/docs/mutants.js +78 -0
  574. package/src/routes/docs/mutants.test.mjs +180 -0
  575. package/src/routes/docs/parsePathParamsParam.js +41 -0
  576. package/src/routes/docs/parsePathParamsParam.test.mjs +42 -0
  577. package/src/routes/docs/parseUserParam.js +27 -21
  578. package/src/routes/docs/parseUserParam.test.mjs +17 -5
  579. package/src/routes/docs/restart.js +35 -0
  580. package/src/routes/docs/restart.test.mjs +64 -0
  581. package/src/routes/docs/runEndpoint.js +54 -0
  582. package/src/routes/docs/runEndpoint.test.mjs +165 -0
  583. package/src/routes/docs/runRequest.js +25 -8
  584. package/src/routes/docs/runRequest.test.mjs +92 -0
  585. package/src/routes/docs/screenshot.js +50 -2
  586. package/src/routes/docs/screenshot.test.mjs +117 -0
  587. package/src/routes/docs/search.js +15 -2
  588. package/src/routes/docs/snapshotState.js +17 -3
  589. package/src/routes/endpoints.js +27 -26
  590. package/src/routes/endpointsTextAnswer.test.mjs +77 -0
  591. package/src/routes/feedback.js +10 -13
  592. package/src/routes/jitPage.js +99 -35
  593. package/src/routes/jitPage.test.mjs +248 -0
  594. package/src/routes/mcp.js +82 -5
  595. package/src/routes/mcp.test.mjs +215 -0
  596. package/src/routes/mcpMemberToken.test.mjs +125 -0
  597. package/src/routes/mcpProtectedResourceMetadata.js +34 -0
  598. package/src/routes/mcpProtectedResourceMetadata.test.mjs +65 -0
  599. package/src/routes/mountDevPageRoutes.js +25 -0
  600. package/src/routes/mountDevPageRoutes.test.mjs +30 -0
  601. package/src/routes/mountOauthDiscovery.js +44 -0
  602. package/src/routes/mountOauthDiscovery.test.mjs +79 -0
  603. package/src/routes/normalizeMcpProtocolVersionHeader.js +46 -0
  604. package/src/routes/normalizeMcpProtocolVersionHeader.test.mjs +62 -0
  605. package/src/routes/reload.js +16 -0
  606. package/src/routes/request.js +6 -4
  607. package/src/routes/request.test.mjs +9 -5
  608. package/src/routes/requestBody.test.mjs +159 -0
  609. package/src/routes/usage.js +6 -2
  610. package/src/routes/user.js +27 -0
  611. package/src/routes/websocket.js +29 -8
  612. package/src/routes/websocket.test.mjs +65 -0
  613. package/src/routes/wellKnownFallback.js +30 -0
  614. package/src/routes/wellKnownFallback.test.mjs +32 -0
  615. package/src/websocket/devWebSocket.js +41 -26
  616. package/src/websocket/devWebSocket.test.mjs +157 -0
  617. package/vite.config.js +53 -23
  618. package/lib/server/auth/getDevSession.js +0 -70
  619. package/lib/server/auth/getDevSession.test.mjs +0 -138
  620. package/manager/processes/warnAuthUrlPortMismatch.mjs +0 -47
  621. package/manager/processes/warnAuthUrlPortMismatch.test.mjs +0 -93
  622. package/manager/watchers/moduleBuildWatcher.mjs +0 -79
  623. package/manager/watchers/serverArtifactWatcher.mjs +0 -120
  624. package/src/routes/apiPage.js +0 -37
@@ -0,0 +1,396 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { serializer } from '@lowdefy/helpers';
18
+
19
+ import applyMutant from './applyMutant.js';
20
+ import enumerateArtifact from './enumerateArtifact.js';
21
+ import findNodeByKey from './findNodeByKey.js';
22
+ import operators from './operators/index.js';
23
+ import { endpointFixture, eventsFixture, pageFixture, requestFixture } from './test/fixtures.mjs';
24
+
25
+ const PAGE = 'pages/tickets.json';
26
+ const EVENTS = 'events.json';
27
+ const REQUEST = 'pages/tickets/requests/assign_ticket.json';
28
+ const ENDPOINT = 'api/notify.json';
29
+
30
+ function describes({ artifact, root, operator }) {
31
+ return enumerateArtifact({ artifact, root, operators: [operators[operator]] }).map(
32
+ (mutant) => mutant.describe
33
+ );
34
+ }
35
+
36
+ function mutate({ artifact, root, operator, describe }) {
37
+ const mutant = enumerateArtifact({ artifact, root, operators: [operators[operator]] }).find(
38
+ (candidate) => candidate.describe === describe
39
+ );
40
+ const copy = serializer.copy(root);
41
+ const result = applyMutant({ root: copy, mutant });
42
+ return { copy, result, mutant };
43
+ }
44
+
45
+ test('drop-action enumerates every action of every page and app event', () => {
46
+ const { root } = pageFixture();
47
+ expect(describes({ artifact: PAGE, root, operator: 'drop-action' })).toEqual([
48
+ 'drop-action SetState "init_state" (1 of 1) from tickets.onInit',
49
+ 'drop-action Validate "validate" (1 of 7) from assign_submit.onClick',
50
+ 'drop-action Request "assign" (2 of 7) from assign_submit.onClick',
51
+ 'drop-action SetState "notify" (3 of 7) from assign_submit.onClick',
52
+ 'drop-action Link "go" (4 of 7) from assign_submit.onClick',
53
+ 'drop-action Link "home_link" (5 of 7) from assign_submit.onClick',
54
+ 'drop-action Link "out" (6 of 7) from assign_submit.onClick',
55
+ 'drop-action Link "back" (7 of 7) from assign_submit.onClick',
56
+ ]);
57
+ expect(
58
+ describes({ artifact: EVENTS, root: eventsFixture().root, operator: 'drop-action' })
59
+ ).toEqual([
60
+ 'drop-action Link "to_login" (1 of 3) from app.onInit',
61
+ 'drop-action Validate "check" (2 of 3) from app.onInit',
62
+ 'drop-action Throw "fail" (3 of 3) from app.onInit',
63
+ ]);
64
+ });
65
+
66
+ test('drop-action anchors on the page, block, event and action', () => {
67
+ const { root } = pageFixture();
68
+ const [, , assign] = enumerateArtifact({
69
+ artifact: PAGE,
70
+ root,
71
+ operators: [operators['drop-action']],
72
+ });
73
+ expect(assign.anchor).toEqual({
74
+ type: 'action',
75
+ pageId: 'tickets',
76
+ blockId: 'assign_submit',
77
+ eventName: 'onClick',
78
+ actionId: 'assign',
79
+ });
80
+ const [appAction] = enumerateArtifact({
81
+ artifact: EVENTS,
82
+ root: eventsFixture().root,
83
+ operators: [operators['drop-action']],
84
+ });
85
+ expect(appAction.anchor).toEqual({
86
+ type: 'action',
87
+ pageId: 'app',
88
+ blockId: null,
89
+ eventName: 'onInit',
90
+ actionId: 'to_login',
91
+ });
92
+ });
93
+
94
+ test('drop-action removes the action from its event', () => {
95
+ const { copy, result } = mutate({
96
+ artifact: PAGE,
97
+ root: pageFixture().root,
98
+ operator: 'drop-action',
99
+ describe: 'drop-action Request "assign" (2 of 7) from assign_submit.onClick',
100
+ });
101
+ expect(result).toEqual({ applied: true });
102
+ expect(copy.slots.content.blocks[2].events.onClick.try.map((action) => action.id)).toEqual([
103
+ 'validate',
104
+ 'notify',
105
+ 'go',
106
+ 'home_link',
107
+ 'out',
108
+ 'back',
109
+ ]);
110
+ });
111
+
112
+ test('skip-validate enumerates Validate actions, required and validate rules on pages, not in app events', () => {
113
+ expect(
114
+ describes({ artifact: PAGE, root: pageFixture().root, operator: 'skip-validate' })
115
+ ).toEqual([
116
+ 'skip-validate required on name',
117
+ 'skip-validate validate rule 1 of 1 on name',
118
+ 'skip-validate Validate "validate" from assign_submit.onClick',
119
+ ]);
120
+ expect(
121
+ describes({ artifact: EVENTS, root: eventsFixture().root, operator: 'skip-validate' })
122
+ ).toEqual([]);
123
+ });
124
+
125
+ test('skip-validate removes required, a validate rule, or a Validate action', () => {
126
+ const root = pageFixture().root;
127
+ const required = mutate({
128
+ artifact: PAGE,
129
+ root,
130
+ operator: 'skip-validate',
131
+ describe: 'skip-validate required on name',
132
+ });
133
+ expect(required.result).toEqual({ applied: true });
134
+ expect(required.copy.slots.content.blocks[1].required).toBeUndefined();
135
+ const rule = mutate({
136
+ artifact: PAGE,
137
+ root,
138
+ operator: 'skip-validate',
139
+ describe: 'skip-validate validate rule 1 of 1 on name',
140
+ });
141
+ expect(rule.copy.slots.content.blocks[1].validate).toEqual([]);
142
+ expect(rule.mutant.anchor).toEqual({
143
+ type: 'block',
144
+ pageId: 'tickets',
145
+ blockId: 'name',
146
+ parentBlockId: 'tickets',
147
+ });
148
+ const action = mutate({
149
+ artifact: PAGE,
150
+ root,
151
+ operator: 'skip-validate',
152
+ describe: 'skip-validate Validate "validate" from assign_submit.onClick',
153
+ });
154
+ expect(action.copy.slots.content.blocks[2].events.onClick.try[0].id).toEqual('assign');
155
+ });
156
+
157
+ test('flip-visible enumerates visible and properties.disabled and skips blocks declaring neither', () => {
158
+ expect(describes({ artifact: PAGE, root: pageFixture().root, operator: 'flip-visible' })).toEqual(
159
+ [
160
+ 'flip-visible properties.disabled on name',
161
+ 'flip-visible visible on assign_submit',
162
+ 'flip-visible visible on box',
163
+ ]
164
+ );
165
+ });
166
+
167
+ test('flip-visible negates a literal boolean and wraps anything else in _not', () => {
168
+ const root = pageFixture().root;
169
+ const literal = mutate({
170
+ artifact: PAGE,
171
+ root,
172
+ operator: 'flip-visible',
173
+ describe: 'flip-visible visible on assign_submit',
174
+ });
175
+ expect(literal.copy.slots.content.blocks[2].visible).toBe(false);
176
+ const computed = mutate({
177
+ artifact: PAGE,
178
+ root,
179
+ operator: 'flip-visible',
180
+ describe: 'flip-visible properties.disabled on name',
181
+ });
182
+ expect(computed.copy.slots.content.blocks[1].properties.disabled).toEqual({
183
+ _not: { _state: 'locked' },
184
+ });
185
+ });
186
+
187
+ test('swap-if enumerates _if with an else in block properties, action params and request payload only', () => {
188
+ expect(describes({ artifact: PAGE, root: pageFixture().root, operator: 'swap-if' })).toEqual([
189
+ 'swap-if _if in title.properties',
190
+ 'swap-if _if in request "assign_ticket" payload',
191
+ ]);
192
+ expect(describes({ artifact: EVENTS, root: eventsFixture().root, operator: 'swap-if' })).toEqual([
193
+ 'swap-if _if in action "fail" params',
194
+ ]);
195
+ expect(
196
+ describes({ artifact: REQUEST, root: requestFixture().root, operator: 'swap-if' })
197
+ ).toEqual(['swap-if _if in request "assign_ticket" properties']);
198
+ expect(
199
+ describes({ artifact: ENDPOINT, root: endpointFixture().root, operator: 'swap-if' })
200
+ ).toEqual(['swap-if _if in endpoint "notify"', 'swap-if :if in endpoint "notify"']);
201
+ });
202
+
203
+ test('swap-if swaps then and else of an _if, and :then and :else of a routine :if', () => {
204
+ const page = mutate({
205
+ artifact: PAGE,
206
+ root: pageFixture().root,
207
+ operator: 'swap-if',
208
+ describe: 'swap-if _if in title.properties',
209
+ });
210
+ expect(page.copy.slots.content.blocks[0].properties.content._if).toEqual({
211
+ test: { _state: 'open' },
212
+ then: 'Closed',
213
+ else: 'Open',
214
+ });
215
+ const routine = mutate({
216
+ artifact: ENDPOINT,
217
+ root: endpointFixture().root,
218
+ operator: 'swap-if',
219
+ describe: 'swap-if :if in endpoint "notify"',
220
+ });
221
+ expect(routine.copy.routine[1][':then'][0].stepId).toEqual('log');
222
+ expect(routine.copy.routine[1][':else'][0].stepId).toEqual('page');
223
+ expect(routine.mutant.anchor).toEqual({ type: 'endpoint', endpointId: 'notify' });
224
+ });
225
+
226
+ test('drop-payload enumerates each payload key of a page request and removes one', () => {
227
+ const root = pageFixture().root;
228
+ expect(describes({ artifact: PAGE, root, operator: 'drop-payload' })).toEqual([
229
+ 'drop-payload "ticket_id" from request "assign_ticket"',
230
+ 'drop-payload "assignee" from request "assign_ticket"',
231
+ 'drop-payload "priority" from request "assign_ticket"',
232
+ ]);
233
+ expect(
234
+ describes({ artifact: REQUEST, root: requestFixture().root, operator: 'drop-payload' })
235
+ ).toEqual([]);
236
+ const { copy, mutant } = mutate({
237
+ artifact: PAGE,
238
+ root,
239
+ operator: 'drop-payload',
240
+ describe: 'drop-payload "assignee" from request "assign_ticket"',
241
+ });
242
+ expect(Object.keys(copy.requests[0].payload)).toEqual(['ticket_id', 'priority']);
243
+ expect(mutant.anchor).toEqual({ type: 'request', pageId: 'tickets', requestId: 'assign_ticket' });
244
+ });
245
+
246
+ test('retarget-link enumerates Links to a page and skips Links by url, back or home', () => {
247
+ expect(
248
+ describes({ artifact: PAGE, root: pageFixture().root, operator: 'retarget-link' })
249
+ ).toEqual([
250
+ 'retarget-link Link "go" from assign_submit.onClick to 404',
251
+ 'retarget-link Link "home_link" from assign_submit.onClick to 404',
252
+ ]);
253
+ expect(
254
+ describes({ artifact: EVENTS, root: eventsFixture().root, operator: 'retarget-link' })
255
+ ).toEqual(['retarget-link Link "to_login" from app.onInit to 404']);
256
+ });
257
+
258
+ test('retarget-link points params.pageId or string params at 404', () => {
259
+ const root = pageFixture().root;
260
+ const object = mutate({
261
+ artifact: PAGE,
262
+ root,
263
+ operator: 'retarget-link',
264
+ describe: 'retarget-link Link "go" from assign_submit.onClick to 404',
265
+ });
266
+ expect(object.copy.slots.content.blocks[2].events.onClick.try[3].params).toEqual({
267
+ pageId: '404',
268
+ urlQuery: { id: 1 },
269
+ });
270
+ const string = mutate({
271
+ artifact: PAGE,
272
+ root,
273
+ operator: 'retarget-link',
274
+ describe: 'retarget-link Link "home_link" from assign_submit.onClick to 404',
275
+ });
276
+ expect(string.copy.slots.content.blocks[2].events.onClick.try[4].params).toEqual('404');
277
+ });
278
+
279
+ test('drop-block enumerates every block but the page and removes one from its slot', () => {
280
+ const root = pageFixture().root;
281
+ expect(describes({ artifact: PAGE, root, operator: 'drop-block' })).toEqual([
282
+ 'drop-block Title "title" from tickets',
283
+ 'drop-block TextInput "name" from tickets',
284
+ 'drop-block Button "assign_submit" from tickets',
285
+ 'drop-block Box "box" from tickets',
286
+ 'drop-block Alert "success" from box',
287
+ ]);
288
+ const { copy, mutant } = mutate({
289
+ artifact: PAGE,
290
+ root,
291
+ operator: 'drop-block',
292
+ describe: 'drop-block Alert "success" from box',
293
+ });
294
+ expect(copy.slots.content.blocks[3].slots.content.blocks).toEqual([]);
295
+ expect(mutant.anchor).toEqual({
296
+ type: 'block',
297
+ pageId: 'tickets',
298
+ blockId: 'success',
299
+ parentBlockId: 'box',
300
+ });
301
+ });
302
+
303
+ test('drop-step enumerates routine steps at any depth, never values shaped like steps', () => {
304
+ const root = endpointFixture().root;
305
+ expect(describes({ artifact: ENDPOINT, root, operator: 'drop-step' })).toEqual([
306
+ 'drop-step MongoDBFindOne "find" from endpoint "notify"',
307
+ 'drop-step AxiosHttp "page" from endpoint "notify"',
308
+ 'drop-step MongoDBInsertOne "log" from endpoint "notify"',
309
+ ]);
310
+ const { copy } = mutate({
311
+ artifact: ENDPOINT,
312
+ root,
313
+ operator: 'drop-step',
314
+ describe: 'drop-step AxiosHttp "page" from endpoint "notify"',
315
+ });
316
+ expect(copy.routine[1][':then']).toEqual([]);
317
+ });
318
+
319
+ test('operators enumerate nothing from request config in a page artifact other than payload', () => {
320
+ const mutants = enumerateArtifact({
321
+ artifact: PAGE,
322
+ root: pageFixture().root,
323
+ operators: Object.values(operators),
324
+ });
325
+ const requestMutants = mutants.filter((mutant) => mutant.anchor.type === 'request');
326
+ expect(requestMutants.map((mutant) => mutant.operator)).toEqual([
327
+ 'drop-payload',
328
+ 'drop-payload',
329
+ 'drop-payload',
330
+ 'swap-if',
331
+ ]);
332
+ expect(
333
+ mutants.some(
334
+ (mutant) => mutant.describe.includes('properties') && mutant.anchor.type === 'request'
335
+ )
336
+ ).toBe(false);
337
+ });
338
+
339
+ test('operators leave out a target the build made without a ~k', () => {
340
+ const { root } = pageFixture();
341
+ const mutants = enumerateArtifact({ artifact: PAGE, root, operators: Object.values(operators) });
342
+ expect(mutants.every((mutant) => typeof mutant.key === 'string')).toBe(true);
343
+ });
344
+
345
+ test('findNodeByKey finds the non-enumerable ~k on objects and on arrays', () => {
346
+ const { root } = pageFixture();
347
+ const blocks = root.slots.content.blocks;
348
+ expect(Object.keys(blocks[0])).not.toContain('~k');
349
+ expect(findNodeByKey({ root, key: blocks[1]['~k'] })).toEqual({
350
+ node: blocks[1],
351
+ parent: blocks,
352
+ keyInParent: 1,
353
+ });
354
+ expect(findNodeByKey({ root, key: blocks['~k'] })).toEqual({
355
+ node: blocks,
356
+ parent: root.slots.content,
357
+ keyInParent: 'blocks',
358
+ });
359
+ expect(findNodeByKey({ root, key: 'missing' })).toBeNull();
360
+ });
361
+
362
+ test('applyMutant reports a missing key and a missing arg as not applied and changes nothing', () => {
363
+ const { root } = pageFixture();
364
+ const copy = serializer.copy(root);
365
+ expect(
366
+ applyMutant({ root: copy, mutant: { operator: 'drop-block', key: 'missing', arg: null } })
367
+ ).toEqual({
368
+ applied: false,
369
+ reason: 'key not found',
370
+ });
371
+ const name = copy.slots.content.blocks[1];
372
+ expect(
373
+ applyMutant({ root: copy, mutant: { operator: 'flip-visible', key: name['~k'] } })
374
+ ).toEqual({
375
+ applied: false,
376
+ reason: 'flip-visible needs arg visible or properties.disabled',
377
+ });
378
+ const payload = copy.requests[0].payload;
379
+ expect(
380
+ applyMutant({ root: copy, mutant: { operator: 'drop-payload', key: payload['~k'] } })
381
+ ).toEqual({
382
+ applied: false,
383
+ reason: 'drop-payload needs the payload key as arg',
384
+ });
385
+ expect(JSON.stringify(copy)).toEqual(JSON.stringify(root));
386
+ });
387
+
388
+ test('applyMutant refuses an unknown operator', () => {
389
+ const { root } = pageFixture();
390
+ expect(applyMutant({ root, mutant: { operator: 'delete-everything', key: root['~k'] } })).toEqual(
391
+ {
392
+ applied: false,
393
+ reason: 'unknown operator "delete-everything"',
394
+ }
395
+ );
396
+ });
@@ -0,0 +1,38 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import configPathSegments from './configPathSegments.js';
18
+
19
+ // A config path without the index of every array item that carries an id, so
20
+ // inserting a block above a node leaves its path unchanged:
21
+ // `blocks[3:assign_submit:Button]` becomes `blocks[assign_submit:Button]`.
22
+ // Items with no id, and siblings that share one (sharedIds), keep their index.
23
+ function positionFreePath({ configPath, sharedIds = new Set() }) {
24
+ let result = '';
25
+ let cursor = 0;
26
+ configPathSegments(configPath).forEach(({ start, end, prefix, label }) => {
27
+ result += configPath.slice(cursor, start);
28
+ if (label === '' || sharedIds.has(`${prefix}[${label}]`)) {
29
+ result += configPath.slice(start, end);
30
+ } else {
31
+ result += `[${label}]`;
32
+ }
33
+ cursor = end;
34
+ });
35
+ return result + configPath.slice(cursor);
36
+ }
37
+
38
+ export default positionFreePath;
@@ -0,0 +1,28 @@
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
+ // Removes a node from the array that holds it, in place.
20
+ function removeFromParent({ parent, keyInParent }) {
21
+ if (!type.isArray(parent) || !type.isInt(keyInParent)) {
22
+ return { applied: false, reason: 'target is not an array item' };
23
+ }
24
+ parent.splice(keyInParent, 1);
25
+ return { applied: true };
26
+ }
27
+
28
+ export default removeFromParent;
@@ -0,0 +1,173 @@
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 keyArtifact from './keyArtifact.mjs';
18
+
19
+ // A page artifact as the dev server reads build/pages/tickets.json: blocks in
20
+ // slots, actions in events try/catch, and the JIT copy of each request with
21
+ // its properties still in it. Event and slot containers are made by the
22
+ // build, so they carry no ~k.
23
+ function pageFixture({ prefix = 'k1_' } = {}) {
24
+ const onInit = {
25
+ try: [{ id: 'init_state', type: 'SetState', params: { tab: 'open' } }],
26
+ catch: [],
27
+ };
28
+ const onClick = {
29
+ try: [
30
+ { id: 'validate', type: 'Validate', params: { regex: '^name' } },
31
+ { id: 'assign', type: 'Request', params: 'assign_ticket' },
32
+ { id: 'notify', type: 'SetState', params: { label: { _if: { test: true, then: 'x' } } } },
33
+ { id: 'go', type: 'Link', params: { pageId: 'ticket', urlQuery: { id: 1 } } },
34
+ { id: 'home_link', type: 'Link', params: 'home' },
35
+ { id: 'out', type: 'Link', params: { url: 'https://example.com' } },
36
+ { id: 'back', type: 'Link', params: { back: true } },
37
+ ],
38
+ catch: [],
39
+ };
40
+ const boxSlot = { blocks: [{ id: 'block:tickets:success', blockId: 'success', type: 'Alert' }] };
41
+ const boxSlots = { content: boxSlot };
42
+ const content = {
43
+ blocks: [
44
+ {
45
+ id: 'block:tickets:title',
46
+ blockId: 'title',
47
+ type: 'Title',
48
+ properties: {
49
+ content: { _if: { test: { _state: 'open' }, then: 'Open', else: 'Closed' } },
50
+ },
51
+ },
52
+ {
53
+ id: 'block:tickets:name',
54
+ blockId: 'name',
55
+ type: 'TextInput',
56
+ required: true,
57
+ validate: [{ pass: { _regex: { pattern: '^.{3,}$' } }, message: 'Too short' }],
58
+ properties: { disabled: { _state: 'locked' } },
59
+ },
60
+ {
61
+ id: 'block:tickets:assign_submit',
62
+ blockId: 'assign_submit',
63
+ type: 'Button',
64
+ visible: true,
65
+ events: { onClick },
66
+ },
67
+ {
68
+ id: 'block:tickets:box',
69
+ blockId: 'box',
70
+ type: 'Box',
71
+ visible: { _state: 'show' },
72
+ slots: boxSlots,
73
+ },
74
+ ],
75
+ };
76
+ const slots = { content };
77
+ const events = { onInit };
78
+ const value = {
79
+ id: 'page:tickets',
80
+ type: 'PageHeaderMenu',
81
+ pageId: 'tickets',
82
+ blockId: 'tickets',
83
+ events,
84
+ slots,
85
+ requests: [
86
+ {
87
+ id: 'request:tickets:assign_ticket',
88
+ requestId: 'assign_ticket',
89
+ pageId: 'tickets',
90
+ type: 'MongoDBUpdateOne',
91
+ connectionId: 'tickets',
92
+ payload: {
93
+ ticket_id: { _state: 'ticket_id' },
94
+ assignee: { _state: 'assignee' },
95
+ priority: { _if: { test: { _state: 'urgent' }, then: 'high', else: 'normal' } },
96
+ },
97
+ properties: { filter: { _if: { test: true, then: { a: 1 }, else: { a: 2 } } } },
98
+ auth: { public: true },
99
+ },
100
+ ],
101
+ };
102
+ return keyArtifact({
103
+ value,
104
+ prefix,
105
+ unkeyed: [events, onInit, onClick, slots, content, boxSlots, boxSlot],
106
+ });
107
+ }
108
+
109
+ function eventsFixture() {
110
+ return keyArtifact({
111
+ value: {
112
+ onInit: {
113
+ try: [
114
+ { id: 'to_login', type: 'Link', params: { pageId: 'login' } },
115
+ { id: 'check', type: 'Validate' },
116
+ {
117
+ id: 'fail',
118
+ type: 'Throw',
119
+ params: { message: { _if: { test: { _user: 'id' }, then: 'a', else: 'b' } } },
120
+ },
121
+ ],
122
+ catch: [],
123
+ },
124
+ },
125
+ });
126
+ }
127
+
128
+ function requestFixture() {
129
+ return keyArtifact({
130
+ value: {
131
+ id: 'request:tickets:assign_ticket',
132
+ requestId: 'assign_ticket',
133
+ pageId: 'tickets',
134
+ type: 'MongoDBUpdateOne',
135
+ connectionId: 'tickets',
136
+ payload: { ticket_id: { _state: 'ticket_id' } },
137
+ properties: {
138
+ filter: { _id: { _payload: 'ticket_id' } },
139
+ update: { $set: { status: { _if: { test: true, then: 'assigned', else: 'open' } } } },
140
+ },
141
+ },
142
+ });
143
+ }
144
+
145
+ function endpointFixture() {
146
+ return keyArtifact({
147
+ value: {
148
+ id: 'endpoint:notify',
149
+ endpointId: 'notify',
150
+ type: 'Api',
151
+ routine: [
152
+ {
153
+ id: 'endpoint:notify:find',
154
+ stepId: 'find',
155
+ type: 'MongoDBFindOne',
156
+ properties: {
157
+ query: { _if: { test: { _payload: 'all' }, then: {}, else: { open: true } } },
158
+ doc: { id: 'not-a-step', type: 'value' },
159
+ },
160
+ },
161
+ {
162
+ ':if': { _payload: 'urgent' },
163
+ ':then': [{ id: 'endpoint:notify:page', stepId: 'page', type: 'AxiosHttp' }],
164
+ ':else': [{ id: 'endpoint:notify:log', stepId: 'log', type: 'MongoDBInsertOne' }],
165
+ },
166
+ { ':if': true, ':then': [] },
167
+ { ':return': { ok: true } },
168
+ ],
169
+ },
170
+ });
171
+ }
172
+
173
+ export { endpointFixture, eventsFixture, pageFixture, requestFixture };
@@ -0,0 +1,63 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { serializer, type } from '@lowdefy/helpers';
18
+
19
+ // Test fixtures for config mutants: gives every object and array of a plain
20
+ // artifact a ~k the way the build's addKeys does (a prefix and a base-36
21
+ // counter, the config path in keyMap with ids and types in array segments),
22
+ // then round-trips it through the serializer as the dev server reads a build
23
+ // artifact, so ~k comes back non-enumerable. Objects listed in `unkeyed` (by
24
+ // identity) stay without a ~k, like objects the build makes itself.
25
+ function keyArtifact({ value, prefix = 'k1_', unkeyed = [], keyMap = {}, refs = {} }) {
26
+ let counter = 0;
27
+ function nextKey() {
28
+ counter += 1;
29
+ return `${prefix}${counter.toString(36)}`;
30
+ }
31
+ function itemPath(arrayPath, index, item) {
32
+ let segment = `${index}`;
33
+ const id = item.blockId ?? item.requestId ?? item.id;
34
+ if (id) segment = `${segment}:${id}`;
35
+ if (item.type) segment = `${segment}:${item.type}`;
36
+ return `${arrayPath}[${segment}]`;
37
+ }
38
+ function mark(node, path) {
39
+ if (unkeyed.includes(node)) {
40
+ return;
41
+ }
42
+ const key = nextKey();
43
+ keyMap[key] = { key: path, ...(refs[path] ?? {}) };
44
+ Object.defineProperty(node, '~k', { value: key, enumerable: false, configurable: true });
45
+ }
46
+ function walk(node, path) {
47
+ if (type.isArray(node)) {
48
+ mark(node, path);
49
+ node.forEach((item, index) => {
50
+ if (type.isObject(item)) walk(item, itemPath(path, index, item));
51
+ else if (type.isArray(item)) walk(item, `${path}[${index}]`);
52
+ });
53
+ return;
54
+ }
55
+ if (!type.isObject(node)) return;
56
+ mark(node, path);
57
+ Object.keys(node).forEach((key) => walk(node[key], `${path}.${key}`));
58
+ }
59
+ walk(value, 'root');
60
+ return { root: serializer.deserialize(serializer.serialize(value)), keyMap };
61
+ }
62
+
63
+ export default keyArtifact;