@lowdefy/server-dev 6.0.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/App.jsx +52 -24
- package/client/Inspector.jsx +73 -30
- package/client/JourneyObserver.jsx +47 -0
- package/client/Page.jsx +54 -22
- package/client/Recorder.jsx +73 -0
- package/client/Routing.jsx +37 -13
- package/client/clientBuildImports.test.mjs +63 -0
- package/client/devStreamConnections.test.mjs +40 -0
- package/client/feedback/FeedbackMount.jsx +2 -2
- package/client/feedback/FeedbackOverlay.jsx +7 -4
- package/client/feedback/feedbackStyles.js +3 -0
- package/client/journeyObserver/startJourneyObserver.js +101 -0
- package/client/journeyObserver/startJourneyObserver.test.mjs +149 -0
- package/client/openInEditor/OpenInEditorListener.jsx +2 -0
- package/client/recorder/buildEngineRecord.js +37 -0
- package/client/recorder/buildInteractionRecord.js +64 -0
- package/client/recorder/buildTraceEvent.js +57 -0
- package/client/recorder/buildTraceEvent.test.mjs +81 -0
- package/client/recorder/chunkRecords.js +69 -0
- package/client/recorder/chunkRecords.test.mjs +59 -0
- package/client/recorder/collectEventActions.js +30 -0
- package/client/recorder/collectEventEndpoints.js +40 -0
- package/client/recorder/collectEventRequests.js +39 -0
- package/client/recorder/createPairingBuffer.js +187 -0
- package/client/recorder/createPairingBuffer.test.mjs +405 -0
- package/client/recorder/createPasswordRedactor.js +84 -0
- package/client/recorder/createRecordBatcher.js +54 -0
- package/client/recorder/createRecordBatcher.test.mjs +76 -0
- package/client/recorder/createRecorder.js +216 -0
- package/client/recorder/createRecorder.test.mjs +318 -0
- package/client/recorder/createRecordingSession.js +61 -0
- package/client/recorder/createRecordingSession.test.mjs +72 -0
- package/client/recorder/diffStateWrites.js +77 -0
- package/client/recorder/diffStateWrites.test.mjs +66 -0
- package/client/recorder/isEventCall.js +30 -0
- package/client/recorder/jsonType.js +39 -0
- package/client/recorder/keyChord.js +47 -0
- package/client/recorder/keyChord.test.mjs +50 -0
- package/client/recorder/sendRecords.js +40 -0
- package/client/recorder/toJsonSafe.js +29 -0
- package/lib/client/BuildingPage.jsx +3 -1
- package/lib/client/ErrorBar.jsx +17 -13
- package/lib/client/RedirectingPage.jsx +56 -0
- package/lib/client/auth/Auth.jsx +11 -6
- package/lib/client/auth/AuthConfigured.jsx +224 -39
- package/lib/client/auth/AuthNotConfigured.js +45 -4
- package/lib/client/auth/authNotConfiguredParity.test.mjs +61 -0
- package/lib/client/auth/authTemplateSync.test.mjs +40 -0
- package/lib/client/auth/betterAuthSessionContract.test.mjs +181 -0
- package/lib/client/auth/createFetchCheckingBuild.js +45 -0
- package/lib/client/bundleBuildId.js +25 -0
- package/lib/client/collectVisibleBlockIds.js +51 -0
- package/lib/client/getCspNonce.js +25 -0
- package/lib/client/getPagePath.js +36 -0
- package/lib/client/getPagePath.test.mjs +51 -0
- package/lib/client/utils/formatErrorsForCopy.js +30 -0
- package/lib/client/utils/formatErrorsForCopy.test.mjs +39 -0
- package/lib/client/utils/getErrorBarColor.js +25 -0
- package/lib/client/utils/getErrorBarColor.test.mjs +34 -0
- package/lib/client/utils/request.js +8 -1
- package/lib/client/utils/usePageConfig.js +89 -25
- package/lib/client/utils/usePageConfig.test.mjs +198 -13
- package/lib/configTree/buildConfigTree.mjs +58 -0
- package/lib/configTree/buildConfigTree.test.mjs +77 -0
- package/lib/configTree/collectConfigText.mjs +101 -0
- package/lib/configTree/collectConfigText.test.mjs +116 -0
- package/lib/configTree/prepareScratchServer.mjs +46 -0
- package/lib/configTree/runConfigTreeBuild.mjs +88 -0
- package/lib/configTree/runConfigTreeBuild.skipPluginChecks.test.mjs +74 -0
- package/lib/configTree/runConfigTreeBuild.test.mjs +202 -0
- package/lib/docs/JourneyStepError.js +30 -0
- package/lib/docs/acquireBrowserSlot.js +30 -0
- package/lib/docs/addNestedEndpoints.js +54 -0
- package/lib/docs/addNestedEndpoints.test.mjs +185 -0
- package/lib/docs/addWriteFlags.js +77 -0
- package/lib/docs/buildEditedPages.js +50 -0
- package/lib/docs/buildWaitHeader.js +21 -0
- package/lib/docs/captureAnnotatedScreenshot.js +23 -12
- package/lib/docs/checkAgentFiles.js +148 -0
- package/lib/docs/checkAgentFiles.test.mjs +225 -0
- package/lib/docs/checkStepReferences.js +239 -0
- package/lib/docs/checkStepReferences.test.mjs +173 -0
- package/lib/docs/checkpointStore.js +3 -0
- package/lib/docs/clientErrorStore.js +18 -1
- package/lib/docs/clientErrorStore.test.mjs +46 -0
- package/lib/docs/collectExercised.js +52 -0
- package/lib/docs/createBrowserLifecycle.js +102 -0
- package/lib/docs/createBrowserLifecycle.test.mjs +212 -0
- package/lib/docs/createDocsMcpServer.js +345 -388
- package/lib/docs/createDocsMcpServer.test.mjs +644 -4
- package/lib/docs/createDocsMcpServerRunJourney.test.mjs +148 -0
- package/lib/docs/createJourneyActors.chromium.test.mjs +227 -0
- package/lib/docs/createJourneyActors.js +213 -0
- package/lib/docs/createJourneyActors.test.mjs +79 -0
- package/lib/docs/createJourneyObservations.js +80 -0
- package/lib/docs/createLeftOriginError.js +26 -0
- package/lib/docs/createNetworkCounter.js +101 -0
- package/lib/docs/dataSets/checkSharedCollectionIds.js +67 -0
- package/lib/docs/dataSets/dataSessionRegistry.js +25 -0
- package/lib/docs/dataSets/dataSetJourneys.chromium.mongodb.test.mjs +679 -0
- package/lib/docs/dataSets/describeDataSetResult.js +25 -0
- package/lib/docs/dataSets/describeDatabaseTarget.js +46 -0
- package/lib/docs/dataSets/deserializeFixture.js +34 -0
- package/lib/docs/dataSets/drainSessionWork.js +37 -0
- package/lib/docs/dataSets/drainSessionWork.test.mjs +64 -0
- package/lib/docs/dataSets/duplicateIndexName.js +24 -0
- package/lib/docs/dataSets/findCollectionCollisions.js +52 -0
- package/lib/docs/dataSets/getDataStore.js +106 -0
- package/lib/docs/dataSets/getDataStore.mongodb.test.mjs +63 -0
- package/lib/docs/dataSets/getDataStore.test.mjs +195 -0
- package/lib/docs/dataSets/groupDataSetByCollection.js +44 -0
- package/lib/docs/dataSets/insertFixture.js +57 -0
- package/lib/docs/dataSets/insertGeneratedDocuments.js +41 -0
- package/lib/docs/dataSets/isDataStoreGone.js +29 -0
- package/lib/docs/dataSets/loadDataSetCollection.js +42 -0
- package/lib/docs/dataSets/mergeCollectionIndexes.js +52 -0
- package/lib/docs/dataSets/mergeCollectionIndexes.test.mjs +59 -0
- package/lib/docs/dataSets/openDataSession.js +119 -0
- package/lib/docs/dataSets/openDataSession.mongodb.test.mjs +343 -0
- package/lib/docs/dataSets/pickDataStorePort.js +58 -0
- package/lib/docs/dataSets/pickDataStorePort.test.mjs +63 -0
- package/lib/docs/dataSets/readConnectionArtifacts.js +46 -0
- package/lib/docs/dataSets/readDataSession.js +41 -0
- package/lib/docs/dataSets/readDataSession.test.mjs +57 -0
- package/lib/docs/dataSets/readDataSet.js +68 -0
- package/lib/docs/dataSets/readDataSet.test.mjs +220 -0
- package/lib/docs/dataSets/registerDataStoreShutdown.js +47 -0
- package/lib/docs/dataSets/removeDataStoreFiles.js +32 -0
- package/lib/docs/dataSets/resolveDataSetCollection.js +46 -0
- package/lib/docs/dataSets/resolveJourneyDataSet.js +106 -0
- package/lib/docs/dataSets/resolveJourneyDataSet.test.mjs +130 -0
- package/lib/docs/dataSets/sweepOrphanDatabases.js +33 -0
- package/lib/docs/dataSets/test/app/lowdefy.yaml +203 -0
- package/lib/docs/dataSets/test/app/modules/archive/module.lowdefy.yaml +30 -0
- package/lib/docs/dataSets/test/app/tests/data/shop.yaml +8 -0
- package/lib/docs/dataSets/test/buildTestApp.mjs +43 -0
- package/lib/docs/devEventBus.js +136 -0
- package/lib/docs/devEventBus.test.mjs +269 -0
- package/lib/docs/devToolDefinitions.js +529 -0
- package/lib/docs/docSources.js +21 -0
- package/lib/docs/docs.test.mjs +171 -15
- package/lib/docs/docsIndex.test.mjs +377 -0
- package/lib/docs/evalOperator.js +5 -3
- package/lib/docs/evalOperatorHeadless.js +84 -12
- package/lib/docs/evalOperatorHeadless.test.mjs +7 -1
- package/lib/docs/evalOperatorInTab.js +2 -1
- package/lib/docs/findConfig.js +95 -49
- package/lib/docs/fitResponse.js +81 -0
- package/lib/docs/fitResponse.test.mjs +128 -0
- package/lib/docs/formatFeedback.js +26 -5
- package/lib/docs/formatFeedback.test.mjs +48 -0
- package/lib/docs/getAppMap.js +7 -1
- package/lib/docs/getBrowser.js +150 -80
- package/lib/docs/getBrowser.test.mjs +407 -16
- package/lib/docs/getBuildId.js +61 -0
- package/lib/docs/getBuildId.test.mjs +84 -0
- package/lib/docs/getBuildStatus.js +54 -6
- package/lib/docs/getBuildStatus.test.mjs +133 -0
- package/lib/docs/getBuildStatusAfterEdits.js +35 -0
- package/lib/docs/getBuildStatusAfterEdits.test.mjs +76 -0
- package/lib/docs/getCoreDoc.js +9 -1
- package/lib/docs/getDoc.js +100 -0
- package/lib/docs/getDocsIndex.js +152 -0
- package/lib/docs/getHazards.js +115 -0
- package/lib/docs/getHazards.test.mjs +175 -0
- package/lib/docs/getMailOutboxDirectory.js +26 -0
- package/lib/docs/getOverview.js +79 -13
- package/lib/docs/getOverviewBasePath.test.mjs +37 -0
- package/lib/docs/getPageBuildStatus.js +46 -0
- package/lib/docs/getPluginDoc.js +21 -23
- package/lib/docs/getSchema.js +33 -1
- package/lib/docs/getStaleStatus.js +36 -0
- package/lib/docs/getStaleStatus.test.mjs +74 -0
- package/lib/docs/guardJourneyOrigin.js +39 -0
- package/lib/docs/inspectState.js +5 -3
- package/lib/docs/inspectStateFromTab.js +2 -2
- package/lib/docs/inspectStateHeadless.js +89 -27
- package/lib/docs/inspectStateHeadless.test.mjs +20 -1
- package/lib/docs/installHeadlessShell.failure.test.mjs +45 -0
- package/lib/docs/installHeadlessShell.js +123 -0
- package/lib/docs/installHeadlessShell.test.mjs +70 -0
- package/lib/docs/installHeadlessShell.timeout.test.mjs +72 -0
- package/lib/docs/isHeadlessShellIncomplete.js +34 -0
- package/lib/docs/isHeadlessShellIncomplete.test.mjs +63 -0
- package/lib/docs/isPageReady.js +77 -0
- package/lib/docs/isPageReady.test.mjs +235 -0
- package/lib/docs/isRunErrorEntry.js +25 -0
- package/lib/docs/journeyMutantCookie.chromium.test.mjs +150 -0
- package/lib/docs/journeyRunRecording.js +35 -0
- package/lib/docs/journeyRunRecording.test.mjs +39 -0
- package/lib/docs/keepEventStreamAlive.js +68 -0
- package/lib/docs/keepEventStreamAlive.test.mjs +77 -0
- package/lib/docs/launchBrowser.js +83 -0
- package/lib/docs/launchBrowser.test.mjs +175 -0
- package/lib/docs/listModuleDocEntries.js +69 -0
- package/lib/docs/listPackageDocFiles.js +67 -0
- package/lib/docs/listPluginDocEntries.js +111 -0
- package/lib/docs/listPlugins.js +9 -58
- package/lib/docs/listTypes.js +14 -6
- package/lib/docs/loadFrameworkHazards.js +40 -0
- package/lib/docs/loadFrameworkHazards.test.mjs +43 -0
- package/lib/docs/loadState.js +103 -35
- package/lib/docs/makeModuleManifestDoc.js +174 -0
- package/lib/docs/matchExercisedPages.js +35 -0
- package/lib/docs/matchJourneyRoute.js +69 -0
- package/lib/docs/matchJourneyRoute.test.mjs +178 -0
- package/lib/docs/mergeNetworkSnapshots.js +49 -0
- package/lib/docs/nextJourneySequence.js +30 -0
- package/lib/docs/noBrowserError.js +22 -0
- package/lib/docs/observe/TRANSIENT_SELECTOR.js +21 -0
- package/lib/docs/observe/claimExpectedErrors.js +67 -0
- package/lib/docs/observe/createJourneyAppErrors.js +236 -0
- package/lib/docs/observe/createJourneyAppErrors.test.mjs +385 -0
- package/lib/docs/observe/evaluateInvariants.js +183 -0
- package/lib/docs/observe/evaluateInvariants.test.mjs +299 -0
- package/lib/docs/observe/findingKey.js +29 -0
- package/lib/docs/observe/hasNoEffect.js +31 -0
- package/lib/docs/observe/hasNoEffect.test.mjs +37 -0
- package/lib/docs/observe/installStepObserver.js +72 -0
- package/lib/docs/observe/isAppApiUrl.js +33 -0
- package/lib/docs/observe/parseAppApiUrl.js +37 -0
- package/lib/docs/observe/readStepObserver.js +42 -0
- package/lib/docs/observe/relativeSource.js +29 -0
- package/lib/docs/observe/resolveConfigKeySource.js +33 -0
- package/lib/docs/observe/takeJourneyEvents.js +36 -0
- package/lib/docs/observe/toAppErrorFailure.js +61 -0
- package/lib/docs/observe/usesSearchStage.js +45 -0
- package/lib/docs/observe/waitForClientErrorReports.js +31 -0
- package/lib/docs/observe/watchJourneyContext.js +75 -0
- package/lib/docs/observe/withAppErrors.js +31 -0
- package/lib/docs/observe/withAppErrors.test.mjs +101 -0
- package/lib/docs/openJourney.js +94 -0
- package/lib/docs/openJourneyEmail.js +41 -0
- package/lib/docs/openJourneyEmail.test.mjs +187 -0
- package/lib/docs/pageBlocks.js +47 -0
- package/lib/docs/pageBuildStatus.test.mjs +103 -0
- package/lib/{client/setPageId.js → docs/parseModuleSource.js} +11 -12
- package/lib/docs/rails/authEngineActions.js +53 -0
- package/lib/docs/rails/authEngineActions.test.mjs +69 -0
- package/lib/docs/rails/authEngineMethods.js +52 -0
- package/lib/docs/rails/authEngineMethods.test.mjs +36 -0
- package/lib/docs/rails/checkAuthActionsRan.js +66 -0
- package/lib/docs/rails/checkAuthActionsRan.test.mjs +149 -0
- package/lib/docs/rails/checkClickRails.js +56 -0
- package/lib/docs/rails/createDataSetRails.js +82 -0
- package/lib/docs/rails/createDataSetRails.test.mjs +172 -0
- package/lib/docs/rails/findExternalBlocks.js +147 -0
- package/lib/docs/rails/findExternalBlocks.test.mjs +254 -0
- package/lib/docs/rails/installAuthActionRecorder.js +56 -0
- package/lib/docs/rails/readAuthActionRecorder.js +32 -0
- package/lib/docs/rails/readBuiltArtifact.js +30 -0
- package/lib/docs/readConnectionPlugins.js +26 -0
- package/lib/docs/readDevAuthMode.js +32 -0
- package/lib/docs/readDocEntry.js +40 -0
- package/lib/docs/readDocTitle.js +26 -0
- package/lib/docs/readJourneyEmailMatch.js +57 -0
- package/lib/docs/readJourneyEmailMatch.test.mjs +110 -0
- package/lib/docs/readJourneySession.js +56 -0
- package/lib/docs/readMailOutbox.js +37 -0
- package/lib/docs/readPagePath.js +27 -0
- package/lib/docs/readPluginPackages.js +99 -0
- package/lib/docs/readProxyBuildWait.js +38 -0
- package/lib/docs/readRoutineSteps.js +45 -0
- package/lib/docs/registerShownTextEngine.js +118 -0
- package/lib/docs/requestRestart.js +30 -0
- package/lib/docs/requestRestart.test.mjs +49 -0
- package/lib/docs/resolvePageInstance.js +46 -0
- package/lib/docs/resolvePageInstance.test.mjs +81 -0
- package/lib/docs/resolveSource.js +11 -7
- package/lib/docs/resolveSource.test.mjs +1 -1
- package/lib/docs/resolveToolCaller.js +62 -0
- package/lib/docs/reviewPage.js +51 -0
- package/lib/docs/reviewPageBuilds.js +56 -0
- package/lib/docs/reviewPageBuilds.test.mjs +207 -0
- package/lib/docs/runCheck.js +75 -0
- package/lib/docs/runCheck.test.mjs +91 -0
- package/lib/docs/runCheckChild.js +49 -0
- package/lib/docs/runEndpoint.js +185 -0
- package/lib/docs/runEndpoint.test.mjs +509 -0
- package/lib/docs/runErrorBuffers.js +99 -0
- package/lib/docs/runErrorBuffers.test.mjs +95 -0
- package/lib/docs/runJourney.js +347 -0
- package/lib/docs/runJourney.test.mjs +2383 -0
- package/lib/docs/runJourneyAsUsers.js +44 -0
- package/lib/docs/runJourneyDataSet.test.mjs +274 -0
- package/lib/docs/runJourneySteps.js +1320 -0
- package/lib/docs/runRequest.js +100 -59
- package/lib/docs/runRequest.test.mjs +190 -0
- package/lib/docs/saveAnnotatedScreenshot.js +1 -1
- package/lib/docs/scaffoldPage.js +3 -1
- package/lib/docs/screenshotPage.js +163 -11
- package/lib/docs/screenshotPage.test.mjs +263 -1
- package/lib/docs/screenshotPageClip.test.mjs +4 -0
- package/lib/docs/searchDocs.js +82 -30
- package/lib/docs/searchDocs.test.mjs +100 -0
- package/lib/docs/searchIcons.js +90 -0
- package/lib/docs/searchIcons.test.mjs +127 -0
- package/lib/docs/selectExercised.js +39 -0
- package/lib/docs/selectFinalState.js +66 -0
- package/lib/docs/selectFinalState.test.mjs +51 -0
- package/lib/docs/serverErrorStore.js +54 -0
- package/lib/docs/serverErrorStore.test.mjs +50 -0
- package/lib/docs/setupTestFixtures.mjs +133 -39
- package/lib/docs/shownTextSelector.js +28 -0
- package/lib/docs/snapshotState.js +13 -5
- package/lib/docs/splitSearchTerms.js +62 -0
- package/lib/docs/splitSearchTerms.test.mjs +50 -0
- package/lib/docs/stateCheckpoints.test.mjs +77 -0
- package/lib/docs/tabAvailable.js +6 -5
- package/lib/docs/tabChannel.js +127 -21
- package/lib/docs/tabChannel.test.mjs +187 -29
- package/lib/docs/unsettledPageNote.js +24 -0
- package/lib/docs/validateJourneyMail.js +36 -0
- package/lib/docs/validateJourneyTimeout.js +38 -0
- package/lib/docs/validateJourneyTimeout.test.mjs +29 -0
- package/lib/docs/validateRecordingParam.js +42 -0
- package/lib/docs/validateStateSelection.js +33 -0
- package/lib/docs/validateStateSelection.test.mjs +33 -0
- package/lib/docs/validateViewport.js +51 -0
- package/lib/docs/validateViewport.test.mjs +54 -0
- package/lib/docs/waitForBuild.js +50 -0
- package/lib/docs/waitForBuild.test.mjs +75 -0
- package/lib/docs/waitForJourneyEmail.js +77 -0
- package/lib/docs/withBrowserSlot.js +45 -0
- package/lib/docs/withBrowserSlot.test.mjs +75 -0
- package/lib/docs/withDataSession.js +53 -0
- package/lib/server/applyDataSetRedirect.js +83 -0
- package/lib/server/applyDataSetRedirect.test.mjs +340 -0
- package/lib/server/applyDataSetRedirectTenantReach.test.mjs +204 -0
- package/lib/server/applyDataSetRedirectWalled.test.mjs +131 -0
- package/lib/server/auth/createSystemContext.js +73 -0
- package/lib/server/auth/getAuth.js +46 -0
- package/lib/server/auth/getHeadlessUser.js +9 -8
- package/lib/server/auth/getHeadlessUser.test.mjs +41 -0
- package/lib/server/auth/getMockUser.js +18 -14
- package/lib/server/auth/{getAuthConfig.js → getStrategies.js} +12 -10
- package/lib/server/auth/headlessUser.js +3 -2
- package/lib/server/auth/journeyActor.js +35 -0
- package/lib/server/auth/resolveHeadlessUser.test.mjs +0 -4
- package/lib/server/checkPageRecord.js +44 -0
- package/lib/server/contextMapBudget.js +24 -0
- package/lib/server/contextMapBudget.test.mjs +98 -0
- package/lib/server/continueJitKeys.js +28 -0
- package/lib/server/continueJitKeys.test.mjs +43 -0
- package/lib/server/createDataSessionEndedError.js +33 -0
- package/lib/server/createDataSetRefusal.js +26 -0
- package/lib/server/createLowdefyContext.js +174 -41
- package/lib/server/createLowdefyContext.test.mjs +437 -0
- package/lib/server/getClientAddress.js +51 -0
- package/lib/server/getClientAddress.test.mjs +56 -0
- package/lib/server/getPageJitEnrichment.test.mjs +29 -11
- package/lib/server/hashConfigContent.js +26 -0
- package/lib/server/importAppCode.test.mjs +57 -0
- package/lib/server/isRoutineStep.js +27 -0
- package/lib/server/jitPageBuilder.js +309 -126
- package/lib/server/jitPageBuilder.skipPluginChecks.test.mjs +78 -0
- package/lib/server/jitPageBuilder.test.mjs +218 -0
- package/lib/server/journeyCookies/JOURNEY_COOKIES.js +37 -0
- package/lib/server/journeyCookies/forwardJourneyCookies.js +46 -0
- package/lib/server/journeyCookies/readJourneyCookie.js +52 -0
- package/lib/server/journeyCookies/writeJourneyCookie.js +46 -0
- package/lib/server/journeyCookies.js +22 -0
- package/lib/server/journeyCookies.test.mjs +145 -0
- package/lib/server/loadDynamicJsMap.js +47 -0
- package/lib/server/log/createHandleError.js +38 -2
- package/lib/server/log/createHandleError.test.mjs +125 -2
- package/lib/server/log/createLogger.js +3 -0
- package/lib/server/log/createLogger.test.mjs +77 -0
- package/lib/server/log/logRequest.js +2 -3
- package/lib/server/mutantDataSetWrappers.test.mjs +112 -0
- package/lib/server/mutants/applyMutant.js +39 -0
- package/lib/server/mutants/artifactPatterns.js +25 -0
- package/lib/server/mutants/buildAnchor.js +45 -0
- package/lib/server/mutants/collectSharedIds.js +41 -0
- package/lib/server/mutants/configPathSegments.js +38 -0
- package/lib/server/mutants/createMutantReadConfigFile.js +42 -0
- package/lib/server/mutants/describeEvent.js +22 -0
- package/lib/server/mutants/enumerateArtifact.js +56 -0
- package/lib/server/mutants/findNodeByKey.js +45 -0
- package/lib/server/mutants/findNodeByPointer.js +49 -0
- package/lib/server/mutants/getArtifactKind.js +32 -0
- package/lib/server/mutants/groupMutantCopies.js +56 -0
- package/lib/server/mutants/hashArtifact.js +25 -0
- package/lib/server/mutants/hashMutantTarget.js +34 -0
- package/lib/server/mutants/listMutants.js +122 -0
- package/lib/server/mutants/listMutants.test.mjs +502 -0
- package/lib/server/mutants/mutantId.js +30 -0
- package/lib/server/mutants/mutantRuns.js +57 -0
- package/lib/server/mutants/mutantRuns.test.mjs +136 -0
- package/lib/server/mutants/operators/dropAction.js +47 -0
- package/lib/server/mutants/operators/dropBlock.js +44 -0
- package/lib/server/mutants/operators/dropPayload.js +52 -0
- package/lib/server/mutants/operators/dropStep.js +44 -0
- package/lib/server/mutants/operators/flipVisible.js +53 -0
- package/lib/server/mutants/operators/index.js +37 -0
- package/lib/server/mutants/operators/retargetLink.js +72 -0
- package/lib/server/mutants/operators/skipValidate.js +77 -0
- package/lib/server/mutants/operators/swapIf.js +112 -0
- package/lib/server/mutants/operators.test.mjs +396 -0
- package/lib/server/mutants/positionFreePath.js +38 -0
- package/lib/server/mutants/removeFromParent.js +28 -0
- package/lib/server/mutants/test/fixtures.mjs +173 -0
- package/lib/server/mutants/test/keyArtifact.mjs +63 -0
- package/lib/server/mutants/validateMutantParam.js +69 -0
- package/lib/server/mutants/validateMutantsBody.js +88 -0
- package/lib/server/mutants/walkArtifact.js +179 -0
- package/lib/server/pageBuildRecords.js +131 -0
- package/lib/server/pageBuildRecords.test.mjs +258 -0
- package/lib/server/pageCache.mjs +22 -7
- package/lib/server/pageCache.test.mjs +22 -7
- package/lib/server/pageRebuilds.test.mjs +543 -0
- package/lib/server/pruneJitMaps.js +36 -0
- package/lib/server/pruneJitMaps.test.mjs +55 -0
- package/lib/server/readInstanceRecord.js +33 -0
- package/lib/server/readInstanceRecord.test.mjs +99 -0
- package/lib/server/readMergedMaps.js +86 -0
- package/lib/server/readMergedMaps.test.mjs +79 -0
- package/lib/server/recording/appendRecords.js +33 -0
- package/lib/server/recording/errorRecordingStamp.js +34 -0
- package/lib/server/recording/errorRecordingStamp.test.mjs +31 -0
- package/lib/server/recording/isRecordingEnabled.js +31 -0
- package/lib/server/recording/readRecordingCookie.js +71 -0
- package/lib/server/recording/readRecordingCookie.test.mjs +73 -0
- package/lib/server/recording/recordingCookiePayload.js +33 -0
- package/lib/server/recording/servedBuilds.js +46 -0
- package/lib/server/recording/servedBuilds.test.mjs +42 -0
- package/lib/server/scrubSecrets.js +23 -0
- package/lib/server/sinkExternalConnections.js +95 -0
- package/lib/server/sinkExternalConnections.test.mjs +135 -0
- package/lib/server/skipStaleMapWrites.js +62 -0
- package/lib/server/skipStaleMapWrites.test.mjs +113 -0
- package/lib/server/startIdleGc.js +67 -0
- package/lib/server/startIdleGc.test.mjs +138 -0
- package/lib/server/testFixtures/importAppCodeTwice.mjs +70 -0
- package/lib/server/trackSessionWork.js +30 -0
- package/lib/vite/createDevServerExclude.js +40 -0
- package/lib/vite/createDevServerExclude.test.mjs +79 -0
- package/manager/getContext.mjs +24 -2
- package/manager/processes/initialBuild.mjs +14 -0
- package/manager/processes/initialBuild.test.mjs +101 -0
- package/manager/processes/lowdefyBuild.mjs +30 -7
- package/manager/processes/lowdefyBuild.skipPluginChecks.test.mjs +62 -0
- package/manager/processes/lowdefyBuild.test.mjs +136 -1
- package/manager/processes/optimizeDependencies.mjs +59 -0
- package/manager/processes/restartServer.mjs +34 -6
- package/manager/processes/restartServer.test.mjs +162 -0
- package/manager/processes/shutdownServer.mjs +11 -0
- package/manager/processes/shutdownServer.test.mjs +45 -0
- package/manager/processes/startFirstServer.mjs +46 -0
- package/manager/processes/startFirstServer.test.mjs +326 -0
- package/manager/processes/startMailSink.mjs +135 -0
- package/manager/processes/startMailSink.test.mjs +146 -0
- package/manager/processes/startProxy.mjs +129 -12
- package/manager/processes/startProxy.test.mjs +366 -0
- package/manager/processes/startServer.mjs +47 -6
- package/manager/processes/startServer.test.mjs +155 -0
- package/manager/processes/startWatchers.mjs +6 -4
- package/manager/processes/syncServer.mjs +69 -0
- package/manager/processes/syncServer.test.mjs +141 -0
- package/manager/processes/waitForDevServer.mjs +48 -0
- package/manager/processes/waitForDevServer.test.mjs +112 -0
- package/manager/run.mjs +128 -43
- package/manager/utils/BatchChanges.mjs +21 -5
- package/manager/utils/BatchChanges.test.mjs +77 -0
- package/manager/utils/acquireDevInstance.mjs +99 -0
- package/manager/utils/acquireDevInstance.test.mjs +160 -0
- package/manager/utils/createBuildActivity.mjs +67 -0
- package/manager/utils/createBuildActivity.test.mjs +58 -0
- package/manager/utils/createChangeSignal.mjs +39 -0
- package/manager/utils/createChangeSignal.test.mjs +62 -0
- package/manager/utils/createCustomPluginMessagesMap.mjs +23 -33
- package/manager/utils/createCustomPluginTypesMap.mjs +24 -30
- package/manager/utils/createCustomPluginTypesMap.test.mjs +94 -0
- package/manager/utils/createDotPathIgnore.mjs +44 -0
- package/manager/utils/createDotPathIgnore.test.mjs +65 -0
- package/manager/utils/createIdleGc.mjs +51 -0
- package/manager/utils/createIdleGc.test.mjs +77 -0
- package/manager/utils/createRequestActivity.mjs +100 -0
- package/manager/utils/createRequestActivity.test.mjs +121 -0
- package/manager/utils/createServerArtifactTracker.mjs +83 -0
- package/manager/utils/createServerArtifactTracker.test.mjs +78 -0
- package/manager/utils/createServerEnv.mjs +39 -0
- package/manager/utils/filesHaveSameBytes.mjs +41 -0
- package/manager/utils/findBuildFilesOutsideWatch.mjs +59 -0
- package/manager/utils/findBuildFilesOutsideWatch.test.mjs +111 -0
- package/manager/utils/importFresh.mjs +46 -0
- package/manager/utils/importFresh.test.mjs +72 -0
- package/manager/utils/importFreshWorker.mjs +20 -0
- package/manager/utils/killTaggedBrowser.mjs +35 -0
- package/manager/utils/pagePathChanged.mjs +56 -0
- package/manager/utils/pruneRecordings.mjs +85 -0
- package/manager/utils/pruneRecordings.test.mjs +117 -0
- package/manager/utils/publishBuildDirectory.mjs +121 -0
- package/manager/utils/publishBuildDirectory.order.test.mjs +74 -0
- package/manager/utils/publishBuildDirectory.test.mjs +262 -0
- package/manager/utils/readBasePath.mjs +27 -0
- package/manager/utils/readBasePath.test.mjs +43 -0
- package/manager/utils/readBuildStatusWait.mjs +67 -0
- package/manager/utils/readManagerVersion.mjs +30 -0
- package/manager/utils/readManagerVersion.test.mjs +35 -0
- package/manager/utils/readPluginDefinitions.mjs +36 -0
- package/manager/utils/resolveDevAuthUrl.mjs +58 -0
- package/manager/utils/resolveDevAuthUrl.test.mjs +107 -0
- package/manager/utils/resolvePorts.mjs +49 -0
- package/manager/utils/setupWatcher.mjs +19 -11
- package/manager/utils/startRecordingPruner.mjs +45 -0
- package/manager/utils/startRecordingPruner.test.mjs +66 -0
- package/manager/utils/waitForChildExit.mjs +53 -0
- package/manager/utils/waitForChildExit.test.mjs +70 -0
- package/manager/utils/waitForServer.mjs +44 -0
- package/manager/utils/writeBuildStatus.mjs +1 -1
- package/manager/watchers/createPluginSourceIgnore.mjs +77 -0
- package/manager/watchers/envWatcher.mjs +9 -2
- package/manager/watchers/envWatcher.test.mjs +87 -0
- package/manager/watchers/listServerSidePackages.mjs +47 -0
- package/manager/watchers/listServerSidePackages.test.mjs +38 -0
- package/manager/watchers/lowdefyBuildWatcher.mjs +106 -22
- package/manager/watchers/lowdefyBuildWatcher.test.mjs +309 -0
- package/manager/watchers/pluginSourceWatcher.mjs +79 -0
- package/manager/watchers/pluginSourceWatcher.test.mjs +222 -0
- package/manager/watchers/restartRequestWatcher.mjs +71 -0
- package/manager/watchers/restartRequestWatcher.test.mjs +111 -0
- package/manager/watchers/selectLocalPluginPackages.mjs +48 -0
- package/manager/watchers/selectLocalPluginPackages.test.mjs +73 -0
- package/manager/watchers/serverPackageWatcher.mjs +38 -0
- package/package.json +61 -49
- package/package.original.json +61 -49
- package/src/app.js +131 -20
- package/src/html/renderDevPage.js +63 -11
- package/src/html/renderDevPage.test.mjs +138 -0
- package/src/lib/getRequestPath.js +29 -0
- package/src/middleware/createSameOriginGuard.js +37 -0
- package/src/middleware/createSameOriginGuard.test.mjs +102 -0
- package/src/middleware/errorHandler.js +51 -1
- package/src/middleware/errorHandler.test.mjs +196 -14
- package/src/middleware/isRebindingSafeHost.js +44 -0
- package/src/middleware/isRebindingSafeHost.test.mjs +32 -0
- package/src/middleware/localDevToolsOnly.js +38 -0
- package/src/middleware/localDevToolsOnly.test.mjs +61 -0
- package/src/middleware/noStore.js +27 -0
- package/src/middleware/noStore.test.mjs +48 -0
- package/src/middleware/requestTimeout.js +38 -0
- package/src/middleware/staleFlag.js +76 -0
- package/src/middleware/staleFlag.test.mjs +127 -0
- package/src/middleware/stampCaller.js +38 -0
- package/src/middleware/stampCaller.test.mjs +64 -0
- package/src/routes/agent.js +15 -3
- package/src/routes/auth.js +29 -20
- package/src/routes/auth.test.mjs +30 -56
- package/src/routes/clientError.js +13 -13
- package/src/routes/cron.js +3 -1
- package/src/routes/cronForward.js +4 -2
- package/src/routes/detached.js +22 -9
- package/src/routes/devInspect.js +17 -8
- package/src/routes/devInspect.test.mjs +44 -7
- package/src/routes/devRecording.js +113 -0
- package/src/routes/devRecording.test.mjs +253 -0
- package/src/routes/docs/buildStatus.js +10 -2
- package/src/routes/docs/buildStatus.test.mjs +46 -0
- package/src/routes/docs/content.js +5 -3
- package/src/routes/docs/evalOperator.js +15 -1
- package/src/routes/docs/events.js +50 -0
- package/src/routes/docs/events.test.mjs +117 -0
- package/src/routes/docs/find.js +9 -1
- package/src/routes/docs/icons.js +30 -0
- package/src/routes/docs/inspectState.js +16 -1
- package/src/routes/docs/journey.js +194 -0
- package/src/routes/docs/journey.test.mjs +401 -0
- package/src/routes/docs/journeyRefusals.test.mjs +71 -0
- package/src/routes/docs/loadState.js +4 -2
- package/src/routes/docs/mcp.js +34 -3
- package/src/routes/docs/mcp.test.mjs +130 -0
- package/src/routes/docs/mutants.js +78 -0
- package/src/routes/docs/mutants.test.mjs +180 -0
- package/src/routes/docs/parsePathParamsParam.js +41 -0
- package/src/routes/docs/parsePathParamsParam.test.mjs +42 -0
- package/src/routes/docs/parseUserParam.js +27 -21
- package/src/routes/docs/parseUserParam.test.mjs +17 -5
- package/src/routes/docs/restart.js +35 -0
- package/src/routes/docs/restart.test.mjs +64 -0
- package/src/routes/docs/runEndpoint.js +54 -0
- package/src/routes/docs/runEndpoint.test.mjs +165 -0
- package/src/routes/docs/runRequest.js +25 -8
- package/src/routes/docs/runRequest.test.mjs +92 -0
- package/src/routes/docs/screenshot.js +50 -2
- package/src/routes/docs/screenshot.test.mjs +117 -0
- package/src/routes/docs/search.js +15 -2
- package/src/routes/docs/snapshotState.js +17 -3
- package/src/routes/endpoints.js +27 -26
- package/src/routes/endpointsTextAnswer.test.mjs +77 -0
- package/src/routes/feedback.js +10 -13
- package/src/routes/jitPage.js +99 -35
- package/src/routes/jitPage.test.mjs +248 -0
- package/src/routes/mcp.js +82 -5
- package/src/routes/mcp.test.mjs +215 -0
- package/src/routes/mcpMemberToken.test.mjs +125 -0
- package/src/routes/mcpProtectedResourceMetadata.js +34 -0
- package/src/routes/mcpProtectedResourceMetadata.test.mjs +65 -0
- package/src/routes/mountDevPageRoutes.js +25 -0
- package/src/routes/mountDevPageRoutes.test.mjs +30 -0
- package/src/routes/mountOauthDiscovery.js +44 -0
- package/src/routes/mountOauthDiscovery.test.mjs +79 -0
- package/src/routes/normalizeMcpProtocolVersionHeader.js +46 -0
- package/src/routes/normalizeMcpProtocolVersionHeader.test.mjs +62 -0
- package/src/routes/reload.js +16 -0
- package/src/routes/request.js +6 -4
- package/src/routes/request.test.mjs +9 -5
- package/src/routes/requestBody.test.mjs +159 -0
- package/src/routes/usage.js +6 -2
- package/src/routes/user.js +27 -0
- package/src/routes/websocket.js +29 -8
- package/src/routes/websocket.test.mjs +65 -0
- package/src/routes/wellKnownFallback.js +30 -0
- package/src/routes/wellKnownFallback.test.mjs +32 -0
- package/src/websocket/devWebSocket.js +41 -26
- package/src/websocket/devWebSocket.test.mjs +157 -0
- package/vite.config.js +53 -23
- package/lib/server/auth/getDevSession.js +0 -70
- package/lib/server/auth/getDevSession.test.mjs +0 -138
- package/lib/server/auth/session.js +0 -39
- package/manager/processes/warnAuthUrlPortMismatch.mjs +0 -47
- package/manager/processes/warnAuthUrlPortMismatch.test.mjs +0 -93
- package/manager/watchers/moduleBuildWatcher.mjs +0 -79
- package/manager/watchers/serverArtifactWatcher.mjs +0 -120
- package/src/routes/apiPage.js +0 -37
|
@@ -0,0 +1,23 @@
|
|
|
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 { createSecretScrubber, getSecretsFromEnv } from '@lowdefy/node-utils';
|
|
18
|
+
|
|
19
|
+
// Dev logs stay unscrubbed, but _error reads this off the context like every
|
|
20
|
+
// other server package, so a thrown message behaves identically in every mode.
|
|
21
|
+
const scrubSecrets = createSecretScrubber({ secrets: getSecretsFromEnv() });
|
|
22
|
+
|
|
23
|
+
export default scrubSecrets;
|
|
@@ -0,0 +1,95 @@
|
|
|
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
|
+
// The loopback hosts the dev server's own mail capture listens on (LOWDEFY_DEV_SMTP_PORT).
|
|
20
|
+
const LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '::1']);
|
|
21
|
+
|
|
22
|
+
// An SMTP request whose connection points at the dev server's mail capture is the one request of
|
|
23
|
+
// an external type that is safe to run for real under a data session: nothing leaves the machine,
|
|
24
|
+
// and the journey's `email` step reads what it captures.
|
|
25
|
+
function isDevMailCapture({ connectionType, connection }) {
|
|
26
|
+
const port = process.env.LOWDEFY_DEV_SMTP_PORT;
|
|
27
|
+
if (connectionType !== 'SMTP' || type.isNone(port)) return false;
|
|
28
|
+
return LOOPBACK_HOSTS.has(connection?.host) && String(connection?.port) === String(port);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// What a sunk request carries: where it was aimed and what it asked for, never the connection's
|
|
32
|
+
// properties, which hold its credentials. Request properties are evaluated app config and
|
|
33
|
+
// payload; secrets are scrubbed.
|
|
34
|
+
function describeSunkRequest({ context, args, connectionType }) {
|
|
35
|
+
const scrub = context.scrubSecrets ?? ((value) => value);
|
|
36
|
+
return {
|
|
37
|
+
connectionId: args.connectionId,
|
|
38
|
+
connectionType,
|
|
39
|
+
requestId: args.requestId,
|
|
40
|
+
pageId: args.pageId ?? null,
|
|
41
|
+
endpointId: args.endpointId ?? null,
|
|
42
|
+
request: type.isUndefined(args.request)
|
|
43
|
+
? null
|
|
44
|
+
: JSON.parse(scrub(JSON.stringify(args.request))),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// The plugin connection map for a data session's requests. A connection type that declares
|
|
49
|
+
// meta.dataSet 'external' reaches an outside service and keeps its real target, so under a data
|
|
50
|
+
// session every one of its request resolvers is replaced by a sink. A single request type can
|
|
51
|
+
// declare meta.dataSet 'external' on its resolver too, and only that resolver is replaced, which
|
|
52
|
+
// suits a walled connection whose other request types read and write the walled database. A sunk
|
|
53
|
+
// request is recorded on the session (session.sunk) and answers null, and nothing reaches the service. This is the one
|
|
54
|
+
// place real mail and HTTP are stopped, in the process that would send them, so a branch a click
|
|
55
|
+
// never takes, or an id an operator computes, cannot reach out either. It leaves the resolver's
|
|
56
|
+
// schema and meta in place, so the request is still validated, read-checked and write-checked
|
|
57
|
+
// as for real. Types with no request resolvers (an Mcp config container) have nothing to sink
|
|
58
|
+
// and stay refused by the click rails. Returns a new map; the plugin map is never mutated.
|
|
59
|
+
function sinkExternalConnections({ context, connections, session }) {
|
|
60
|
+
const sunk = {};
|
|
61
|
+
Object.entries(connections).forEach(([connectionType, definition]) => {
|
|
62
|
+
const requests = definition?.requests;
|
|
63
|
+
if (!type.isObject(requests)) {
|
|
64
|
+
sunk[connectionType] = definition;
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const connectionExternal = definition?.meta?.dataSet === 'external';
|
|
68
|
+
let anySunk = false;
|
|
69
|
+
const sunkRequests = {};
|
|
70
|
+
Object.entries(requests).forEach(([requestType, resolver]) => {
|
|
71
|
+
// A request type can declare meta.dataSet 'external' itself, so a walled connection that
|
|
72
|
+
// holds database access can still have request types that reach an outside service sunk.
|
|
73
|
+
if (!connectionExternal && resolver?.meta?.dataSet !== 'external') {
|
|
74
|
+
sunkRequests[requestType] = resolver;
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
anySunk = true;
|
|
78
|
+
const sink = async (args) => {
|
|
79
|
+
if (isDevMailCapture({ connectionType, connection: args.connection })) {
|
|
80
|
+
return resolver(args);
|
|
81
|
+
}
|
|
82
|
+
session.sunk.push({
|
|
83
|
+
...describeSunkRequest({ context, args, connectionType }),
|
|
84
|
+
requestType,
|
|
85
|
+
});
|
|
86
|
+
return null;
|
|
87
|
+
};
|
|
88
|
+
sunkRequests[requestType] = Object.assign(sink, resolver);
|
|
89
|
+
});
|
|
90
|
+
sunk[connectionType] = anySunk ? { ...definition, requests: sunkRequests } : definition;
|
|
91
|
+
});
|
|
92
|
+
return sunk;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export default sinkExternalConnections;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { jest } from '@jest/globals';
|
|
18
|
+
|
|
19
|
+
import sinkExternalConnections from './sinkExternalConnections.js';
|
|
20
|
+
|
|
21
|
+
const realSend = jest.fn(async () => ({ sent: true }));
|
|
22
|
+
realSend.schema = { type: 'object' };
|
|
23
|
+
realSend.meta = { checkWrite: true };
|
|
24
|
+
const realGather = jest.fn(async () => ({ downloaded: true }));
|
|
25
|
+
realGather.schema = { type: 'object' };
|
|
26
|
+
realGather.meta = { checkRead: true, dataSet: 'external' };
|
|
27
|
+
const realRead = jest.fn(async () => ['doc']);
|
|
28
|
+
realRead.meta = { checkRead: true };
|
|
29
|
+
const realMongo = jest.fn(async () => ['row']);
|
|
30
|
+
|
|
31
|
+
const connections = {
|
|
32
|
+
SMTP: { schema: {}, meta: { dataSet: 'external' }, requests: { SMTPMailSend: realSend } },
|
|
33
|
+
MongoDBCollection: { meta: { dataSet: 'redirect' }, requests: { MongoDBFind: realMongo } },
|
|
34
|
+
Mcp: { meta: { dataSet: 'external' } },
|
|
35
|
+
Evidence: { meta: { dataSet: 'walled' }, requests: { Gather: realGather, Read: realRead } },
|
|
36
|
+
};
|
|
37
|
+
const context = { scrubSecrets: (value) => value.replace('s3cret', '[REDACTED]') };
|
|
38
|
+
|
|
39
|
+
let session;
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
jest.clearAllMocks();
|
|
42
|
+
delete process.env.LOWDEFY_DEV_SMTP_PORT;
|
|
43
|
+
session = { sunk: [] };
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
function args(overrides = {}) {
|
|
47
|
+
return {
|
|
48
|
+
connection: { host: 'smtp.example.com', port: 587, pass: 's3cret' },
|
|
49
|
+
connectionId: 'notifications-email',
|
|
50
|
+
requestId: 'send',
|
|
51
|
+
pageId: 'review',
|
|
52
|
+
request: { to: 'a@example.com', note: 's3cret' },
|
|
53
|
+
...overrides,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
test('an external request is recorded and answers null, and never reaches the real resolver', async () => {
|
|
58
|
+
const map = sinkExternalConnections({ context, connections, session });
|
|
59
|
+
const response = await map.SMTP.requests.SMTPMailSend(args());
|
|
60
|
+
expect(response).toBeNull();
|
|
61
|
+
expect(realSend).not.toHaveBeenCalled();
|
|
62
|
+
expect(session.sunk).toEqual([
|
|
63
|
+
{
|
|
64
|
+
connectionId: 'notifications-email',
|
|
65
|
+
connectionType: 'SMTP',
|
|
66
|
+
requestType: 'SMTPMailSend',
|
|
67
|
+
requestId: 'send',
|
|
68
|
+
pageId: 'review',
|
|
69
|
+
endpointId: null,
|
|
70
|
+
request: { to: 'a@example.com', note: '[REDACTED]' },
|
|
71
|
+
},
|
|
72
|
+
]);
|
|
73
|
+
// The connection's own properties, which hold its credentials, are never recorded.
|
|
74
|
+
expect(JSON.stringify(session.sunk)).not.toContain('smtp.example.com');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('a sunk resolver keeps the schema and meta of the real one', () => {
|
|
78
|
+
const map = sinkExternalConnections({ context, connections, session });
|
|
79
|
+
expect(map.SMTP.requests.SMTPMailSend.schema).toBe(realSend.schema);
|
|
80
|
+
expect(map.SMTP.requests.SMTPMailSend.meta).toBe(realSend.meta);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('redirect types, types without requests and the plugin map are left alone', () => {
|
|
84
|
+
const map = sinkExternalConnections({ context, connections, session });
|
|
85
|
+
expect(map.MongoDBCollection).toBe(connections.MongoDBCollection);
|
|
86
|
+
expect(map.Mcp).toBe(connections.Mcp);
|
|
87
|
+
expect(connections.SMTP.requests.SMTPMailSend).toBe(realSend);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('an SMTP request to the dev mail capture runs for real, any other host is sunk', async () => {
|
|
91
|
+
process.env.LOWDEFY_DEV_SMTP_PORT = '2525';
|
|
92
|
+
const map = sinkExternalConnections({ context, connections, session });
|
|
93
|
+
const capture = args({ connection: { host: '127.0.0.1', port: 2525 } });
|
|
94
|
+
expect(await map.SMTP.requests.SMTPMailSend(capture)).toEqual({ sent: true });
|
|
95
|
+
expect(realSend).toHaveBeenCalledTimes(1);
|
|
96
|
+
await map.SMTP.requests.SMTPMailSend(args({ connection: { host: '127.0.0.1', port: 587 } }));
|
|
97
|
+
await map.SMTP.requests.SMTPMailSend(
|
|
98
|
+
args({ connection: { host: 'smtp.example.com', port: 2525 } })
|
|
99
|
+
);
|
|
100
|
+
expect(realSend).toHaveBeenCalledTimes(1);
|
|
101
|
+
expect(session.sunk).toHaveLength(2);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('a request type that declares external is sunk on a walled connection, its siblings run for real', async () => {
|
|
105
|
+
const map = sinkExternalConnections({ context, connections, session });
|
|
106
|
+
const response = await map.Evidence.requests.Gather(
|
|
107
|
+
args({ connectionId: 'evidence', requestId: 'gather' })
|
|
108
|
+
);
|
|
109
|
+
expect(response).toBeNull();
|
|
110
|
+
expect(realGather).not.toHaveBeenCalled();
|
|
111
|
+
expect(session.sunk).toEqual([
|
|
112
|
+
{
|
|
113
|
+
connectionId: 'evidence',
|
|
114
|
+
connectionType: 'Evidence',
|
|
115
|
+
requestType: 'Gather',
|
|
116
|
+
requestId: 'gather',
|
|
117
|
+
pageId: 'review',
|
|
118
|
+
endpointId: null,
|
|
119
|
+
request: { to: 'a@example.com', note: '[REDACTED]' },
|
|
120
|
+
},
|
|
121
|
+
]);
|
|
122
|
+
expect(map.Evidence.requests.Gather.meta).toBe(realGather.meta);
|
|
123
|
+
expect(map.Evidence.meta).toBe(connections.Evidence.meta);
|
|
124
|
+
expect(map.Evidence.requests.Read).toBe(realRead);
|
|
125
|
+
expect(await map.Evidence.requests.Read(args())).toEqual(['doc']);
|
|
126
|
+
expect(session.sunk).toHaveLength(1);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('a connection with no external request type is returned as is, and the plugin map is not mutated', () => {
|
|
130
|
+
const map = sinkExternalConnections({ context, connections, session });
|
|
131
|
+
expect(map.MongoDBCollection).toBe(connections.MongoDBCollection);
|
|
132
|
+
expect(connections.Evidence.requests.Gather).toBe(realGather);
|
|
133
|
+
const plain = { Plain: { meta: { dataSet: 'walled' }, requests: { Read: realRead } } };
|
|
134
|
+
expect(sinkExternalConnections({ context, connections: plain, session }).Plain).toBe(plain.Plain);
|
|
135
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from 'fs';
|
|
18
|
+
import path from 'path';
|
|
19
|
+
|
|
20
|
+
const mapFiles = new Set(['keyMap.json', 'refMap.json']);
|
|
21
|
+
const jsMapFiles = new Set([
|
|
22
|
+
'plugins/operators/clientJsMap.js',
|
|
23
|
+
'plugins/operators/serverJsMap.js',
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
function isMapFile(filePath) {
|
|
27
|
+
return mapFiles.has(filePath) || filePath.startsWith('jitMaps/');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function readLiveKeyPrefix({ buildDirectory }) {
|
|
31
|
+
return JSON.parse(fs.readFileSync(path.join(buildDirectory, 'idCounter.json'), 'utf8')).prefix;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// A config rebuild publishes new maps while a page build that started against the
|
|
35
|
+
// previous build may still be running. That build's keyMap and refMap describe the
|
|
36
|
+
// previous build, so writing them would replace the new maps, and errors would resolve
|
|
37
|
+
// against the wrong build until the next rebuild. Its jitMaps/ files would outlive the
|
|
38
|
+
// publish that cleared the old build's. The build's key prefix names the build
|
|
39
|
+
// a context was created from.
|
|
40
|
+
//
|
|
41
|
+
// The JS map files are written whole from a context's jsMap, so a page build on
|
|
42
|
+
// a context that is no longer the kept one (a config publish, or the map budget,
|
|
43
|
+
// discarded it) would replace the entries pages built on the newer context
|
|
44
|
+
// added. Pages built on the discarded context are rebuilt on their next request
|
|
45
|
+
// or API call, which writes their entries again.
|
|
46
|
+
function skipStaleMapWrites({ buildDirectory, context, keyPrefix, isKeptContext }) {
|
|
47
|
+
const writeBuildArtifact = context.writeBuildArtifact;
|
|
48
|
+
context.writeBuildArtifact = async (filePath, content, options) => {
|
|
49
|
+
if (isMapFile(filePath) && readLiveKeyPrefix({ buildDirectory }) !== keyPrefix) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (
|
|
53
|
+
jsMapFiles.has(filePath) &&
|
|
54
|
+
(!isKeptContext() || readLiveKeyPrefix({ buildDirectory }) !== keyPrefix)
|
|
55
|
+
) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
await writeBuildArtifact(filePath, content, options);
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export default skipStaleMapWrites;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from 'fs';
|
|
18
|
+
import os from 'os';
|
|
19
|
+
import path from 'path';
|
|
20
|
+
|
|
21
|
+
import { jest } from '@jest/globals';
|
|
22
|
+
|
|
23
|
+
import skipStaleMapWrites from './skipStaleMapWrites.js';
|
|
24
|
+
|
|
25
|
+
let buildDirectory;
|
|
26
|
+
|
|
27
|
+
function publishBuild(prefix) {
|
|
28
|
+
fs.writeFileSync(
|
|
29
|
+
path.join(buildDirectory, 'idCounter.json'),
|
|
30
|
+
JSON.stringify({ prefix, counter: 10 })
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
buildDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-stale-maps-'));
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
fs.rmSync(buildDirectory, { recursive: true });
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('a page build writes its maps while its config build is the live one', async () => {
|
|
43
|
+
publishBuild('a1b2_');
|
|
44
|
+
const writeBuildArtifact = jest.fn();
|
|
45
|
+
const context = { writeBuildArtifact };
|
|
46
|
+
skipStaleMapWrites({ buildDirectory, context, keyPrefix: 'a1b2_', isKeptContext: () => true });
|
|
47
|
+
|
|
48
|
+
await context.writeBuildArtifact('keyMap.json', '{}');
|
|
49
|
+
await context.writeBuildArtifact('refMap.json', '{}');
|
|
50
|
+
|
|
51
|
+
expect(writeBuildArtifact.mock.calls).toEqual([
|
|
52
|
+
['keyMap.json', '{}'],
|
|
53
|
+
['refMap.json', '{}'],
|
|
54
|
+
]);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('a page build started before a config rebuild does not replace the new maps', async () => {
|
|
58
|
+
publishBuild('a1b2_');
|
|
59
|
+
const writeBuildArtifact = jest.fn();
|
|
60
|
+
const context = { writeBuildArtifact };
|
|
61
|
+
skipStaleMapWrites({ buildDirectory, context, keyPrefix: 'a1b2_', isKeptContext: () => true });
|
|
62
|
+
|
|
63
|
+
publishBuild('c3d4_');
|
|
64
|
+
await context.writeBuildArtifact('keyMap.json', '{}');
|
|
65
|
+
await context.writeBuildArtifact('refMap.json', '{}');
|
|
66
|
+
await context.writeBuildArtifact('pages/home.json', '{}');
|
|
67
|
+
|
|
68
|
+
expect(writeBuildArtifact.mock.calls).toEqual([['pages/home.json', '{}']]);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('a page build started before a config rebuild writes no jitMaps file', async () => {
|
|
72
|
+
publishBuild('a1b2_');
|
|
73
|
+
const writeBuildArtifact = jest.fn();
|
|
74
|
+
const context = { writeBuildArtifact };
|
|
75
|
+
skipStaleMapWrites({ buildDirectory, context, keyPrefix: 'a1b2_', isKeptContext: () => true });
|
|
76
|
+
|
|
77
|
+
await context.writeBuildArtifact('jitMaps/abc123-1-1.json', '{}');
|
|
78
|
+
publishBuild('c3d4_');
|
|
79
|
+
await context.writeBuildArtifact('jitMaps/abc123-1-2.json', '{}');
|
|
80
|
+
|
|
81
|
+
expect(writeBuildArtifact.mock.calls).toEqual([['jitMaps/abc123-1-1.json', '{}']]);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('a page build on a context that is no longer kept does not write the JS maps', async () => {
|
|
85
|
+
publishBuild('a1b2_');
|
|
86
|
+
const writeBuildArtifact = jest.fn();
|
|
87
|
+
const context = { writeBuildArtifact };
|
|
88
|
+
let kept = true;
|
|
89
|
+
skipStaleMapWrites({ buildDirectory, context, keyPrefix: 'a1b2_', isKeptContext: () => kept });
|
|
90
|
+
|
|
91
|
+
await context.writeBuildArtifact('plugins/operators/serverJsMap.js', 'a');
|
|
92
|
+
kept = false;
|
|
93
|
+
await context.writeBuildArtifact('plugins/operators/serverJsMap.js', 'b');
|
|
94
|
+
await context.writeBuildArtifact('plugins/operators/clientJsMap.js', 'b');
|
|
95
|
+
await context.writeBuildArtifact('pages/home.json', '{}');
|
|
96
|
+
|
|
97
|
+
expect(writeBuildArtifact.mock.calls).toEqual([
|
|
98
|
+
['plugins/operators/serverJsMap.js', 'a'],
|
|
99
|
+
['pages/home.json', '{}'],
|
|
100
|
+
]);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('a page build started before a config rebuild does not write the JS maps', async () => {
|
|
104
|
+
publishBuild('a1b2_');
|
|
105
|
+
const writeBuildArtifact = jest.fn();
|
|
106
|
+
const context = { writeBuildArtifact };
|
|
107
|
+
skipStaleMapWrites({ buildDirectory, context, keyPrefix: 'a1b2_', isKeptContext: () => true });
|
|
108
|
+
|
|
109
|
+
publishBuild('c3d4_');
|
|
110
|
+
await context.writeBuildArtifact('plugins/operators/serverJsMap.js', 'a');
|
|
111
|
+
|
|
112
|
+
expect(writeBuildArtifact.mock.calls).toEqual([]);
|
|
113
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { type } from '@lowdefy/helpers';
|
|
18
|
+
|
|
19
|
+
import readInstanceRecord from './readInstanceRecord.js';
|
|
20
|
+
|
|
21
|
+
// Vite can evaluate the app entry more than once (an SSR reload, the
|
|
22
|
+
// websocket upgrade path), and each evaluation calls this.
|
|
23
|
+
const STARTED = Symbol.for('lowdefy.server-dev.idleGc');
|
|
24
|
+
|
|
25
|
+
/*
|
|
26
|
+
Hands the child's V8 slack back once the server goes quiet: one full GC per
|
|
27
|
+
quiet period, when the activity the manager records in the instance record
|
|
28
|
+
(lastActivityAt, activeRequests, building) says nothing has used the server
|
|
29
|
+
for quietMs and nothing is in flight. It runs again only after activity
|
|
30
|
+
resumes (a newer lastActivityAt). A child started without --expose-gc has no
|
|
31
|
+
globalThis.gc and does nothing.
|
|
32
|
+
*/
|
|
33
|
+
function startIdleGc({
|
|
34
|
+
configDirectory,
|
|
35
|
+
gc = globalThis.gc,
|
|
36
|
+
readInstance = readInstanceRecord,
|
|
37
|
+
now = Date.now,
|
|
38
|
+
pollMs = 5000,
|
|
39
|
+
quietMs = 10000,
|
|
40
|
+
}) {
|
|
41
|
+
if (globalThis[STARTED] === true || !type.isFunction(gc)) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
globalThis[STARTED] = true;
|
|
45
|
+
let collectedFor = null;
|
|
46
|
+
const interval = setInterval(() => {
|
|
47
|
+
const record = readInstance({ configDirectory });
|
|
48
|
+
if (type.isNone(record) || !type.isString(record.lastActivityAt)) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (record.building === true || (record.activeRequests ?? 0) > 0) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (record.lastActivityAt === collectedFor) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (now() - Date.parse(record.lastActivityAt) < quietMs) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
collectedFor = record.lastActivityAt;
|
|
61
|
+
gc();
|
|
62
|
+
}, pollMs);
|
|
63
|
+
// Never the reason the child stays up.
|
|
64
|
+
interval.unref();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export default startIdleGc;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { jest } from '@jest/globals';
|
|
18
|
+
|
|
19
|
+
import startIdleGc from './startIdleGc.js';
|
|
20
|
+
|
|
21
|
+
const STARTED = Symbol.for('lowdefy.server-dev.idleGc');
|
|
22
|
+
const START = Date.parse('2026-10-03T10:00:00.000Z');
|
|
23
|
+
|
|
24
|
+
let clock;
|
|
25
|
+
let record;
|
|
26
|
+
let gc;
|
|
27
|
+
|
|
28
|
+
function start(options = {}) {
|
|
29
|
+
startIdleGc({
|
|
30
|
+
configDirectory: '/apps/tenant',
|
|
31
|
+
gc,
|
|
32
|
+
readInstance: () => record,
|
|
33
|
+
now: () => clock,
|
|
34
|
+
...options,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function tick(ms) {
|
|
39
|
+
clock += ms;
|
|
40
|
+
jest.advanceTimersByTime(ms);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
// performance is not redefinable on this Node, and nothing here reads it.
|
|
45
|
+
jest.useFakeTimers({ doNotFake: ['performance'] });
|
|
46
|
+
delete globalThis[STARTED];
|
|
47
|
+
clock = START;
|
|
48
|
+
gc = jest.fn();
|
|
49
|
+
record = {
|
|
50
|
+
lastActivityAt: new Date(START).toISOString(),
|
|
51
|
+
activeRequests: 0,
|
|
52
|
+
building: false,
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
afterEach(() => {
|
|
57
|
+
jest.clearAllTimers();
|
|
58
|
+
jest.useRealTimers();
|
|
59
|
+
delete globalThis[STARTED];
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('startIdleGc collects once the server has been quiet for 10 s', () => {
|
|
63
|
+
start();
|
|
64
|
+
|
|
65
|
+
tick(5000);
|
|
66
|
+
expect(gc).not.toHaveBeenCalled();
|
|
67
|
+
tick(5000);
|
|
68
|
+
expect(gc).toHaveBeenCalledTimes(1);
|
|
69
|
+
tick(60_000);
|
|
70
|
+
expect(gc).toHaveBeenCalledTimes(1);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('startIdleGc does not collect while a request is in flight', () => {
|
|
74
|
+
record.activeRequests = 1;
|
|
75
|
+
start();
|
|
76
|
+
|
|
77
|
+
tick(60_000);
|
|
78
|
+
|
|
79
|
+
expect(gc).not.toHaveBeenCalled();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('startIdleGc does not collect while a build runs', () => {
|
|
83
|
+
record.building = true;
|
|
84
|
+
start();
|
|
85
|
+
|
|
86
|
+
tick(60_000);
|
|
87
|
+
|
|
88
|
+
expect(gc).not.toHaveBeenCalled();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('startIdleGc collects again only after activity resumes', () => {
|
|
92
|
+
start();
|
|
93
|
+
tick(10_000);
|
|
94
|
+
expect(gc).toHaveBeenCalledTimes(1);
|
|
95
|
+
|
|
96
|
+
record = { ...record, lastActivityAt: new Date(clock).toISOString() };
|
|
97
|
+
tick(5000);
|
|
98
|
+
expect(gc).toHaveBeenCalledTimes(1);
|
|
99
|
+
tick(5000);
|
|
100
|
+
|
|
101
|
+
expect(gc).toHaveBeenCalledTimes(2);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('startIdleGc does nothing when the instance record has no activity', () => {
|
|
105
|
+
record = null;
|
|
106
|
+
start();
|
|
107
|
+
|
|
108
|
+
tick(60_000);
|
|
109
|
+
|
|
110
|
+
expect(gc).not.toHaveBeenCalled();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('startIdleGc does nothing when the child was started without gc exposed', () => {
|
|
114
|
+
// The gc default is globalThis.gc, which every later test file in a jest worker has once
|
|
115
|
+
// createIdleGc.test.mjs has set --expose-gc in it.
|
|
116
|
+
const exposedGc = globalThis.gc;
|
|
117
|
+
delete globalThis.gc;
|
|
118
|
+
gc = undefined;
|
|
119
|
+
try {
|
|
120
|
+
start();
|
|
121
|
+
|
|
122
|
+
expect(globalThis[STARTED]).toBeUndefined();
|
|
123
|
+
expect(jest.getTimerCount()).toBe(0);
|
|
124
|
+
} finally {
|
|
125
|
+
if (exposedGc !== undefined) {
|
|
126
|
+
globalThis.gc = exposedGc;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('startIdleGc starts one interval however often the app entry is evaluated', () => {
|
|
132
|
+
start();
|
|
133
|
+
start();
|
|
134
|
+
|
|
135
|
+
expect(jest.getTimerCount()).toBe(1);
|
|
136
|
+
tick(10_000);
|
|
137
|
+
expect(gc).toHaveBeenCalledTimes(1);
|
|
138
|
+
});
|