@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,343 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from 'node:fs';
|
|
18
|
+
import os from 'node:os';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import { jest } from '@jest/globals';
|
|
21
|
+
import { MongoClient, ObjectId } from 'mongodb';
|
|
22
|
+
import { parseDataSet } from '@lowdefy/node-utils';
|
|
23
|
+
|
|
24
|
+
import dataSessionRegistry from './dataSessionRegistry.js';
|
|
25
|
+
import getDataStore from './getDataStore.js';
|
|
26
|
+
import openDataSession from './openDataSession.js';
|
|
27
|
+
import readDataSession from './readDataSession.js';
|
|
28
|
+
import { journeyActorToken } from '../../server/auth/journeyActor.js';
|
|
29
|
+
|
|
30
|
+
jest.setTimeout(120000);
|
|
31
|
+
|
|
32
|
+
let store;
|
|
33
|
+
let client;
|
|
34
|
+
|
|
35
|
+
beforeAll(async () => {
|
|
36
|
+
store = await getDataStore();
|
|
37
|
+
client = new MongoClient(store.uri);
|
|
38
|
+
await client.connect();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
afterAll(async () => {
|
|
42
|
+
await client.close();
|
|
43
|
+
await store.stop();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
function makeDataSet(overrides) {
|
|
47
|
+
return {
|
|
48
|
+
name: 'sample',
|
|
49
|
+
fixtures: {},
|
|
50
|
+
generated: {},
|
|
51
|
+
indexes: {},
|
|
52
|
+
users: {},
|
|
53
|
+
collections: {},
|
|
54
|
+
...overrides,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function sessionDb(session) {
|
|
59
|
+
return client.db(session.databaseName);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function cookieFor(id) {
|
|
63
|
+
return `lowdefy_journey_data=${journeyActorToken}.${id}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function listDatabaseNames() {
|
|
67
|
+
const { databases } = await client.db('admin').admin().listDatabases({ nameOnly: true });
|
|
68
|
+
return databases.map((database) => database.name);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
test('openDataSession creates indexes before documents, so a fixture duplicating a unique key fails naming both', async () => {
|
|
72
|
+
const dataSet = makeDataSet({
|
|
73
|
+
collections: { tickets: 'tickets' },
|
|
74
|
+
indexes: { tickets: [{ key: { number: 1 }, name: 'number_1', unique: true }] },
|
|
75
|
+
fixtures: {
|
|
76
|
+
tickets: [
|
|
77
|
+
{ _id: 'f0', number: 7 },
|
|
78
|
+
{ _id: 'f1', number: 7 },
|
|
79
|
+
],
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
await expect(openDataSession({ dataSet })).rejects.toThrow(
|
|
83
|
+
'Data set "sample" fixture tickets[1] breaks unique index "number_1": duplicate key {"number":7} held by document _id "f0".'
|
|
84
|
+
);
|
|
85
|
+
expect([...dataSessionRegistry.values()].filter((s) => s.name === 'sample')).toEqual([]);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('openDataSession drops expireAfterSeconds, creates a text index and lets a later index replace one with the same key', async () => {
|
|
89
|
+
const dataSet = makeDataSet({
|
|
90
|
+
collections: { events: 'events', 'events-archive': 'events' },
|
|
91
|
+
fixtures: {
|
|
92
|
+
events: [{ _id: 'e1', title: 'hello', created: { '~d': '2000-01-01T00:00:00.000Z' } }],
|
|
93
|
+
},
|
|
94
|
+
indexes: {
|
|
95
|
+
events: [
|
|
96
|
+
{ key: { created: 1 }, name: 'created_ttl', expireAfterSeconds: 60 },
|
|
97
|
+
{
|
|
98
|
+
key: { _fts: 'text', _ftsx: 1 },
|
|
99
|
+
name: 'title_text',
|
|
100
|
+
weights: { title: 1 },
|
|
101
|
+
default_language: 'english',
|
|
102
|
+
language_override: 'language',
|
|
103
|
+
textIndexVersion: 3,
|
|
104
|
+
},
|
|
105
|
+
{ key: { organizationId: 1 }, name: 'org_1' },
|
|
106
|
+
],
|
|
107
|
+
'events-archive': [{ key: { organizationId: 1 }, name: 'org_unique', unique: true }],
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
const { session, close } = await openDataSession({ dataSet });
|
|
111
|
+
try {
|
|
112
|
+
const indexes = await sessionDb(session).collection('events').indexes();
|
|
113
|
+
const byName = Object.fromEntries(indexes.map((index) => [index.name, index]));
|
|
114
|
+
expect(byName.created_ttl.expireAfterSeconds).toBeUndefined();
|
|
115
|
+
expect(byName.title_text.weights).toEqual({ title: 1 });
|
|
116
|
+
expect(byName.org_1).toBeUndefined();
|
|
117
|
+
expect(byName.org_unique.unique).toBe(true);
|
|
118
|
+
const found = await sessionDb(session)
|
|
119
|
+
.collection('events')
|
|
120
|
+
.find({ $text: { $search: 'hello' } })
|
|
121
|
+
.toArray();
|
|
122
|
+
expect(found.map((document) => document._id)).toEqual(['e1']);
|
|
123
|
+
} finally {
|
|
124
|
+
await close();
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('openDataSession lets a later fixture replace one with the same _id and revives dates and ObjectIds', async () => {
|
|
129
|
+
const oid = new ObjectId();
|
|
130
|
+
const dataSet = makeDataSet({
|
|
131
|
+
collections: { tickets: 'tickets', 'tickets-archive': 'tickets' },
|
|
132
|
+
fixtures: {
|
|
133
|
+
tickets: [
|
|
134
|
+
{ _id: 't1', title: 'first' },
|
|
135
|
+
{ _id: 't1', title: 'from fixture' },
|
|
136
|
+
],
|
|
137
|
+
'tickets-archive': [
|
|
138
|
+
{
|
|
139
|
+
title: 'no id',
|
|
140
|
+
due: { '~d': '2026-01-02T00:00:00.000Z' },
|
|
141
|
+
ref: { _oid: oid.toHexString() },
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
const { session, close } = await openDataSession({ dataSet });
|
|
147
|
+
try {
|
|
148
|
+
const tickets = sessionDb(session).collection('tickets');
|
|
149
|
+
expect(await tickets.findOne({ _id: 't1' })).toEqual({ _id: 't1', title: 'from fixture' });
|
|
150
|
+
const noId = await tickets.findOne({ title: 'no id' });
|
|
151
|
+
expect(noId.due).toEqual(new Date('2026-01-02T00:00:00.000Z'));
|
|
152
|
+
expect(noId.ref).toEqual(oid);
|
|
153
|
+
expect(await tickets.countDocuments()).toBe(2);
|
|
154
|
+
} finally {
|
|
155
|
+
await close();
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('openDataSession loads fixtures and generated documents together, with generated dates as Dates', async () => {
|
|
160
|
+
const configDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-generate-'));
|
|
161
|
+
try {
|
|
162
|
+
fs.mkdirSync(path.join(configDirectory, 'tests', 'data'), { recursive: true });
|
|
163
|
+
fs.writeFileSync(
|
|
164
|
+
path.join(configDirectory, 'tests', 'data', 'invoices.yaml'),
|
|
165
|
+
[
|
|
166
|
+
'fixtures:',
|
|
167
|
+
' customers_db:',
|
|
168
|
+
' - { _id: c-fixture, name: Fixture customer }',
|
|
169
|
+
'generate:',
|
|
170
|
+
' seed: 7',
|
|
171
|
+
' customers_db:',
|
|
172
|
+
' count: 4',
|
|
173
|
+
' fields:',
|
|
174
|
+
' name: { company: true }',
|
|
175
|
+
' invoices_db:',
|
|
176
|
+
' count: 30',
|
|
177
|
+
' fields:',
|
|
178
|
+
' _id: { sequence: { prefix: inv-, start: 1 } }',
|
|
179
|
+
' status: { oneOf: [draft, sent, paid], weights: [1, 2, 5] }',
|
|
180
|
+
' issued: { date: { from: 2026-01-01, to: 2026-09-30 } }',
|
|
181
|
+
' customerId: { ref: customers_db }',
|
|
182
|
+
'',
|
|
183
|
+
].join('\n')
|
|
184
|
+
);
|
|
185
|
+
const parsed = await parseDataSet({ configDirectory, name: 'invoices' });
|
|
186
|
+
const dataSet = makeDataSet({
|
|
187
|
+
...parsed,
|
|
188
|
+
name: 'sample',
|
|
189
|
+
collections: { customers_db: 'customers', invoices_db: 'invoices' },
|
|
190
|
+
});
|
|
191
|
+
const { session, close } = await openDataSession({ dataSet });
|
|
192
|
+
try {
|
|
193
|
+
const customers = await sessionDb(session)
|
|
194
|
+
.collection('customers')
|
|
195
|
+
.find({})
|
|
196
|
+
.sort({ _id: 1 })
|
|
197
|
+
.toArray();
|
|
198
|
+
expect(customers.map(({ _id }) => _id)).toEqual([
|
|
199
|
+
'c-fixture',
|
|
200
|
+
'customers_db-1',
|
|
201
|
+
'customers_db-2',
|
|
202
|
+
'customers_db-3',
|
|
203
|
+
'customers_db-4',
|
|
204
|
+
]);
|
|
205
|
+
const invoices = await sessionDb(session).collection('invoices').find({}).toArray();
|
|
206
|
+
expect(invoices).toHaveLength(30);
|
|
207
|
+
expect(invoices[0]._id).toEqual('inv-1');
|
|
208
|
+
expect(invoices[0].issued).toBeInstanceOf(Date);
|
|
209
|
+
const customerIds = customers.map(({ _id }) => _id);
|
|
210
|
+
invoices.forEach((invoice) => expect(customerIds).toContain(invoice.customerId));
|
|
211
|
+
expect(
|
|
212
|
+
await sessionDb(session).collection('invoices').countDocuments({ status: 'paid' })
|
|
213
|
+
).toEqual(parsed.generated.invoices_db.filter(({ status }) => status === 'paid').length);
|
|
214
|
+
} finally {
|
|
215
|
+
await close();
|
|
216
|
+
}
|
|
217
|
+
} finally {
|
|
218
|
+
fs.rmSync(configDirectory, { recursive: true, force: true });
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test('openDataSession fails naming the generated document that breaks a unique index', async () => {
|
|
223
|
+
const dataSet = makeDataSet({
|
|
224
|
+
collections: { tickets: 'tickets' },
|
|
225
|
+
indexes: { tickets: [{ key: { status: 1 }, name: 'status_1', unique: true }] },
|
|
226
|
+
generated: {
|
|
227
|
+
tickets: [
|
|
228
|
+
{ _id: 'g1', status: 'open' },
|
|
229
|
+
{ _id: 'g2', status: 'closed' },
|
|
230
|
+
{ _id: 'g3', status: 'open' },
|
|
231
|
+
],
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
await expect(openDataSession({ dataSet })).rejects.toThrow(
|
|
235
|
+
'Data set "sample" generated tickets[2] (_id "g3") breaks unique index "status_1": duplicate key { status: "open" }.'
|
|
236
|
+
);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test('a session database runs a transaction and opens a change stream', async () => {
|
|
240
|
+
const dataSet = makeDataSet({
|
|
241
|
+
collections: { tickets: 'tickets' },
|
|
242
|
+
fixtures: { tickets: [{ _id: 't1' }] },
|
|
243
|
+
});
|
|
244
|
+
const { session, close } = await openDataSession({ dataSet });
|
|
245
|
+
const sessionClient = new MongoClient(session.databaseUri);
|
|
246
|
+
await sessionClient.connect();
|
|
247
|
+
try {
|
|
248
|
+
const tickets = sessionClient.db(session.databaseName).collection('tickets');
|
|
249
|
+
const stream = tickets.watch();
|
|
250
|
+
const next = stream.next();
|
|
251
|
+
const mongoSession = sessionClient.startSession();
|
|
252
|
+
await mongoSession.withTransaction(async () => {
|
|
253
|
+
await tickets.insertOne({ _id: 't2' }, { session: mongoSession });
|
|
254
|
+
});
|
|
255
|
+
await mongoSession.endSession();
|
|
256
|
+
const change = await next;
|
|
257
|
+
expect(change.documentKey).toEqual({ _id: 't2' });
|
|
258
|
+
await stream.close();
|
|
259
|
+
expect(await tickets.countDocuments()).toBe(2);
|
|
260
|
+
} finally {
|
|
261
|
+
await sessionClient.close();
|
|
262
|
+
await close();
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test('two sessions on one data set are isolated, and close() drops the database', async () => {
|
|
267
|
+
const dataSet = makeDataSet({
|
|
268
|
+
collections: { tickets: 'tickets' },
|
|
269
|
+
fixtures: { tickets: [{ _id: 't1' }] },
|
|
270
|
+
});
|
|
271
|
+
const first = await openDataSession({ dataSet });
|
|
272
|
+
const second = await openDataSession({ dataSet });
|
|
273
|
+
expect(first.session.databaseName).not.toEqual(second.session.databaseName);
|
|
274
|
+
await sessionDb(first.session).collection('tickets').insertOne({ _id: 'written' });
|
|
275
|
+
expect(await sessionDb(second.session).collection('tickets').countDocuments()).toBe(1);
|
|
276
|
+
await first.close();
|
|
277
|
+
await second.close();
|
|
278
|
+
const names = await listDatabaseNames();
|
|
279
|
+
expect(names).not.toContain(first.session.databaseName);
|
|
280
|
+
expect(names).not.toContain(second.session.databaseName);
|
|
281
|
+
expect(first.session.state).toEqual('closed');
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
test('close() waits for background work added to the session', async () => {
|
|
285
|
+
const { session, close } = await openDataSession({ dataSet: makeDataSet({}) });
|
|
286
|
+
let release;
|
|
287
|
+
const work = new Promise((resolve) => {
|
|
288
|
+
release = resolve;
|
|
289
|
+
});
|
|
290
|
+
session.work.add(work);
|
|
291
|
+
work.then(() => session.work.delete(work));
|
|
292
|
+
let closed = false;
|
|
293
|
+
const closing = close().then(() => {
|
|
294
|
+
closed = true;
|
|
295
|
+
});
|
|
296
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
297
|
+
expect(closed).toBe(false);
|
|
298
|
+
expect(session.state).toEqual('closing');
|
|
299
|
+
release();
|
|
300
|
+
await closing;
|
|
301
|
+
expect(closed).toBe(true);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test('close() drops after 30 s with a warning when background work never settles', async () => {
|
|
305
|
+
const { session, close } = await openDataSession({ dataSet: makeDataSet({}) });
|
|
306
|
+
await sessionDb(session).collection('kept').insertOne({ _id: 1 });
|
|
307
|
+
const warn = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
308
|
+
session.work.add(new Promise(() => {}));
|
|
309
|
+
jest.useFakeTimers({ doNotFake: ['nextTick', 'setImmediate', 'queueMicrotask', 'performance'] });
|
|
310
|
+
let warnings;
|
|
311
|
+
try {
|
|
312
|
+
const closing = close();
|
|
313
|
+
jest.advanceTimersByTime(30000);
|
|
314
|
+
jest.useRealTimers();
|
|
315
|
+
await closing;
|
|
316
|
+
warnings = warn.mock.calls.map(([message]) => message);
|
|
317
|
+
} finally {
|
|
318
|
+
jest.useRealTimers();
|
|
319
|
+
warn.mockRestore();
|
|
320
|
+
}
|
|
321
|
+
expect(warnings).toEqual([expect.stringContaining('Data set "sample"')]);
|
|
322
|
+
expect(session.state).toEqual('closed');
|
|
323
|
+
expect(await listDatabaseNames()).not.toContain(session.databaseName);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test('openDataSession sweeps an ld_ database no open session owns', async () => {
|
|
327
|
+
await client.db('ld_orphan00000').collection('left').insertOne({ _id: 1 });
|
|
328
|
+
await client.db('not_a_session').collection('kept').insertOne({ _id: 1 });
|
|
329
|
+
const { close } = await openDataSession({ dataSet: makeDataSet({}) });
|
|
330
|
+
const names = await listDatabaseNames();
|
|
331
|
+
expect(names).not.toContain('ld_orphan00000');
|
|
332
|
+
expect(names).toContain('not_a_session');
|
|
333
|
+
await close();
|
|
334
|
+
await client.db('not_a_session').dropDatabase();
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
test('readDataSession returns the open session, and { ended } once it closes', async () => {
|
|
338
|
+
const { id, cookie, session, close } = await openDataSession({ dataSet: makeDataSet({}) });
|
|
339
|
+
expect(cookie).toEqual(id);
|
|
340
|
+
expect(readDataSession(cookieFor(id))).toBe(session);
|
|
341
|
+
await close();
|
|
342
|
+
expect(readDataSession(cookieFor(id))).toEqual({ ended: id });
|
|
343
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
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 crypto from 'node:crypto';
|
|
18
|
+
import net from 'node:net';
|
|
19
|
+
|
|
20
|
+
// The IANA dynamic range. The store binds above the ports apps usually take (3000 and up), so an
|
|
21
|
+
// app the developer starts while it runs never finds its port held by mongod.
|
|
22
|
+
const MIN_PORT = 49152;
|
|
23
|
+
const MAX_PORT = 65535;
|
|
24
|
+
const MAX_TRIES = 20;
|
|
25
|
+
|
|
26
|
+
function isPortFree(port) {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
const server = net.createServer();
|
|
29
|
+
server.unref();
|
|
30
|
+
server.once('error', (error) => {
|
|
31
|
+
// Windows refuses a port inside a range it reserves (Hyper-V, WinNAT) with EACCES, and those
|
|
32
|
+
// ranges sit inside the dynamic range, so such a port is taken like one in use.
|
|
33
|
+
if (error.code === 'EADDRINUSE' || error.code === 'EACCES') {
|
|
34
|
+
resolve(false);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
reject(error);
|
|
38
|
+
});
|
|
39
|
+
server.listen(port, '127.0.0.1', () => {
|
|
40
|
+
server.close(() => resolve(true));
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function pickDataStorePort() {
|
|
46
|
+
for (let tries = 0; tries < MAX_TRIES; tries += 1) {
|
|
47
|
+
const port = crypto.randomInt(MIN_PORT, MAX_PORT + 1);
|
|
48
|
+
if (await isPortFree(port)) {
|
|
49
|
+
return port;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
throw new Error(
|
|
53
|
+
`No free port between ${MIN_PORT} and ${MAX_PORT} for the journey data store after ${MAX_TRIES} tries.`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export { MIN_PORT };
|
|
58
|
+
export default pickDataStorePort;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { EventEmitter } from 'node:events';
|
|
18
|
+
import { jest } from '@jest/globals';
|
|
19
|
+
|
|
20
|
+
const listenErrors = [];
|
|
21
|
+
|
|
22
|
+
function createServer() {
|
|
23
|
+
const server = new EventEmitter();
|
|
24
|
+
server.unref = () => {};
|
|
25
|
+
server.close = (callback) => callback();
|
|
26
|
+
server.listen = (port, host, callback) => {
|
|
27
|
+
const code = listenErrors.shift();
|
|
28
|
+
if (code === undefined) {
|
|
29
|
+
callback();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
server.emit('error', Object.assign(new Error(`listen ${code}`), { code }));
|
|
33
|
+
};
|
|
34
|
+
return server;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
jest.unstable_mockModule('node:net', () => ({ default: { createServer } }));
|
|
38
|
+
|
|
39
|
+
const { default: pickDataStorePort, MIN_PORT } = await import('./pickDataStorePort.js');
|
|
40
|
+
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
listenErrors.length = 0;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('pickDataStorePort tries another port when one is in use or reserved by Windows', async () => {
|
|
46
|
+
listenErrors.push('EADDRINUSE', 'EACCES');
|
|
47
|
+
const port = await pickDataStorePort();
|
|
48
|
+
expect(port).toBeGreaterThanOrEqual(MIN_PORT);
|
|
49
|
+
expect(port).toBeLessThanOrEqual(65535);
|
|
50
|
+
expect(listenErrors).toEqual([]);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('pickDataStorePort rejects on a listen error that is not about the port', async () => {
|
|
54
|
+
listenErrors.push('EPERM');
|
|
55
|
+
await expect(pickDataStorePort()).rejects.toThrow('listen EPERM');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('pickDataStorePort gives up after twenty ports that cannot be taken', async () => {
|
|
59
|
+
listenErrors.push(...Array(20).fill('EACCES'));
|
|
60
|
+
await expect(pickDataStorePort()).rejects.toThrow(
|
|
61
|
+
'No free port between 49152 and 65535 for the journey data store after 20 tries.'
|
|
62
|
+
);
|
|
63
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from 'node:fs';
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
|
|
20
|
+
// Every built connection artifact, keyed by connection id. The build always writes the
|
|
21
|
+
// connections directory when the app declares connections; an app with none has no directory.
|
|
22
|
+
// Module connections are written under their module's folder (connections/<module>/<id>.json),
|
|
23
|
+
// so the directory is read recursively and each artifact keyed by its own connectionId.
|
|
24
|
+
async function readConnectionArtifacts({ buildDirectory }) {
|
|
25
|
+
const directory = path.join(buildDirectory, 'connections');
|
|
26
|
+
let fileNames;
|
|
27
|
+
try {
|
|
28
|
+
fileNames = await fs.promises.readdir(directory, { recursive: true });
|
|
29
|
+
} catch (error) {
|
|
30
|
+
if (error.code === 'ENOENT') return {};
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
const artifacts = {};
|
|
34
|
+
await Promise.all(
|
|
35
|
+
fileNames
|
|
36
|
+
.filter((fileName) => fileName.endsWith('.json'))
|
|
37
|
+
.map(async (fileName) => {
|
|
38
|
+
const content = await fs.promises.readFile(path.join(directory, fileName), 'utf8');
|
|
39
|
+
const artifact = JSON.parse(content);
|
|
40
|
+
artifacts[artifact.connectionId] = artifact;
|
|
41
|
+
})
|
|
42
|
+
);
|
|
43
|
+
return artifacts;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default readConnectionArtifacts;
|
|
@@ -0,0 +1,41 @@
|
|
|
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 dataSessionRegistry from './dataSessionRegistry.js';
|
|
20
|
+
import { JOURNEY_COOKIES, readJourneyCookie } from '../../server/journeyCookies.js';
|
|
21
|
+
|
|
22
|
+
// The data session a request's lowdefy_journey_data cookie names:
|
|
23
|
+
// - null when there is no cookie or its token is not this process's: not a journey's request, so it
|
|
24
|
+
// reads the app's own database like any other;
|
|
25
|
+
// - the session while it is open or closing;
|
|
26
|
+
// - { ended: id } when the token verifies but the session is not open (closed, or never opened). A
|
|
27
|
+
// verified token proves the request came from a journey, so the caller refuses it rather than let
|
|
28
|
+
// it fall back to the real database.
|
|
29
|
+
function readDataSession(cookieHeader) {
|
|
30
|
+
const id = readJourneyCookie({ cookieHeader, name: JOURNEY_COOKIES.data.name });
|
|
31
|
+
if (type.isNone(id)) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const session = dataSessionRegistry.get(id);
|
|
35
|
+
if (type.isNone(session) || (session.state !== 'open' && session.state !== 'closing')) {
|
|
36
|
+
return { ended: id };
|
|
37
|
+
}
|
|
38
|
+
return session;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default readDataSession;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import dataSessionRegistry from './dataSessionRegistry.js';
|
|
18
|
+
import readDataSession from './readDataSession.js';
|
|
19
|
+
import { journeyActorToken } from '../../server/auth/journeyActor.js';
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
dataSessionRegistry.clear();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
function register(state) {
|
|
26
|
+
const session = { id: 'abc', state, work: new Set() };
|
|
27
|
+
dataSessionRegistry.set('abc', session);
|
|
28
|
+
return session;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
test('readDataSession returns null without a data cookie', () => {
|
|
32
|
+
register('open');
|
|
33
|
+
expect(readDataSession(undefined)).toBeNull();
|
|
34
|
+
expect(readDataSession('other=1')).toBeNull();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('readDataSession returns null for a cookie with the wrong token', () => {
|
|
38
|
+
register('open');
|
|
39
|
+
expect(readDataSession('lowdefy_journey_data=forged.abc')).toBeNull();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('readDataSession returns an open or closing session for a verified cookie', () => {
|
|
43
|
+
const session = register('open');
|
|
44
|
+
expect(readDataSession(`a=1; lowdefy_journey_data=${journeyActorToken}.abc`)).toBe(session);
|
|
45
|
+
session.state = 'closing';
|
|
46
|
+
expect(readDataSession(`lowdefy_journey_data=${journeyActorToken}.abc`)).toBe(session);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('readDataSession returns { ended } for a verified cookie naming an unknown or still loading session', () => {
|
|
50
|
+
expect(readDataSession(`lowdefy_journey_data=${journeyActorToken}.gone`)).toEqual({
|
|
51
|
+
ended: 'gone',
|
|
52
|
+
});
|
|
53
|
+
register('loading');
|
|
54
|
+
expect(readDataSession(`lowdefy_journey_data=${journeyActorToken}.abc`)).toEqual({
|
|
55
|
+
ended: 'abc',
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
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 { parseDataSet } from '@lowdefy/node-utils';
|
|
18
|
+
|
|
19
|
+
import checkSharedCollectionIds from './checkSharedCollectionIds.js';
|
|
20
|
+
import findCollectionCollisions from './findCollectionCollisions.js';
|
|
21
|
+
import readConnectionArtifacts from './readConnectionArtifacts.js';
|
|
22
|
+
import resolveDataSetCollection from './resolveDataSetCollection.js';
|
|
23
|
+
|
|
24
|
+
function getKeyedConnectionIds({ dataSet }) {
|
|
25
|
+
return [
|
|
26
|
+
...new Set([
|
|
27
|
+
...Object.keys(dataSet.fixtures),
|
|
28
|
+
...Object.keys(dataSet.generated),
|
|
29
|
+
...Object.keys(dataSet.indexes),
|
|
30
|
+
]),
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// The data set checks only the dev build can make, on top of parseDataSet's: every connection the
|
|
35
|
+
// data set names is a MongoDBCollection with a literal collection, and connections that would merge
|
|
36
|
+
// into one collection under the one-database redirect are refused (when the data set names one of
|
|
37
|
+
// them) or reported, and a generated _id that another connection on the same collection also holds
|
|
38
|
+
// is refused. Runs before any browser opens.
|
|
39
|
+
async function readDataSet({ configDirectory, buildDirectory, name }) {
|
|
40
|
+
const dataSet = await parseDataSet({ configDirectory, name });
|
|
41
|
+
const warnings = [...dataSet.warnings];
|
|
42
|
+
|
|
43
|
+
const artifacts = await readConnectionArtifacts({ buildDirectory });
|
|
44
|
+
const keyedIds = getKeyedConnectionIds({ dataSet });
|
|
45
|
+
const collections = {};
|
|
46
|
+
keyedIds.forEach((connectionId) => {
|
|
47
|
+
collections[connectionId] = resolveDataSetCollection({
|
|
48
|
+
dataSetName: name,
|
|
49
|
+
connectionId,
|
|
50
|
+
artifact: artifacts[connectionId],
|
|
51
|
+
}).collection;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
findCollectionCollisions({ artifacts }).forEach(({ collection, connectionIds }) => {
|
|
55
|
+
const [a, b] = connectionIds;
|
|
56
|
+
const message = `Connections "${a}" and "${b}" both name collection "${collection}" in different databases; under a data set they read one database, so they share one collection.`;
|
|
57
|
+
if (keyedIds.includes(a) || keyedIds.includes(b)) {
|
|
58
|
+
throw new Error(`Data set "${name}": ${message}`);
|
|
59
|
+
}
|
|
60
|
+
warnings.push(message);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
checkSharedCollectionIds({ dataSetName: name, dataSet: { ...dataSet, collections } });
|
|
64
|
+
|
|
65
|
+
return { ...dataSet, collections, warnings };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export default readDataSet;
|