@lowdefy/server-dev 6.1.0 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (623) hide show
  1. package/client/App.jsx +52 -24
  2. package/client/Inspector.jsx +73 -30
  3. package/client/JourneyObserver.jsx +47 -0
  4. package/client/Page.jsx +54 -22
  5. package/client/Recorder.jsx +73 -0
  6. package/client/Routing.jsx +37 -13
  7. package/client/clientBuildImports.test.mjs +2 -0
  8. package/client/devStreamConnections.test.mjs +40 -0
  9. package/client/feedback/FeedbackMount.jsx +2 -2
  10. package/client/feedback/FeedbackOverlay.jsx +7 -4
  11. package/client/feedback/feedbackStyles.js +3 -0
  12. package/client/journeyObserver/startJourneyObserver.js +101 -0
  13. package/client/journeyObserver/startJourneyObserver.test.mjs +149 -0
  14. package/client/openInEditor/OpenInEditorListener.jsx +2 -0
  15. package/client/recorder/buildEngineRecord.js +37 -0
  16. package/client/recorder/buildInteractionRecord.js +64 -0
  17. package/client/recorder/buildTraceEvent.js +57 -0
  18. package/client/recorder/buildTraceEvent.test.mjs +81 -0
  19. package/client/recorder/chunkRecords.js +69 -0
  20. package/client/recorder/chunkRecords.test.mjs +59 -0
  21. package/client/recorder/collectEventActions.js +30 -0
  22. package/client/recorder/collectEventEndpoints.js +40 -0
  23. package/client/recorder/collectEventRequests.js +39 -0
  24. package/client/recorder/createPairingBuffer.js +187 -0
  25. package/client/recorder/createPairingBuffer.test.mjs +405 -0
  26. package/client/recorder/createPasswordRedactor.js +84 -0
  27. package/client/recorder/createRecordBatcher.js +54 -0
  28. package/client/recorder/createRecordBatcher.test.mjs +76 -0
  29. package/client/recorder/createRecorder.js +216 -0
  30. package/client/recorder/createRecorder.test.mjs +318 -0
  31. package/client/recorder/createRecordingSession.js +61 -0
  32. package/client/recorder/createRecordingSession.test.mjs +72 -0
  33. package/client/recorder/diffStateWrites.js +77 -0
  34. package/client/recorder/diffStateWrites.test.mjs +66 -0
  35. package/client/recorder/isEventCall.js +30 -0
  36. package/client/recorder/jsonType.js +39 -0
  37. package/client/recorder/keyChord.js +47 -0
  38. package/client/recorder/keyChord.test.mjs +50 -0
  39. package/client/recorder/sendRecords.js +40 -0
  40. package/client/recorder/toJsonSafe.js +29 -0
  41. package/lib/client/BuildingPage.jsx +3 -1
  42. package/lib/client/ErrorBar.jsx +17 -13
  43. package/lib/client/RedirectingPage.jsx +56 -0
  44. package/lib/client/auth/Auth.jsx +11 -6
  45. package/lib/client/auth/AuthConfigured.jsx +222 -36
  46. package/lib/client/auth/AuthNotConfigured.js +45 -4
  47. package/lib/client/auth/authNotConfiguredParity.test.mjs +61 -0
  48. package/lib/client/auth/authTemplateSync.test.mjs +40 -0
  49. package/lib/client/auth/betterAuthSessionContract.test.mjs +181 -0
  50. package/lib/client/auth/createFetchCheckingBuild.js +45 -0
  51. package/lib/client/bundleBuildId.js +25 -0
  52. package/lib/client/collectVisibleBlockIds.js +51 -0
  53. package/lib/client/getCspNonce.js +25 -0
  54. package/lib/client/getPagePath.js +36 -0
  55. package/lib/client/getPagePath.test.mjs +51 -0
  56. package/lib/client/utils/formatErrorsForCopy.js +30 -0
  57. package/lib/client/utils/formatErrorsForCopy.test.mjs +39 -0
  58. package/lib/client/utils/getErrorBarColor.js +25 -0
  59. package/lib/client/utils/getErrorBarColor.test.mjs +34 -0
  60. package/lib/client/utils/request.js +8 -1
  61. package/lib/client/utils/usePageConfig.js +89 -25
  62. package/lib/client/utils/usePageConfig.test.mjs +198 -13
  63. package/lib/configTree/buildConfigTree.mjs +58 -0
  64. package/lib/configTree/buildConfigTree.test.mjs +77 -0
  65. package/lib/configTree/collectConfigText.mjs +101 -0
  66. package/lib/configTree/collectConfigText.test.mjs +116 -0
  67. package/lib/configTree/prepareScratchServer.mjs +46 -0
  68. package/lib/configTree/runConfigTreeBuild.mjs +88 -0
  69. package/lib/configTree/runConfigTreeBuild.skipPluginChecks.test.mjs +74 -0
  70. package/lib/configTree/runConfigTreeBuild.test.mjs +202 -0
  71. package/lib/docs/JourneyStepError.js +30 -0
  72. package/lib/docs/acquireBrowserSlot.js +30 -0
  73. package/lib/docs/addNestedEndpoints.js +54 -0
  74. package/lib/docs/addNestedEndpoints.test.mjs +185 -0
  75. package/lib/docs/addWriteFlags.js +77 -0
  76. package/lib/docs/buildEditedPages.js +50 -0
  77. package/lib/docs/buildWaitHeader.js +21 -0
  78. package/lib/docs/captureAnnotatedScreenshot.js +23 -12
  79. package/lib/docs/checkAgentFiles.js +148 -0
  80. package/lib/docs/checkAgentFiles.test.mjs +225 -0
  81. package/lib/docs/checkStepReferences.js +239 -0
  82. package/lib/docs/checkStepReferences.test.mjs +173 -0
  83. package/lib/docs/checkpointStore.js +3 -0
  84. package/lib/docs/clientErrorStore.js +18 -1
  85. package/lib/docs/clientErrorStore.test.mjs +46 -0
  86. package/lib/docs/collectExercised.js +52 -0
  87. package/lib/docs/createBrowserLifecycle.js +102 -0
  88. package/lib/docs/createBrowserLifecycle.test.mjs +212 -0
  89. package/lib/docs/createDocsMcpServer.js +345 -388
  90. package/lib/docs/createDocsMcpServer.test.mjs +644 -4
  91. package/lib/docs/createDocsMcpServerRunJourney.test.mjs +148 -0
  92. package/lib/docs/createJourneyActors.chromium.test.mjs +227 -0
  93. package/lib/docs/createJourneyActors.js +213 -0
  94. package/lib/docs/createJourneyActors.test.mjs +79 -0
  95. package/lib/docs/createJourneyObservations.js +80 -0
  96. package/lib/docs/createLeftOriginError.js +26 -0
  97. package/lib/docs/createNetworkCounter.js +101 -0
  98. package/lib/docs/dataSets/checkSharedCollectionIds.js +67 -0
  99. package/lib/docs/dataSets/dataSessionRegistry.js +25 -0
  100. package/lib/docs/dataSets/dataSetJourneys.chromium.mongodb.test.mjs +679 -0
  101. package/lib/docs/dataSets/describeDataSetResult.js +25 -0
  102. package/lib/docs/dataSets/describeDatabaseTarget.js +46 -0
  103. package/lib/docs/dataSets/deserializeFixture.js +34 -0
  104. package/lib/docs/dataSets/drainSessionWork.js +37 -0
  105. package/lib/docs/dataSets/drainSessionWork.test.mjs +64 -0
  106. package/lib/docs/dataSets/duplicateIndexName.js +24 -0
  107. package/lib/docs/dataSets/findCollectionCollisions.js +52 -0
  108. package/lib/docs/dataSets/getDataStore.js +106 -0
  109. package/lib/docs/dataSets/getDataStore.mongodb.test.mjs +63 -0
  110. package/lib/docs/dataSets/getDataStore.test.mjs +195 -0
  111. package/lib/docs/dataSets/groupDataSetByCollection.js +44 -0
  112. package/lib/docs/dataSets/insertFixture.js +57 -0
  113. package/lib/docs/dataSets/insertGeneratedDocuments.js +41 -0
  114. package/lib/docs/dataSets/isDataStoreGone.js +29 -0
  115. package/lib/docs/dataSets/loadDataSetCollection.js +42 -0
  116. package/lib/docs/dataSets/mergeCollectionIndexes.js +52 -0
  117. package/lib/docs/dataSets/mergeCollectionIndexes.test.mjs +59 -0
  118. package/lib/docs/dataSets/openDataSession.js +119 -0
  119. package/lib/docs/dataSets/openDataSession.mongodb.test.mjs +343 -0
  120. package/lib/docs/dataSets/pickDataStorePort.js +58 -0
  121. package/lib/docs/dataSets/pickDataStorePort.test.mjs +63 -0
  122. package/lib/docs/dataSets/readConnectionArtifacts.js +46 -0
  123. package/lib/docs/dataSets/readDataSession.js +41 -0
  124. package/lib/docs/dataSets/readDataSession.test.mjs +57 -0
  125. package/lib/docs/dataSets/readDataSet.js +68 -0
  126. package/lib/docs/dataSets/readDataSet.test.mjs +220 -0
  127. package/lib/docs/dataSets/registerDataStoreShutdown.js +47 -0
  128. package/lib/docs/dataSets/removeDataStoreFiles.js +32 -0
  129. package/lib/docs/dataSets/resolveDataSetCollection.js +46 -0
  130. package/lib/docs/dataSets/resolveJourneyDataSet.js +106 -0
  131. package/lib/docs/dataSets/resolveJourneyDataSet.test.mjs +130 -0
  132. package/lib/docs/dataSets/sweepOrphanDatabases.js +33 -0
  133. package/lib/docs/dataSets/test/app/lowdefy.yaml +203 -0
  134. package/lib/docs/dataSets/test/app/modules/archive/module.lowdefy.yaml +30 -0
  135. package/lib/docs/dataSets/test/app/tests/data/shop.yaml +8 -0
  136. package/lib/docs/dataSets/test/buildTestApp.mjs +43 -0
  137. package/lib/docs/devEventBus.js +136 -0
  138. package/lib/docs/devEventBus.test.mjs +269 -0
  139. package/lib/docs/devToolDefinitions.js +529 -0
  140. package/lib/docs/docSources.js +21 -0
  141. package/lib/docs/docs.test.mjs +171 -15
  142. package/lib/docs/docsIndex.test.mjs +377 -0
  143. package/lib/docs/evalOperator.js +5 -3
  144. package/lib/docs/evalOperatorHeadless.js +84 -12
  145. package/lib/docs/evalOperatorHeadless.test.mjs +7 -1
  146. package/lib/docs/evalOperatorInTab.js +2 -1
  147. package/lib/docs/findConfig.js +95 -49
  148. package/lib/docs/fitResponse.js +81 -0
  149. package/lib/docs/fitResponse.test.mjs +128 -0
  150. package/lib/docs/formatFeedback.js +26 -5
  151. package/lib/docs/formatFeedback.test.mjs +48 -0
  152. package/lib/docs/getAppMap.js +7 -1
  153. package/lib/docs/getBrowser.js +150 -80
  154. package/lib/docs/getBrowser.test.mjs +407 -16
  155. package/lib/docs/getBuildId.js +61 -0
  156. package/lib/docs/getBuildId.test.mjs +84 -0
  157. package/lib/docs/getBuildStatus.js +54 -6
  158. package/lib/docs/getBuildStatus.test.mjs +133 -0
  159. package/lib/docs/getBuildStatusAfterEdits.js +35 -0
  160. package/lib/docs/getBuildStatusAfterEdits.test.mjs +76 -0
  161. package/lib/docs/getCoreDoc.js +9 -1
  162. package/lib/docs/getDoc.js +100 -0
  163. package/lib/docs/getDocsIndex.js +152 -0
  164. package/lib/docs/getHazards.js +115 -0
  165. package/lib/docs/getHazards.test.mjs +175 -0
  166. package/lib/docs/getMailOutboxDirectory.js +26 -0
  167. package/lib/docs/getOverview.js +79 -13
  168. package/lib/docs/getOverviewBasePath.test.mjs +37 -0
  169. package/lib/docs/getPageBuildStatus.js +46 -0
  170. package/lib/docs/getPluginDoc.js +21 -23
  171. package/lib/docs/getSchema.js +33 -1
  172. package/lib/docs/getStaleStatus.js +36 -0
  173. package/lib/docs/getStaleStatus.test.mjs +74 -0
  174. package/lib/docs/guardJourneyOrigin.js +39 -0
  175. package/lib/docs/inspectState.js +5 -3
  176. package/lib/docs/inspectStateFromTab.js +2 -2
  177. package/lib/docs/inspectStateHeadless.js +89 -27
  178. package/lib/docs/inspectStateHeadless.test.mjs +20 -1
  179. package/lib/docs/installHeadlessShell.failure.test.mjs +45 -0
  180. package/lib/docs/installHeadlessShell.js +123 -0
  181. package/lib/docs/installHeadlessShell.test.mjs +70 -0
  182. package/lib/docs/installHeadlessShell.timeout.test.mjs +72 -0
  183. package/lib/docs/isHeadlessShellIncomplete.js +34 -0
  184. package/lib/docs/isHeadlessShellIncomplete.test.mjs +63 -0
  185. package/lib/docs/isPageReady.js +77 -0
  186. package/lib/docs/isPageReady.test.mjs +235 -0
  187. package/lib/docs/isRunErrorEntry.js +25 -0
  188. package/lib/docs/journeyMutantCookie.chromium.test.mjs +150 -0
  189. package/lib/docs/journeyRunRecording.js +35 -0
  190. package/lib/docs/journeyRunRecording.test.mjs +39 -0
  191. package/lib/docs/keepEventStreamAlive.js +68 -0
  192. package/lib/docs/keepEventStreamAlive.test.mjs +77 -0
  193. package/lib/docs/launchBrowser.js +83 -0
  194. package/lib/docs/launchBrowser.test.mjs +175 -0
  195. package/lib/docs/listModuleDocEntries.js +69 -0
  196. package/lib/docs/listPackageDocFiles.js +67 -0
  197. package/lib/docs/listPluginDocEntries.js +111 -0
  198. package/lib/docs/listPlugins.js +9 -58
  199. package/lib/docs/listTypes.js +14 -6
  200. package/lib/docs/loadFrameworkHazards.js +40 -0
  201. package/lib/docs/loadFrameworkHazards.test.mjs +43 -0
  202. package/lib/docs/loadState.js +103 -35
  203. package/lib/docs/makeModuleManifestDoc.js +174 -0
  204. package/lib/docs/matchExercisedPages.js +35 -0
  205. package/lib/docs/matchJourneyRoute.js +69 -0
  206. package/lib/docs/matchJourneyRoute.test.mjs +178 -0
  207. package/lib/docs/mergeNetworkSnapshots.js +49 -0
  208. package/lib/docs/nextJourneySequence.js +30 -0
  209. package/lib/docs/noBrowserError.js +22 -0
  210. package/lib/docs/observe/TRANSIENT_SELECTOR.js +21 -0
  211. package/lib/docs/observe/claimExpectedErrors.js +67 -0
  212. package/lib/docs/observe/createJourneyAppErrors.js +236 -0
  213. package/lib/docs/observe/createJourneyAppErrors.test.mjs +385 -0
  214. package/lib/docs/observe/evaluateInvariants.js +183 -0
  215. package/lib/docs/observe/evaluateInvariants.test.mjs +299 -0
  216. package/lib/docs/observe/findingKey.js +29 -0
  217. package/lib/docs/observe/hasNoEffect.js +31 -0
  218. package/lib/docs/observe/hasNoEffect.test.mjs +37 -0
  219. package/lib/docs/observe/installStepObserver.js +72 -0
  220. package/lib/docs/observe/isAppApiUrl.js +33 -0
  221. package/lib/docs/observe/parseAppApiUrl.js +37 -0
  222. package/lib/docs/observe/readStepObserver.js +42 -0
  223. package/lib/docs/observe/relativeSource.js +29 -0
  224. package/lib/docs/observe/resolveConfigKeySource.js +33 -0
  225. package/lib/docs/observe/takeJourneyEvents.js +36 -0
  226. package/lib/docs/observe/toAppErrorFailure.js +61 -0
  227. package/lib/docs/observe/usesSearchStage.js +45 -0
  228. package/lib/docs/observe/waitForClientErrorReports.js +31 -0
  229. package/lib/docs/observe/watchJourneyContext.js +75 -0
  230. package/lib/docs/observe/withAppErrors.js +31 -0
  231. package/lib/docs/observe/withAppErrors.test.mjs +101 -0
  232. package/lib/docs/openJourney.js +94 -0
  233. package/lib/docs/openJourneyEmail.js +41 -0
  234. package/lib/docs/openJourneyEmail.test.mjs +187 -0
  235. package/lib/docs/pageBlocks.js +47 -0
  236. package/lib/docs/pageBuildStatus.test.mjs +103 -0
  237. package/lib/{client/setPageId.js → docs/parseModuleSource.js} +11 -12
  238. package/lib/docs/rails/authEngineActions.js +53 -0
  239. package/lib/docs/rails/authEngineActions.test.mjs +69 -0
  240. package/lib/docs/rails/authEngineMethods.js +52 -0
  241. package/lib/docs/rails/authEngineMethods.test.mjs +36 -0
  242. package/lib/docs/rails/checkAuthActionsRan.js +66 -0
  243. package/lib/docs/rails/checkAuthActionsRan.test.mjs +149 -0
  244. package/lib/docs/rails/checkClickRails.js +56 -0
  245. package/lib/docs/rails/createDataSetRails.js +82 -0
  246. package/lib/docs/rails/createDataSetRails.test.mjs +172 -0
  247. package/lib/docs/rails/findExternalBlocks.js +147 -0
  248. package/lib/docs/rails/findExternalBlocks.test.mjs +254 -0
  249. package/lib/docs/rails/installAuthActionRecorder.js +56 -0
  250. package/lib/docs/rails/readAuthActionRecorder.js +32 -0
  251. package/lib/docs/rails/readBuiltArtifact.js +30 -0
  252. package/lib/docs/readConnectionPlugins.js +26 -0
  253. package/lib/docs/readDevAuthMode.js +32 -0
  254. package/lib/docs/readDocEntry.js +40 -0
  255. package/lib/docs/readDocTitle.js +26 -0
  256. package/lib/docs/readJourneyEmailMatch.js +57 -0
  257. package/lib/docs/readJourneyEmailMatch.test.mjs +110 -0
  258. package/lib/docs/readJourneySession.js +56 -0
  259. package/lib/docs/readMailOutbox.js +37 -0
  260. package/lib/docs/readPagePath.js +27 -0
  261. package/lib/docs/readPluginPackages.js +99 -0
  262. package/lib/docs/readProxyBuildWait.js +38 -0
  263. package/lib/docs/readRoutineSteps.js +45 -0
  264. package/lib/docs/registerShownTextEngine.js +118 -0
  265. package/lib/docs/requestRestart.js +30 -0
  266. package/lib/docs/requestRestart.test.mjs +49 -0
  267. package/lib/docs/resolvePageInstance.js +46 -0
  268. package/lib/docs/resolvePageInstance.test.mjs +81 -0
  269. package/lib/docs/resolveSource.js +11 -7
  270. package/lib/docs/resolveSource.test.mjs +1 -1
  271. package/lib/docs/resolveToolCaller.js +62 -0
  272. package/lib/docs/reviewPage.js +51 -0
  273. package/lib/docs/reviewPageBuilds.js +56 -0
  274. package/lib/docs/reviewPageBuilds.test.mjs +207 -0
  275. package/lib/docs/runCheck.js +75 -0
  276. package/lib/docs/runCheck.test.mjs +91 -0
  277. package/lib/docs/runCheckChild.js +49 -0
  278. package/lib/docs/runEndpoint.js +185 -0
  279. package/lib/docs/runEndpoint.test.mjs +509 -0
  280. package/lib/docs/runErrorBuffers.js +99 -0
  281. package/lib/docs/runErrorBuffers.test.mjs +95 -0
  282. package/lib/docs/runJourney.js +347 -0
  283. package/lib/docs/runJourney.test.mjs +2383 -0
  284. package/lib/docs/runJourneyAsUsers.js +44 -0
  285. package/lib/docs/runJourneyDataSet.test.mjs +274 -0
  286. package/lib/docs/runJourneySteps.js +1320 -0
  287. package/lib/docs/runRequest.js +100 -59
  288. package/lib/docs/runRequest.test.mjs +190 -0
  289. package/lib/docs/saveAnnotatedScreenshot.js +1 -1
  290. package/lib/docs/scaffoldPage.js +3 -1
  291. package/lib/docs/screenshotPage.js +163 -11
  292. package/lib/docs/screenshotPage.test.mjs +263 -1
  293. package/lib/docs/screenshotPageClip.test.mjs +4 -0
  294. package/lib/docs/searchDocs.js +82 -30
  295. package/lib/docs/searchDocs.test.mjs +100 -0
  296. package/lib/docs/searchIcons.js +90 -0
  297. package/lib/docs/searchIcons.test.mjs +127 -0
  298. package/lib/docs/selectExercised.js +39 -0
  299. package/lib/docs/selectFinalState.js +66 -0
  300. package/lib/docs/selectFinalState.test.mjs +51 -0
  301. package/lib/docs/serverErrorStore.js +54 -0
  302. package/lib/docs/serverErrorStore.test.mjs +50 -0
  303. package/lib/docs/setupTestFixtures.mjs +133 -39
  304. package/lib/docs/shownTextSelector.js +28 -0
  305. package/lib/docs/snapshotState.js +13 -5
  306. package/lib/docs/splitSearchTerms.js +62 -0
  307. package/lib/docs/splitSearchTerms.test.mjs +50 -0
  308. package/lib/docs/stateCheckpoints.test.mjs +77 -0
  309. package/lib/docs/tabAvailable.js +6 -5
  310. package/lib/docs/tabChannel.js +127 -21
  311. package/lib/docs/tabChannel.test.mjs +187 -29
  312. package/lib/docs/unsettledPageNote.js +24 -0
  313. package/lib/docs/validateJourneyMail.js +36 -0
  314. package/lib/docs/validateJourneyTimeout.js +38 -0
  315. package/lib/docs/validateJourneyTimeout.test.mjs +29 -0
  316. package/lib/docs/validateRecordingParam.js +42 -0
  317. package/lib/docs/validateStateSelection.js +33 -0
  318. package/lib/docs/validateStateSelection.test.mjs +33 -0
  319. package/lib/docs/validateViewport.js +51 -0
  320. package/lib/docs/validateViewport.test.mjs +54 -0
  321. package/lib/docs/waitForBuild.js +50 -0
  322. package/lib/docs/waitForBuild.test.mjs +75 -0
  323. package/lib/docs/waitForJourneyEmail.js +77 -0
  324. package/lib/docs/withBrowserSlot.js +45 -0
  325. package/lib/docs/withBrowserSlot.test.mjs +75 -0
  326. package/lib/docs/withDataSession.js +53 -0
  327. package/lib/server/applyDataSetRedirect.js +83 -0
  328. package/lib/server/applyDataSetRedirect.test.mjs +340 -0
  329. package/lib/server/applyDataSetRedirectTenantReach.test.mjs +204 -0
  330. package/lib/server/applyDataSetRedirectWalled.test.mjs +131 -0
  331. package/lib/server/auth/createSystemContext.js +73 -0
  332. package/lib/server/auth/{getAuthConfig.js → getAuth.js} +15 -9
  333. package/lib/server/auth/getHeadlessUser.js +9 -8
  334. package/lib/server/auth/getHeadlessUser.test.mjs +41 -0
  335. package/lib/server/auth/getMockUser.js +18 -14
  336. package/lib/server/auth/{session.js → getStrategies.js} +17 -16
  337. package/lib/server/auth/headlessUser.js +3 -2
  338. package/lib/server/auth/journeyActor.js +35 -0
  339. package/lib/server/auth/resolveHeadlessUser.test.mjs +0 -4
  340. package/lib/server/checkPageRecord.js +44 -0
  341. package/lib/server/contextMapBudget.js +24 -0
  342. package/lib/server/contextMapBudget.test.mjs +98 -0
  343. package/lib/server/continueJitKeys.js +28 -0
  344. package/lib/server/continueJitKeys.test.mjs +43 -0
  345. package/lib/server/createDataSessionEndedError.js +33 -0
  346. package/lib/server/createDataSetRefusal.js +26 -0
  347. package/lib/server/createLowdefyContext.js +168 -41
  348. package/lib/server/createLowdefyContext.test.mjs +328 -8
  349. package/lib/server/getClientAddress.js +51 -0
  350. package/lib/server/getClientAddress.test.mjs +56 -0
  351. package/lib/server/getPageJitEnrichment.test.mjs +29 -11
  352. package/lib/server/hashConfigContent.js +26 -0
  353. package/lib/server/importAppCode.test.mjs +57 -0
  354. package/lib/server/isRoutineStep.js +27 -0
  355. package/lib/server/jitPageBuilder.js +309 -126
  356. package/lib/server/jitPageBuilder.skipPluginChecks.test.mjs +78 -0
  357. package/lib/server/jitPageBuilder.test.mjs +218 -0
  358. package/lib/server/journeyCookies/JOURNEY_COOKIES.js +37 -0
  359. package/lib/server/journeyCookies/forwardJourneyCookies.js +46 -0
  360. package/lib/server/journeyCookies/readJourneyCookie.js +52 -0
  361. package/lib/server/journeyCookies/writeJourneyCookie.js +46 -0
  362. package/lib/server/journeyCookies.js +22 -0
  363. package/lib/server/journeyCookies.test.mjs +145 -0
  364. package/lib/server/loadDynamicJsMap.js +47 -0
  365. package/lib/server/log/createHandleError.js +38 -2
  366. package/lib/server/log/createHandleError.test.mjs +125 -2
  367. package/lib/server/log/createLogger.js +3 -0
  368. package/lib/server/log/createLogger.test.mjs +15 -0
  369. package/lib/server/log/logRequest.js +2 -3
  370. package/lib/server/mutantDataSetWrappers.test.mjs +112 -0
  371. package/lib/server/mutants/applyMutant.js +39 -0
  372. package/lib/server/mutants/artifactPatterns.js +25 -0
  373. package/lib/server/mutants/buildAnchor.js +45 -0
  374. package/lib/server/mutants/collectSharedIds.js +41 -0
  375. package/lib/server/mutants/configPathSegments.js +38 -0
  376. package/lib/server/mutants/createMutantReadConfigFile.js +42 -0
  377. package/lib/server/mutants/describeEvent.js +22 -0
  378. package/lib/server/mutants/enumerateArtifact.js +56 -0
  379. package/lib/server/mutants/findNodeByKey.js +45 -0
  380. package/lib/server/mutants/findNodeByPointer.js +49 -0
  381. package/lib/server/mutants/getArtifactKind.js +32 -0
  382. package/lib/server/mutants/groupMutantCopies.js +56 -0
  383. package/lib/server/mutants/hashArtifact.js +25 -0
  384. package/lib/server/mutants/hashMutantTarget.js +34 -0
  385. package/lib/server/mutants/listMutants.js +122 -0
  386. package/lib/server/mutants/listMutants.test.mjs +502 -0
  387. package/lib/server/mutants/mutantId.js +30 -0
  388. package/lib/server/mutants/mutantRuns.js +57 -0
  389. package/lib/server/mutants/mutantRuns.test.mjs +136 -0
  390. package/lib/server/mutants/operators/dropAction.js +47 -0
  391. package/lib/server/mutants/operators/dropBlock.js +44 -0
  392. package/lib/server/mutants/operators/dropPayload.js +52 -0
  393. package/lib/server/mutants/operators/dropStep.js +44 -0
  394. package/lib/server/mutants/operators/flipVisible.js +53 -0
  395. package/lib/server/mutants/operators/index.js +37 -0
  396. package/lib/server/mutants/operators/retargetLink.js +72 -0
  397. package/lib/server/mutants/operators/skipValidate.js +77 -0
  398. package/lib/server/mutants/operators/swapIf.js +112 -0
  399. package/lib/server/mutants/operators.test.mjs +396 -0
  400. package/lib/server/mutants/positionFreePath.js +38 -0
  401. package/lib/server/mutants/removeFromParent.js +28 -0
  402. package/lib/server/mutants/test/fixtures.mjs +173 -0
  403. package/lib/server/mutants/test/keyArtifact.mjs +63 -0
  404. package/lib/server/mutants/validateMutantParam.js +69 -0
  405. package/lib/server/mutants/validateMutantsBody.js +88 -0
  406. package/lib/server/mutants/walkArtifact.js +179 -0
  407. package/lib/server/pageBuildRecords.js +131 -0
  408. package/lib/server/pageBuildRecords.test.mjs +258 -0
  409. package/lib/server/pageCache.mjs +22 -7
  410. package/lib/server/pageCache.test.mjs +22 -7
  411. package/lib/server/pageRebuilds.test.mjs +543 -0
  412. package/lib/server/pruneJitMaps.js +36 -0
  413. package/lib/server/pruneJitMaps.test.mjs +55 -0
  414. package/lib/server/readInstanceRecord.js +33 -0
  415. package/lib/server/readInstanceRecord.test.mjs +99 -0
  416. package/lib/server/readMergedMaps.js +86 -0
  417. package/lib/server/readMergedMaps.test.mjs +79 -0
  418. package/lib/server/recording/appendRecords.js +33 -0
  419. package/lib/server/recording/errorRecordingStamp.js +34 -0
  420. package/lib/server/recording/errorRecordingStamp.test.mjs +31 -0
  421. package/lib/server/recording/isRecordingEnabled.js +31 -0
  422. package/lib/server/recording/readRecordingCookie.js +71 -0
  423. package/lib/server/recording/readRecordingCookie.test.mjs +73 -0
  424. package/lib/server/recording/recordingCookiePayload.js +33 -0
  425. package/lib/server/recording/servedBuilds.js +46 -0
  426. package/lib/server/recording/servedBuilds.test.mjs +42 -0
  427. package/lib/server/sinkExternalConnections.js +95 -0
  428. package/lib/server/sinkExternalConnections.test.mjs +135 -0
  429. package/lib/server/skipStaleMapWrites.js +62 -0
  430. package/lib/server/skipStaleMapWrites.test.mjs +113 -0
  431. package/lib/server/startIdleGc.js +67 -0
  432. package/lib/server/startIdleGc.test.mjs +138 -0
  433. package/lib/server/testFixtures/importAppCodeTwice.mjs +70 -0
  434. package/lib/server/trackSessionWork.js +30 -0
  435. package/lib/vite/createDevServerExclude.js +40 -0
  436. package/lib/vite/createDevServerExclude.test.mjs +79 -0
  437. package/manager/getContext.mjs +24 -2
  438. package/manager/processes/initialBuild.mjs +14 -0
  439. package/manager/processes/initialBuild.test.mjs +101 -0
  440. package/manager/processes/lowdefyBuild.mjs +30 -7
  441. package/manager/processes/lowdefyBuild.skipPluginChecks.test.mjs +62 -0
  442. package/manager/processes/lowdefyBuild.test.mjs +136 -1
  443. package/manager/processes/optimizeDependencies.mjs +59 -0
  444. package/manager/processes/restartServer.mjs +34 -6
  445. package/manager/processes/restartServer.test.mjs +162 -0
  446. package/manager/processes/shutdownServer.mjs +11 -0
  447. package/manager/processes/shutdownServer.test.mjs +45 -0
  448. package/manager/processes/startFirstServer.mjs +46 -0
  449. package/manager/processes/startFirstServer.test.mjs +326 -0
  450. package/manager/processes/startMailSink.mjs +135 -0
  451. package/manager/processes/startMailSink.test.mjs +146 -0
  452. package/manager/processes/startProxy.mjs +129 -12
  453. package/manager/processes/startProxy.test.mjs +366 -0
  454. package/manager/processes/startServer.mjs +47 -6
  455. package/manager/processes/startServer.test.mjs +155 -0
  456. package/manager/processes/startWatchers.mjs +6 -4
  457. package/manager/processes/syncServer.mjs +69 -0
  458. package/manager/processes/syncServer.test.mjs +141 -0
  459. package/manager/processes/waitForDevServer.mjs +48 -0
  460. package/manager/processes/waitForDevServer.test.mjs +112 -0
  461. package/manager/run.mjs +128 -43
  462. package/manager/utils/BatchChanges.mjs +21 -5
  463. package/manager/utils/BatchChanges.test.mjs +77 -0
  464. package/manager/utils/acquireDevInstance.mjs +99 -0
  465. package/manager/utils/acquireDevInstance.test.mjs +160 -0
  466. package/manager/utils/createBuildActivity.mjs +67 -0
  467. package/manager/utils/createBuildActivity.test.mjs +58 -0
  468. package/manager/utils/createChangeSignal.mjs +39 -0
  469. package/manager/utils/createChangeSignal.test.mjs +62 -0
  470. package/manager/utils/createCustomPluginMessagesMap.mjs +23 -33
  471. package/manager/utils/createCustomPluginTypesMap.mjs +24 -30
  472. package/manager/utils/createCustomPluginTypesMap.test.mjs +94 -0
  473. package/manager/utils/createDotPathIgnore.mjs +44 -0
  474. package/manager/utils/createDotPathIgnore.test.mjs +65 -0
  475. package/manager/utils/createIdleGc.mjs +51 -0
  476. package/manager/utils/createIdleGc.test.mjs +77 -0
  477. package/manager/utils/createRequestActivity.mjs +100 -0
  478. package/manager/utils/createRequestActivity.test.mjs +121 -0
  479. package/manager/utils/createServerArtifactTracker.mjs +83 -0
  480. package/manager/utils/createServerArtifactTracker.test.mjs +78 -0
  481. package/manager/utils/createServerEnv.mjs +39 -0
  482. package/manager/utils/filesHaveSameBytes.mjs +41 -0
  483. package/manager/utils/findBuildFilesOutsideWatch.mjs +59 -0
  484. package/manager/utils/findBuildFilesOutsideWatch.test.mjs +111 -0
  485. package/manager/utils/importFresh.mjs +46 -0
  486. package/manager/utils/importFresh.test.mjs +72 -0
  487. package/manager/utils/importFreshWorker.mjs +20 -0
  488. package/manager/utils/killTaggedBrowser.mjs +35 -0
  489. package/manager/utils/pagePathChanged.mjs +56 -0
  490. package/manager/utils/pruneRecordings.mjs +85 -0
  491. package/manager/utils/pruneRecordings.test.mjs +117 -0
  492. package/manager/utils/publishBuildDirectory.mjs +121 -0
  493. package/manager/utils/publishBuildDirectory.order.test.mjs +74 -0
  494. package/manager/utils/publishBuildDirectory.test.mjs +262 -0
  495. package/manager/utils/readBasePath.mjs +27 -0
  496. package/manager/utils/readBasePath.test.mjs +43 -0
  497. package/manager/utils/readBuildStatusWait.mjs +67 -0
  498. package/manager/utils/readManagerVersion.mjs +30 -0
  499. package/manager/utils/readManagerVersion.test.mjs +35 -0
  500. package/manager/utils/readPluginDefinitions.mjs +36 -0
  501. package/manager/utils/resolveDevAuthUrl.mjs +58 -0
  502. package/manager/utils/resolveDevAuthUrl.test.mjs +107 -0
  503. package/manager/utils/resolvePorts.mjs +49 -0
  504. package/manager/utils/setupWatcher.mjs +19 -11
  505. package/manager/utils/startRecordingPruner.mjs +45 -0
  506. package/manager/utils/startRecordingPruner.test.mjs +66 -0
  507. package/manager/utils/waitForChildExit.mjs +53 -0
  508. package/manager/utils/waitForChildExit.test.mjs +70 -0
  509. package/manager/utils/waitForServer.mjs +44 -0
  510. package/manager/utils/writeBuildStatus.mjs +1 -1
  511. package/manager/watchers/createPluginSourceIgnore.mjs +77 -0
  512. package/manager/watchers/envWatcher.mjs +9 -2
  513. package/manager/watchers/envWatcher.test.mjs +87 -0
  514. package/manager/watchers/listServerSidePackages.mjs +47 -0
  515. package/manager/watchers/listServerSidePackages.test.mjs +38 -0
  516. package/manager/watchers/lowdefyBuildWatcher.mjs +106 -22
  517. package/manager/watchers/lowdefyBuildWatcher.test.mjs +309 -0
  518. package/manager/watchers/pluginSourceWatcher.mjs +79 -0
  519. package/manager/watchers/pluginSourceWatcher.test.mjs +222 -0
  520. package/manager/watchers/restartRequestWatcher.mjs +71 -0
  521. package/manager/watchers/restartRequestWatcher.test.mjs +111 -0
  522. package/manager/watchers/selectLocalPluginPackages.mjs +48 -0
  523. package/manager/watchers/selectLocalPluginPackages.test.mjs +73 -0
  524. package/manager/watchers/serverPackageWatcher.mjs +38 -0
  525. package/package.json +60 -47
  526. package/package.original.json +60 -47
  527. package/src/app.js +131 -20
  528. package/src/html/renderDevPage.js +63 -11
  529. package/src/html/renderDevPage.test.mjs +138 -0
  530. package/src/lib/getRequestPath.js +29 -0
  531. package/src/middleware/createSameOriginGuard.js +37 -0
  532. package/src/middleware/createSameOriginGuard.test.mjs +102 -0
  533. package/src/middleware/errorHandler.js +51 -1
  534. package/src/middleware/errorHandler.test.mjs +173 -3
  535. package/src/middleware/isRebindingSafeHost.js +44 -0
  536. package/src/middleware/isRebindingSafeHost.test.mjs +32 -0
  537. package/src/middleware/localDevToolsOnly.js +38 -0
  538. package/src/middleware/localDevToolsOnly.test.mjs +61 -0
  539. package/src/middleware/noStore.js +27 -0
  540. package/src/middleware/noStore.test.mjs +48 -0
  541. package/src/middleware/requestTimeout.js +38 -0
  542. package/src/middleware/staleFlag.js +76 -0
  543. package/src/middleware/staleFlag.test.mjs +127 -0
  544. package/src/middleware/stampCaller.js +38 -0
  545. package/src/middleware/stampCaller.test.mjs +64 -0
  546. package/src/routes/agent.js +15 -3
  547. package/src/routes/auth.js +29 -20
  548. package/src/routes/auth.test.mjs +30 -56
  549. package/src/routes/clientError.js +13 -13
  550. package/src/routes/cron.js +3 -1
  551. package/src/routes/cronForward.js +3 -1
  552. package/src/routes/detached.js +22 -9
  553. package/src/routes/devInspect.js +17 -8
  554. package/src/routes/devInspect.test.mjs +44 -7
  555. package/src/routes/devRecording.js +113 -0
  556. package/src/routes/devRecording.test.mjs +253 -0
  557. package/src/routes/docs/buildStatus.js +10 -2
  558. package/src/routes/docs/buildStatus.test.mjs +46 -0
  559. package/src/routes/docs/content.js +5 -3
  560. package/src/routes/docs/evalOperator.js +15 -1
  561. package/src/routes/docs/events.js +50 -0
  562. package/src/routes/docs/events.test.mjs +117 -0
  563. package/src/routes/docs/find.js +9 -1
  564. package/src/routes/docs/icons.js +30 -0
  565. package/src/routes/docs/inspectState.js +16 -1
  566. package/src/routes/docs/journey.js +194 -0
  567. package/src/routes/docs/journey.test.mjs +401 -0
  568. package/src/routes/docs/journeyRefusals.test.mjs +71 -0
  569. package/src/routes/docs/loadState.js +4 -2
  570. package/src/routes/docs/mcp.js +34 -3
  571. package/src/routes/docs/mcp.test.mjs +130 -0
  572. package/src/routes/docs/mutants.js +78 -0
  573. package/src/routes/docs/mutants.test.mjs +180 -0
  574. package/src/routes/docs/parsePathParamsParam.js +41 -0
  575. package/src/routes/docs/parsePathParamsParam.test.mjs +42 -0
  576. package/src/routes/docs/parseUserParam.js +27 -21
  577. package/src/routes/docs/parseUserParam.test.mjs +17 -5
  578. package/src/routes/docs/restart.js +35 -0
  579. package/src/routes/docs/restart.test.mjs +64 -0
  580. package/src/routes/docs/runEndpoint.js +54 -0
  581. package/src/routes/docs/runEndpoint.test.mjs +165 -0
  582. package/src/routes/docs/runRequest.js +25 -8
  583. package/src/routes/docs/runRequest.test.mjs +92 -0
  584. package/src/routes/docs/screenshot.js +50 -2
  585. package/src/routes/docs/screenshot.test.mjs +117 -0
  586. package/src/routes/docs/search.js +15 -2
  587. package/src/routes/docs/snapshotState.js +17 -3
  588. package/src/routes/endpoints.js +27 -26
  589. package/src/routes/endpointsTextAnswer.test.mjs +77 -0
  590. package/src/routes/feedback.js +10 -13
  591. package/src/routes/jitPage.js +99 -35
  592. package/src/routes/jitPage.test.mjs +248 -0
  593. package/src/routes/mcp.js +82 -5
  594. package/src/routes/mcp.test.mjs +215 -0
  595. package/src/routes/mcpMemberToken.test.mjs +125 -0
  596. package/src/routes/mcpProtectedResourceMetadata.js +34 -0
  597. package/src/routes/mcpProtectedResourceMetadata.test.mjs +65 -0
  598. package/src/routes/mountDevPageRoutes.js +25 -0
  599. package/src/routes/mountDevPageRoutes.test.mjs +30 -0
  600. package/src/routes/mountOauthDiscovery.js +44 -0
  601. package/src/routes/mountOauthDiscovery.test.mjs +79 -0
  602. package/src/routes/normalizeMcpProtocolVersionHeader.js +46 -0
  603. package/src/routes/normalizeMcpProtocolVersionHeader.test.mjs +62 -0
  604. package/src/routes/reload.js +16 -0
  605. package/src/routes/request.js +6 -4
  606. package/src/routes/request.test.mjs +9 -5
  607. package/src/routes/requestBody.test.mjs +159 -0
  608. package/src/routes/usage.js +6 -2
  609. package/src/routes/user.js +27 -0
  610. package/src/routes/websocket.js +29 -8
  611. package/src/routes/websocket.test.mjs +65 -0
  612. package/src/routes/wellKnownFallback.js +30 -0
  613. package/src/routes/wellKnownFallback.test.mjs +32 -0
  614. package/src/websocket/devWebSocket.js +41 -26
  615. package/src/websocket/devWebSocket.test.mjs +157 -0
  616. package/vite.config.js +53 -23
  617. package/lib/server/auth/getDevSession.js +0 -70
  618. package/lib/server/auth/getDevSession.test.mjs +0 -138
  619. package/manager/processes/warnAuthUrlPortMismatch.mjs +0 -47
  620. package/manager/processes/warnAuthUrlPortMismatch.test.mjs +0 -93
  621. package/manager/watchers/moduleBuildWatcher.mjs +0 -79
  622. package/manager/watchers/serverArtifactWatcher.mjs +0 -120
  623. package/src/routes/apiPage.js +0 -37
@@ -0,0 +1,27 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { type } from '@lowdefy/helpers';
18
+
19
+ // A step of a built endpoint routine. The build gives every step a stepId
20
+ // (the id the config wrote; its id becomes the scoped endpoint:<id>:<step>),
21
+ // so a node with a string type and stepId is a step, and a control object
22
+ // (`:if`, `:try`, ...) or a step's own properties never is.
23
+ function isRoutineStep(node) {
24
+ return type.isObject(node) && type.isString(node.type) && type.isString(node.stepId);
25
+ }
26
+
27
+ export default isRoutineStep;
@@ -14,21 +14,31 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
+ import crypto from 'crypto';
17
18
  import fs from 'fs';
18
19
  import path from 'path';
19
20
  import { serializer, type } from '@lowdefy/helpers';
20
21
  import {
21
22
  buildPageJit,
23
+ collectIconNames,
22
24
  createContext,
25
+ createJitMaps,
26
+ createReadConfigFile,
23
27
  generateClientJsModule,
24
28
  hydrateDeferredRecords,
25
- iconPackages,
26
- makeId,
29
+ prepareJitContext,
30
+ restoreTenantTargets,
27
31
  } from '@lowdefy/build/dev';
28
32
 
33
+ import checkPageRecord from './checkPageRecord.js';
34
+ import contextMapBudget from './contextMapBudget.js';
35
+ import continueJitKeys from './continueJitKeys.js';
29
36
  import createLogger from './log/createLogger.js';
37
+ import pageBuildRecords from './pageBuildRecords.js';
30
38
  import PageCache from './pageCache.mjs';
39
+ import pruneJitMaps from './pruneJitMaps.js';
31
40
  import readBuildApiArtifacts from './readBuildApiArtifacts.mjs';
41
+ import skipStaleMapWrites from './skipStaleMapWrites.js';
32
42
 
33
43
  const jitLogger = createLogger({ name: 'jit-build' });
34
44
 
@@ -36,17 +46,32 @@ function formatDuration(ms) {
36
46
  if (ms < 1000) return `${ms}ms`;
37
47
  return `${(ms / 1000).toFixed(2)}s`;
38
48
  }
39
- const pageCache = new PageCache();
40
- let cachedRegistryMtime = null;
41
- let cachedRegistry = null;
42
- let cachedBuildContext = null;
43
- let lastInvalidationMtime = null;
44
49
 
45
- // Frozen snapshot of icon imports from the initial build (what's actually in the client bundle).
46
- // Module-level so it persists across context resets (skeleton rebuilds update iconImports.json
47
- // with newly discovered icons, but those aren't in the bundle until a server restart).
48
- // Only resets when the server process restarts.
49
- let bundledIconImports = null;
50
+ const pageCache = new PageCache();
51
+ let keptContext = null;
52
+ let registry = null;
53
+ let registryIdentity = null;
54
+ let lastChangeSignal = null;
55
+ // Counts the change events (page file edits) this process has seen. A compiled
56
+ // page whose checkedAt is behind it has its inputs checked before it is served.
57
+ let eventCounter = 0;
58
+ let budgetExceeded = false;
59
+
60
+ // Names this process's JIT keys and jitMaps files. A restarted child continues
61
+ // the same config build as the one before it, so its keys need their own
62
+ // prefix, or both would hand out the same keys for different nodes.
63
+ const childId = crypto.randomBytes(3).toString('hex');
64
+ // The generation of the current build context, or of the next one when none is
65
+ // made yet. A page built on an earlier generation is rebuilt, and a context's
66
+ // jitMaps files carry it.
67
+ let contextGeneration = 1;
68
+ let previousContextGeneration = 0;
69
+
70
+ // Frozen snapshot of the icon names in the dev client bundle, from the initial
71
+ // build. Module-level so it persists across context resets: skeleton rebuilds
72
+ // rewrite iconImports.json with newly used names, but those are not in the
73
+ // bundle until the server restarts. JIT delivers the rest as data.
74
+ let bundledIcons = null;
50
75
 
51
76
  function readJsonFile(filePath) {
52
77
  try {
@@ -57,43 +82,29 @@ function readJsonFile(filePath) {
57
82
  }
58
83
  }
59
84
 
60
- function checkPageInvalidations(buildDirectory) {
61
- const invalidatePath = path.join(buildDirectory, 'invalidatePages');
85
+ // The value the manager writes to build/invalidatePages after every batch of
86
+ // watched changes, or null before the first.
87
+ function readChangeSignal(buildDirectory) {
62
88
  try {
63
- const stat = fs.statSync(invalidatePath);
64
- if (lastInvalidationMtime && stat.mtimeMs === lastInvalidationMtime) {
65
- return;
66
- }
67
- lastInvalidationMtime = stat.mtimeMs;
68
- pageCache.invalidateAll();
69
- cachedBuildContext = null;
89
+ return fs.readFileSync(path.join(buildDirectory, 'invalidatePages'), 'utf8');
70
90
  } catch {
71
- // File doesn't exist yet — nothing to invalidate
91
+ return null;
72
92
  }
73
93
  }
74
94
 
75
- function loadPageRegistry(buildDirectory) {
76
- const registryPath = path.join(buildDirectory, 'pageRegistry.json');
95
+ // Every config build publish renames a new pageRegistry.json into place, so
96
+ // its inode and mtime identify the published build, even two publishes in one
97
+ // clock tick.
98
+ function readRegistryIdentity(buildDirectory) {
77
99
  try {
78
- const stat = fs.statSync(registryPath);
79
- // Only reload if file has changed
80
- if (cachedRegistryMtime && stat.mtimeMs === cachedRegistryMtime) {
81
- return cachedRegistry;
82
- }
83
- cachedRegistryMtime = stat.mtimeMs;
84
- cachedRegistry = readJsonFile(registryPath);
85
- // Invalidate all pages when registry changes (skeleton rebuild happened)
86
- pageCache.invalidateAll();
87
- cachedBuildContext = null;
88
- return cachedRegistry;
100
+ const stat = fs.statSync(path.join(buildDirectory, 'pageRegistry.json'), { bigint: true });
101
+ return `${stat.ino}:${stat.mtimeNs}`;
89
102
  } catch {
90
103
  return null;
91
104
  }
92
105
  }
93
106
 
94
- function getBuildContext(buildDirectory, configDirectory) {
95
- if (cachedBuildContext) return cachedBuildContext;
96
-
107
+ function createBuildContext(buildDirectory, configDirectory) {
97
108
  const refMap = readJsonFile(path.join(buildDirectory, 'refMap.json')) ?? {};
98
109
  const keyMap = readJsonFile(path.join(buildDirectory, 'keyMap.json')) ?? {};
99
110
  const jsMap = readJsonFile(path.join(buildDirectory, 'jsMap.json')) ?? { client: {}, server: {} };
@@ -103,7 +114,7 @@ function getBuildContext(buildDirectory, configDirectory) {
103
114
  const customTypesMap = readJsonFile(path.join(buildDirectory, 'customTypesMap.json')) ?? {};
104
115
  const customMessagesMap = readJsonFile(path.join(buildDirectory, 'customMessagesMap.json')) ?? {};
105
116
 
106
- cachedBuildContext = createContext({
117
+ const context = createContext({
107
118
  customMessagesMap,
108
119
  customTypesMap,
109
120
  directories: {
@@ -112,30 +123,46 @@ function getBuildContext(buildDirectory, configDirectory) {
112
123
  server: path.resolve(buildDirectory, '..'),
113
124
  },
114
125
  logger: jitLogger,
126
+ // The manager's shallowBuild() reads the same variable; the page builds
127
+ // run in the server process and build their own context.
128
+ skipPluginChecks: process.env.LOWDEFY_BUILD_SKIP_PLUGIN_CHECKS === 'true',
115
129
  stage: 'dev',
116
130
  });
131
+ pageBuildRecords.trackFileReads({ context, configDirectory });
117
132
 
118
133
  // Restore refMap, keyMap, jsMap, connectionIds, and websocketIds from skeleton build
119
- Object.assign(cachedBuildContext.refMap, refMap);
120
- Object.assign(cachedBuildContext.keyMap, keyMap);
121
- cachedBuildContext.jsMap.client = jsMap.client ?? {};
122
- cachedBuildContext.jsMap.server = jsMap.server ?? {};
134
+ Object.assign(context.refMap, refMap);
135
+ Object.assign(context.keyMap, keyMap);
136
+ context.jsMap.client = jsMap.client ?? {};
137
+ context.jsMap.server = jsMap.server ?? {};
123
138
  for (const id of connectionIds) {
124
- cachedBuildContext.connectionIds.add(id);
139
+ context.connectionIds.add(id);
125
140
  }
126
141
  for (const id of websocketIds) {
127
- cachedBuildContext.websocketIds.add(id);
142
+ context.websocketIds.add(id);
128
143
  }
144
+ // The scoped connections, walled collections and shared connections, so a
145
+ // page's requests get the same tenant pipeline checks as in a full build.
146
+ const tenantTargets = readJsonFile(path.join(buildDirectory, 'tenantTargets.json'));
147
+ if (tenantTargets) {
148
+ restoreTenantTargets({ context, tenantTargets });
149
+ }
150
+ // Pages that host a policy-bound Dynamic block count the policy's types
151
+ // and validate its pages and endpoints.
152
+ Object.assign(
153
+ context.dynamicPolicies,
154
+ readJsonFile(path.join(buildDirectory, 'dynamicPolicies.json')) ?? {}
155
+ );
129
156
 
130
157
  // Load installed packages snapshot from skeleton build for missing-package detection
131
158
  const installedPluginPackages =
132
159
  readJsonFile(path.join(buildDirectory, 'installedPluginPackages.json')) ?? [];
133
- cachedBuildContext.installedPluginPackages = new Set(installedPluginPackages);
160
+ context.installedPluginPackages = new Set(installedPluginPackages);
134
161
 
135
162
  // Restore module entries from skeleton build for JIT module page builds
136
163
  const modules = readJsonFile(path.join(buildDirectory, 'modules.json'));
137
164
  if (modules) {
138
- Object.assign(cachedBuildContext.modules, modules);
165
+ Object.assign(context.modules, modules);
139
166
  }
140
167
 
141
168
  // Hydrate the deferred-record registry — module component bodies referenced
@@ -143,94 +170,238 @@ function getBuildContext(buildDirectory, configDirectory) {
143
170
  // the marker-restoring reviver, so record-body ~r/~l markers survive.
144
171
  const deferredRecords = readJsonFile(path.join(buildDirectory, 'deferredRecords.json'));
145
172
  if (deferredRecords) {
146
- hydrateDeferredRecords(cachedBuildContext, deferredRecords);
173
+ hydrateDeferredRecords(context, deferredRecords);
147
174
  }
148
175
 
149
176
  // Restore app metadata so JIT page builds resolve _app / _build.app against
150
177
  // the same metadata the skeleton build computed.
151
- cachedBuildContext.appMeta = readJsonFile(path.join(buildDirectory, 'appMeta.json')) ?? null;
178
+ context.appMeta = readJsonFile(path.join(buildDirectory, 'appMeta.json')) ?? null;
152
179
 
153
180
  // Restore api endpoint configs so JIT CallAPI validation (validateCallApiRefs in
154
181
  // buildPageJit) can resolve endpointIds. Without this the dev context has no
155
182
  // components.api and every CallAPI action is flagged as a non-existent endpoint.
156
- cachedBuildContext.components = { api: readBuildApiArtifacts(buildDirectory) };
157
-
158
- // Use the frozen icon imports from the initial build for JIT detection.
159
- // This represents what's actually in the client bundle — not what shallowBuild
160
- // discovers on subsequent rebuilds (those icons aren't bundled yet).
161
- // bundledIconImports is module-level and only resets on server restart.
162
- if (!bundledIconImports) {
163
- bundledIconImports = readJsonFile(path.join(buildDirectory, 'iconImports.json')) ?? [];
164
- }
165
- cachedBuildContext.iconImports = bundledIconImports;
183
+ context.components = { api: readBuildApiArtifacts(buildDirectory) };
166
184
 
167
- // Accumulator for dynamically extracted icon SVG data written to plugins/iconsDynamic.js.
168
- // Reset on skeleton rebuild (cachedBuildContext = null) — JIT re-discovers as needed.
169
- cachedBuildContext.dynamicIconData = {};
185
+ if (!bundledIcons) {
186
+ bundledIcons = new Set(readJsonFile(path.join(buildDirectory, 'iconImports.json')) ?? []);
187
+ }
188
+ context.bundledIcons = bundledIcons;
189
+ // IconData delivered to pages as _dynamicIcons. Starts empty with each
190
+ // context, since theme.icons or icon set plugins may have changed; JIT
191
+ // re-resolves as pages are requested.
192
+ context.dynamicIconData = {};
170
193
 
171
- // Advance makeId past all skeleton IDs to prevent collisions with JIT builds
172
194
  const idCounter = readJsonFile(path.join(buildDirectory, 'idCounter.json'));
173
- if (idCounter != null) {
174
- makeId.setCounter(idCounter);
175
- }
195
+ continueJitKeys({ idCounter, childId });
196
+
197
+ // The context's page builds write the entries they add to jitMaps/. Only the
198
+ // previous context's files are kept besides its own: they resolve errors that
199
+ // pages built just before the recreation still report.
200
+ context.jitMaps = createJitMaps({
201
+ keyMap: context.keyMap,
202
+ refMap: context.refMap,
203
+ name: `${childId}-${contextGeneration}`,
204
+ });
205
+ pruneJitMaps({ buildDirectory, keep: `${childId}-${previousContextGeneration}-` });
206
+ previousContextGeneration = contextGeneration;
207
+ skipStaleMapWrites({
208
+ buildDirectory,
209
+ context,
210
+ keyPrefix: idCounter.prefix,
211
+ isKeptContext: () => context === keptContext,
212
+ });
213
+ prepareJitContext(context);
176
214
 
177
- return cachedBuildContext;
215
+ return context;
178
216
  }
179
217
 
180
- async function buildPageIfNeeded({ pageId, buildDirectory, configDirectory }) {
181
- checkPageInvalidations(buildDirectory);
182
- const registry = loadPageRegistry(buildDirectory);
183
- if (!registry || !registry[pageId]) {
184
- return false;
218
+ // The next page build makes a new context, of a new generation. Pages built on
219
+ // this one are rebuilt.
220
+ function discardBuildContext() {
221
+ keptContext = null;
222
+ contextGeneration += 1;
223
+ budgetExceeded = false;
224
+ }
225
+
226
+ // The build context JIT page builds share. It outlives page edits and is
227
+ // recreated (a new generation) only when a config build is published or its
228
+ // maps pass contextMapBudget.
229
+ export function getBuildContext(buildDirectory, configDirectory) {
230
+ keptContext ??= createBuildContext(buildDirectory, configDirectory);
231
+ return keptContext;
232
+ }
233
+
234
+ // A change event: the files pages read may have changed. Reads from here on go
235
+ // through a fresh read cache, so a check or build sees what is on disk now;
236
+ // builds already running keep the cache they started with, and what they read
237
+ // is checked on the page's next request. Warnings are logged again.
238
+ function startChangeEvent(configDirectory) {
239
+ eventCounter += 1;
240
+ if (!keptContext) return;
241
+ keptContext.readConfigFile = pageBuildRecords.trackReadConfigFile({
242
+ readConfigFile: createReadConfigFile({ directories: keptContext.directories }),
243
+ configDirectory,
244
+ });
245
+ keptContext.seenSourceLines.clear();
246
+ }
247
+
248
+ // Reads what the manager and the config build changed since the last call, and
249
+ // acts on it: a new build/invalidatePages value is a change event; a newly
250
+ // published page registry, or a context past its budget, discards the build
251
+ // context, so the next page build makes a new one. Both a page request and the
252
+ // build status review call it first, so each sees an edit the other has not.
253
+ export function syncBuildSignals({ buildDirectory, configDirectory }) {
254
+ const changeSignal = readChangeSignal(buildDirectory);
255
+ if (changeSignal !== lastChangeSignal) {
256
+ lastChangeSignal = changeSignal;
257
+ startChangeEvent(configDirectory);
258
+ }
259
+ const identity = readRegistryIdentity(buildDirectory);
260
+ if (identity === null) {
261
+ return { registry: null, eventCounter, generation: contextGeneration };
185
262
  }
263
+ if (identity !== registryIdentity) {
264
+ registryIdentity = identity;
265
+ registry = readJsonFile(path.join(buildDirectory, 'pageRegistry.json'));
266
+ discardBuildContext();
267
+ } else if (budgetExceeded) {
268
+ discardBuildContext();
269
+ }
270
+ return { registry, eventCounter, generation: contextGeneration };
271
+ }
186
272
 
187
- if (pageCache.isCompiled(pageId)) {
188
- return true;
273
+ function countAddedMapEntries(context) {
274
+ return context.jitMaps.keys.added.length + context.jitMaps.refs.added.length;
275
+ }
276
+
277
+ // Whether a page's last build still describes it: 'current' or 'edited'. A
278
+ // page built on an earlier context is edited: its content, _js and icons
279
+ // belong to a context that is gone. A page checked up to the current change
280
+ // event is current. Otherwise its last build's inputs are checked
281
+ // (checkPageRecord), and a match moves its checkedAt to the event the check
282
+ // started at, so the next request or review does not check it again. A page
283
+ // request and the build status review both decide with this, so a build
284
+ // status wait builds exactly the pages the next requests would.
285
+ export async function reviewBuiltPage({ pageId, eventCounter: counter, generation }) {
286
+ const record = pageBuildRecords.get(pageId);
287
+ if (!record || record.generation !== generation) return 'edited';
288
+ const compiled = pageCache.get(pageId);
289
+ if (record.checkedAt === counter || compiled?.checkedAt === counter) return 'current';
290
+ const check = await checkPageRecord({ record, readConfigFile: keptContext.readConfigFile });
291
+ const latest = pageBuildRecords.get(pageId);
292
+ if (latest !== record) {
293
+ // The page was built again while this check read its previous build's
294
+ // inputs, so the check says nothing about the new build: it is current
295
+ // only when it started at or after this check's event.
296
+ return latest?.generation === generation && latest.checkedAt >= counter ? 'current' : 'edited';
189
297
  }
298
+ if (check !== 'current') return 'edited';
299
+ record.checkedAt = Math.max(record.checkedAt, counter);
300
+ pageCache.markChecked(pageId, { generation, checkedAt: counter });
301
+ return 'current';
302
+ }
190
303
 
191
- const shouldBuild = await pageCache.acquireBuildLock(pageId);
192
- if (!shouldBuild) {
193
- // Another request completed the build
194
- return true;
304
+ // Whether a compiled page can be served as it is.
305
+ async function isPageCurrent({ pageId, eventCounter: counter, generation }) {
306
+ const compiled = pageCache.get(pageId);
307
+ if (!compiled || compiled.generation !== generation) return false;
308
+ if ((await reviewBuiltPage({ pageId, eventCounter: counter, generation })) !== 'current') {
309
+ return false;
195
310
  }
311
+ // A rebuild that failed while the page was checked leaves nothing to serve.
312
+ return pageCache.isCompiled(pageId);
313
+ }
314
+
315
+ async function buildPage({ pageId, buildDirectory, configDirectory }) {
316
+ // Read together, before the build starts: a change event or recreation
317
+ // during the build leaves the page behind, so its next request checks or
318
+ // rebuilds it.
319
+ const context = getBuildContext(buildDirectory, configDirectory);
320
+ const generation = contextGeneration;
321
+ const checkedAt = eventCounter;
322
+ const pageRegistry = registry;
323
+ pageCache.remove(pageId);
196
324
 
197
325
  jitLogger.info({ spin: 'start' }, `Building page "${pageId}"...`);
198
326
  const startTime = Date.now();
327
+ let result;
199
328
  try {
200
- const context = getBuildContext(buildDirectory, configDirectory);
201
- const result = await buildPageJit({
329
+ result = await pageBuildRecords.record({
202
330
  pageId,
203
- pageRegistry: registry,
204
331
  context,
332
+ configDirectory,
333
+ generation,
334
+ checkedAt,
335
+ build: () => buildPageJit({ pageId, pageRegistry, context }),
205
336
  });
206
- if (result && result.installing) {
207
- jitLogger.info(
208
- `Installing plugin packages for page "${pageId}": ${result.packages.join(', ')}. ` +
209
- 'The page will be available after the server restarts.'
210
- );
211
- return result;
212
- }
213
- pageCache.markCompiled(pageId);
214
- // Touch the candidates file so Vite's CSS pipeline re-runs Tailwind for
215
- // classes the JIT build discovered — globals.css imports it. This import
216
- // is the ONLY recompile trigger: the tailwind .html scan inputs are
217
- // excluded from Vite's watcher (their .html change events would force
218
- // full browser reloads). Only touched when the build actually changed
219
- // tailwind content, so unchanged rebuilds cause no CSS recompile.
220
- if (result?._tailwindChanged) {
221
- fs.writeFileSync(
222
- path.join(buildDirectory, 'tailwind-candidates.css'),
223
- `/* Generated by Lowdefy build — rewritten on page changes to trigger CSS recompilation */\n/* ${Date.now()} */\n`
224
- );
337
+ } finally {
338
+ // A failed build adds map entries too, and a page being fixed fails
339
+ // build after build.
340
+ if (context === keptContext && countAddedMapEntries(context) > contextMapBudget) {
341
+ budgetExceeded = true;
225
342
  }
343
+ }
344
+ if (result && result.installing) {
226
345
  jitLogger.info(
227
- { spin: 'succeed', color: 'white' },
228
- `Built page "${pageId}" in ${formatDuration(Date.now() - startTime)}.`
346
+ `Installing plugin packages for page "${pageId}": ${result.packages.join(', ')}. ` +
347
+ 'The page will be available after the server restarts.'
348
+ );
349
+ return { result, buildContext: context };
350
+ }
351
+ pageCache.markCompiled(pageId, { generation, checkedAt });
352
+ // Touch the candidates file so Vite's CSS pipeline re-runs Tailwind for
353
+ // classes the JIT build discovered — globals.css imports it. This import
354
+ // is the ONLY recompile trigger: the tailwind .html scan inputs are
355
+ // excluded from Vite's watcher (their .html change events would force
356
+ // full browser reloads). Only touched when the build actually changed
357
+ // tailwind content, so unchanged rebuilds cause no CSS recompile.
358
+ if (result?._tailwindChanged) {
359
+ fs.writeFileSync(
360
+ path.join(buildDirectory, 'tailwind-candidates.css'),
361
+ `/* Generated by Lowdefy build — rewritten on page changes to trigger CSS recompilation */\n/* ${Date.now()} */\n`
229
362
  );
230
- return { built: true, warnings: result?._warnings };
231
- } finally {
232
- pageCache.releaseBuildLock(pageId);
233
363
  }
364
+ jitLogger.info(
365
+ { spin: 'succeed', color: 'white' },
366
+ `Built page "${pageId}" in ${formatDuration(Date.now() - startTime)}.`
367
+ );
368
+ return { result: { built: true, warnings: result?._warnings }, buildContext: context };
369
+ }
370
+
371
+ // Serves a page from its last JIT build while that build is current, else
372
+ // builds it. Returns { result, buildContext }: result is false for a page not
373
+ // in the registry, true for a page already current, or the build's result;
374
+ // buildContext is the context the page was built on, which holds its _js
375
+ // entries and icons even after a later request discards it.
376
+ export async function buildPageWithContext({ pageId, buildDirectory, configDirectory }) {
377
+ for (;;) {
378
+ const signals = syncBuildSignals({ buildDirectory, configDirectory });
379
+ // A kept context is always of the current generation, so a page current
380
+ // in this generation was built on it.
381
+ const currentContext = keptContext;
382
+ if (!signals.registry || !signals.registry[pageId]) {
383
+ return { result: false, buildContext: null };
384
+ }
385
+ if (await isPageCurrent({ pageId, ...signals })) {
386
+ return { result: true, buildContext: currentContext };
387
+ }
388
+ const shouldBuild = await pageCache.acquireBuildLock(pageId);
389
+ if (shouldBuild) {
390
+ try {
391
+ return await buildPage({ pageId, buildDirectory, configDirectory });
392
+ } finally {
393
+ pageCache.releaseBuildLock(pageId);
394
+ }
395
+ }
396
+ // Another request built the page meanwhile. Its build may have started
397
+ // before an event this request saw, or failed, so the page is looked at
398
+ // again rather than taken as current.
399
+ }
400
+ }
401
+
402
+ async function buildPageIfNeeded({ pageId, buildDirectory, configDirectory }) {
403
+ const { result } = await buildPageWithContext({ pageId, buildDirectory, configDirectory });
404
+ return result;
234
405
  }
235
406
 
236
407
  // Collect every client _js hash the page references. jsMapParser reduces a _js
@@ -269,36 +440,45 @@ function scopeDynamicIcons({ pageConfig, scopedJsMap, dynamicIconData }) {
269
440
  if (Object.keys(dynamicIconData).length === 0) return undefined;
270
441
  const scanText = [JSON.stringify(pageConfig), ...Object.values(scopedJsMap)].join('\n');
271
442
  const found = {};
272
- for (const regex of Object.values(iconPackages)) {
273
- for (const match of scanText.matchAll(regex)) {
274
- const name = match[1];
275
- if (dynamicIconData[name] && !found[name]) {
276
- found[name] = dynamicIconData[name];
277
- }
443
+ for (const name of collectIconNames({ text: scanText })) {
444
+ if (Object.hasOwn(dynamicIconData, name)) {
445
+ found[name] = dynamicIconData[name];
278
446
  }
279
447
  }
280
448
  return Object.keys(found).length > 0 ? found : undefined;
281
449
  }
282
450
 
283
- // Scope this page's JIT-discovered enrichment out of the persistent build
284
- // context so jitPageHandler can fold it into the page-config response the client
285
- // already awaits — removing the two secondary fetches that stalled first paint.
286
- // buildContext defaults to the module-private cachedBuildContext (re-read on
287
- // every call, so it tracks invalidation resets); tests pass a stub.
288
- export function getPageJitEnrichment({ pageConfig, buildContext = cachedBuildContext }) {
289
- // No build context (before the first build, or after an invalidation reset)
290
- // means nothing JIT-discovered to fold — the page serves what the static
291
- // client bundle already carries.
292
- if (!buildContext) return {};
293
-
294
- const clientJsMap = buildContext.jsMap.client ?? {};
451
+ // The contexts whose jsMap and icons can hold a served page's entries: the one
452
+ // the page was built on, and the kept one. The page config is read from disk
453
+ // after the build, so a build of the page on a newer context can have written
454
+ // it meanwhile. _js keys are content hashes, so an entry found in either is
455
+ // the page's own.
456
+ function enrichmentContexts(buildContext) {
457
+ return [buildContext, keptContext].filter(
458
+ (context, index, contexts) => context && contexts.indexOf(context) === index
459
+ );
460
+ }
461
+
462
+ // Scope this page's JIT-discovered enrichment out of the build context it was
463
+ // built on, so jitPageHandler can fold it into the page-config response the
464
+ // client already awaits — removing the two secondary fetches that stalled
465
+ // first paint. A request can discard that context while the page config is
466
+ // read, so it is passed in (from buildPageWithContext) rather than taken from
467
+ // the kept context.
468
+ export function getPageJitEnrichment({ pageConfig, buildContext }) {
469
+ const contexts = enrichmentContexts(buildContext);
470
+ // No build context (before the first build) means nothing JIT-discovered to
471
+ // fold — the page serves what the static client bundle already carries.
472
+ if (contexts.length === 0) return {};
473
+
295
474
  const hashes = new Set();
296
475
  collectJsHashes(pageConfig, hashes);
297
476
 
298
477
  const scopedJsMap = {};
299
478
  for (const hash of hashes) {
300
- if (Object.prototype.hasOwnProperty.call(clientJsMap, hash)) {
301
- scopedJsMap[hash] = clientJsMap[hash];
479
+ const context = contexts.find((candidate) => Object.hasOwn(candidate.jsMap.client ?? {}, hash));
480
+ if (context) {
481
+ scopedJsMap[hash] = context.jsMap.client[hash];
302
482
  }
303
483
  }
304
484
 
@@ -308,7 +488,10 @@ export function getPageJitEnrichment({ pageConfig, buildContext = cachedBuildCon
308
488
  const dynamicIcons = scopeDynamicIcons({
309
489
  pageConfig,
310
490
  scopedJsMap,
311
- dynamicIconData: buildContext.dynamicIconData ?? {},
491
+ dynamicIconData: Object.assign(
492
+ {},
493
+ ...[...contexts].reverse().map((context) => context.dynamicIconData ?? {})
494
+ ),
312
495
  });
313
496
 
314
497
  return { jsEntries, dynamicIcons };